整体文章目录
一、 当前章节目录
二、测试驱动开发
测试驱动开发是敏捷开发中的一个重要组成部分,其基本思想是测试先行。也就是说,在开发具体的功能代码之前,需要首先编写此功能的测试代码。只有通过了测试代码,才能够加入代码仓库中。
2.1 测试驱动开发模式
传统的软件开发模型包括瀑布模型、演进模型、螺旋模型、喷泉模型和智能模型等。
在传统的软件开发模型中,测试阶段是放在软件生命周期的后期来完成的。这种处理方式使得软件编码中的问题在后期才能够被发现,从而会造成严重的后果。在这种开发模式下,测试阶段所占的时间比例要占整个开发所用时间的一半左右。为了避免传统开发模式下的这些问题,敏捷方法学在快速开发的目标上强调基于测试的开发过程。
2.2 TDD的优势
因为TDD采用的是以测试作为核心的开发模式,所以具有传统开发模型所不具备的优点。
- 可以在短时间内构建出一个软件的原型。
- 改正程序错误方面。
- 项目主管可以很清楚地看到已经完成了哪些软件需求,从而有助于把握项目进度。
- 通过编写测试用例,优先考虑实现代码的使用需求,包括功能、过程和接口等。能够提供代码的内聚性和复用性。
2.3 TDD的使用步骤
- 明确当前代码需要完成的功能,必要的时候书写相关的接口等。
- 快速新增一个测试。
- 运行所有的测试,看是否可以通过。若是通过,跳到步骤6。
- 对功能代码进行细微的改动。
- 重新运行所以的测试用例,保证全部通过。
- 对代码进行重构,消除重复设计,优化代码结构。
两类代码测试代码模块:
- unittest:可以用来书写PyUnit的测试代码,和其他语言的单元测试工具一样,支持对软件代码的自动化测试。
- docttest:是一个特殊的测试模块,此模块将测试用例内置在了函数的文档字符串中,从而实现了文档和测试代码的统一。
三、unittest框架
3.1 unittest模块介绍
- 支持测试的自动化处理。
- 支持单元测试的各种重要概念。其中最重要的概念是测试用例。
- 一组测试用例包含共同需要处理的代码,称之为测试固件。这些代码可能是测试之前需要进行的初始化工作,也可能是测试结束后所要做的代码清理工作。
- 随着测试用例的增多,逐个管理测试用例显然是非常低效的。unittest模块提供了测试套件来解决这个问题。测试套件将一组测试用例集合起来作为一个测试对象。
- 包含一个测试运行器,用来运行这些测试并为用户提供输出。
3.2 构建测试用例
import unittest # 导入模块
class StringReplaceTestCase1(unittest.TestCase):
"""测试空字符替换"""
def runTest(self):
source = "HELLO"
expect = "HELLO"
result = source.replace(source, "")
self.assertEqual(expect, result)
class StringReplaceTestCase2(unittest.TestCase):
"""测试空字符替换成常规字符"""
def runTest(self):
source = "HELLO"
expect = "*H*E*L*L*O*"
result = source.replace("", "*")
self.assertEqual(expect, result)
class StringReplaceTestCase3(unittest.TestCase):
"""测试常规字符替换为空字符"""
def runTest(self):
source = "HELLO"
expect = "HEO"
result = source.replace("LL", "")
self.assertEqual(expect, result)
class StringReplaceTestCase4(unittest.TestCase):
"""测试常规字符替换"""
def runTest(self):
source = "HELLO"
expect = "HEMMO"
result = source.replace("LL", "MM")
self.assertEqual(expect, result)
3.3 构建测试固件
import unittest # 导入模块
class SimpleStringReplaceTestCase(unittest.TestCase):
"""准备测试的源字符串"""
def setUp(self):
self.source = "HELLO"
class StringReplaceTestCase1(SimpleStringReplaceTestCase):
"""测试空字符替换"""
def runTest(self):
expect = "HELLO"
result = self.source.replace(source, "")
self.assertEqual(expect, result)
class StringReplaceTestCase2(SimpleStringReplaceTestCase):
"""测试空字符替换成常规字符"""
def runTest(self):
expect = "*H*E*L*L*O*"
result = self.source.replace("", "*")
self.assertEqual(expect, result)
class StringReplaceTestCase3(SimpleStringReplaceTestCase):
"""测试常规字符替换为空字符"""
def runTest(self):
expect = "HEO"
result = self.source.replace("LL", "")
self.assertEqual(expect, result)
class StringReplaceTestCase4(SimpleStringReplaceTestCase):
"""测试常规字符替换"""
def runTest(self):
expect = "HEMMO"
result = self.source.replace("LL", "MM")
self.assertEqual(expect, result)
3.4 组织多个测试用例
import unittest # 导入模块
class StringReplaceTestCase(unittest.TestCase):
"""准备测试的源字符串"""
def setUp(self):
self.source = "HELLO"
"""测试空字符替换"""
def testBlank(self):
expect = "HELLO"
result = self.source.replace(source, "")
self.assertEqual(expect, result)
"""测试空字符替换成常规字符"""
def testBlankOrd(self):
expect = "*H*E*L*L*O*"
result = self.source.replace("", "*")
self.assertEqual(expect, result)
"""测试常规字符替换为空字符"""
def testOrdBlank(self):
expect = "HEO"
result = self.source.replace("LL", "")
self.assertEqual(expect, result)
"""测试常规字符替换"""
def testOrd(self):
expect = "HEMMO"
result = self.source.replace("LL", "MM")
self.assertEqual(expect, result)
3.5 构建测试套件
def suite():
StringReplaceTestSuite = unittest.TestSuite()
StringReplaceTestSuite.addTest(SimpleStringReplaceTestCase("testBlank"))
StringReplaceTestSuite.addTest(SimpleStringReplaceTestCase("testOrd"))
return StringReplaceTestSuite
def suite():
tests = ['testBlank', 'testOrd']
StringReplaceTestSuite = unittest.TestSuite(
map(StringReplaceTestCase, tests)
)
return StringReplaceTestSuite
StringReplaceTestSuite = unittest.makeSuite(StringReplaceTestCase, 'test')
return StringReplaceTestSuite
import unittest # 导入模块
class StringStripTestCase(unittest.TestCase):
def testBlank(self):
expect = "HELLO"
st = "HELLO "
result = st.strip()
self.assertEqual(expect, result)
def testStr(self):
expect = "HELLO"
st = "xxHELLOxx"
result = st.strip("xx")
self.assertEqual(expect, result)
class StringReplaceTestCase(unittest.TestCase):
# shenglu
def suite():
StringStripTestSuite = unittest.makeSuite(StringStripTestCase, 'test')
StringReplaceTestSuite = unittest.makeSuite(StringReplaceTestCase, 'test')
alltests = unittest.TestSuite((StringStripTestSuite, StringReplaceTestSuite))
return alltests
3.6 重构代码
import unittest # 导入模块
class StringReplaceTestCase(unittest.TestCase):
"""准备测试的源字符串"""
def setUp(self):
self.source = "HELLO"
def checkequal(self, result, object, methodname, *args):
realresult = getattr(object, methodname)(*args)
self.assertEqual(
result,
realresult
)
"""测试空字符替换"""
def testBlank(self):
self.checkequal("HELLO", self.source, "replace", "", "")
"""测试空字符替换成常规字符"""
def testBlankOrd(self):
self.checkequal("*H*E*L*L*O*", self.source, "replace", "", "*")
"""测试常规字符替换为空字符"""
def testOrdBlank(self):
self.checkequal("HE*O", self.source, "replace", "LL", "*")
"""测试常规字符替换"""
def testOrd(self):
self.checkequal("HEMMO", self.source, "replace", "LL", "MM")
3.7 执行测试
import unittest # 导入模块
class StringStripTestCase(unittest.TestCase):
# 省略StringStripTestCase实现代码
class StringReplaceTestCase(unittest.TestCase):
# 省略StringReplaceTestCase实现代码
def suite():
StringStripTestSuite = unittest.makeSuite(StringStripTestCase, 'test')
StringReplaceTestSuite = unittest.makeSuite(StringReplaceTestCase, 'test')
alltests = unittest.TestSuite((StringStripTestSuite, StringReplaceTestSuite))
return alltests
if __name__ == "__main__":
runner == unittest.TextTestRunner()
runner.run(suite())
代码
In [1]: import unittest
In [2]: import utest6
In [3]: runner = unittest.TextTestRunner()
In [4]: runner.run(utest6.StringReplaceTestCase("testBlank"))
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Out[4]: <unittest.runner.TextTestResult run=1 errors=0 failures=0>
# 省略部分代码
def suite():
StringStripTestSuite = unittest.makeSuite(StringStripTestCase, 'test')
StringReplaceTestSuite = unittest.makeSuite(StringReplaceTestCase, 'test')
alltests = unittest.TestSuite((StringStripTestSuite, StringReplaceTestSuite))
return alltests
if __name__ == "__main__":
# runner == unittest.TextTestRunner()
# runner.run(suite())
unittest.main()
In [5]: %run utest7.py
......
----------------------------------------------------------------------
Ran 6 tests in 0.007s
OK
In [6]: %run utest7.py -v
testBlank (__main__.StringReplaceTestCase) ... ok
testBlankOrd (__main__.StringReplaceTestCase) ... ok
testOrd (__main__.StringReplaceTestCase) ... ok
testOrdBlank (__main__.StringReplaceTestCase) ... ok
testBlank (__main__.StringStripTestCase) ... ok
testStr (__main__.StringStripTestCase) ... ok
----------------------------------------------------------------------
Ran 6 tests in 0.009s
OK
In [7]: %run utest7.py -v
testBlank (__main__.StringReplaceTestCase) ... ok
testBlankOrd (__main__.StringReplaceTestCase) ... ok
testOrd (__main__.StringReplaceTestCase) ... ok
testOrdBlank (__main__.StringReplaceTestCase) ... FAIL
testBlank (__main__.StringStripTestCase) ... ok
testStr (__main__.StringStripTestCase) ... ok
======================================================================
FAIL: testOrdBlank (__main__.StringReplaceTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\86155\Desktop\Python\第16章 敏捷方法学在Python中的应用——测试驱动开发\utest7.py", line 38, in testOrdBlank
self.checkequal("HEO", self.source, "replace", "LL", "*")
File "C:\Users\86155\Desktop\Python\第16章 敏捷方法学在Python中的应用——测试驱动开发\utest7.py", line 23, in checkequal
self.assertEqual(
AssertionError: 'HEO' != 'HE*O'
- HEO
+ HE*O
? +
----------------------------------------------------------------------
Ran 6 tests in 0.013s
FAILED (failures=1)
An exception has occurred, use %tb to see the full traceback.
SystemExit: True
四、使用doctest进行测试
4.1 doctest模块介绍
doctest模块作为一种新的单元测试框架,可以有效地利用代码注释中的文档内容。这种处理方式使得文档即可以作为测试代码来执行。
4.2 构建可执行文档
def factorial(n):
"""
这里包含了多个测试例,用来测试factorial方法的返回值
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
检测浮点数是否为整数
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000
It must also not be ridiculously large:
溢出检查
>>> factorial(1e100)
Traceback (most recent call last):
...
OverflowError: n too large
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like le300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
def _test():
import doctest
doctest.testmod()
# doctest.testfile("factorial_docstring.txt")
if __name__ == "__main__":
_test()
# if __name__=='__main__':
# import doctest
# doctest.testmod(verbose=True)
4.3 执行doctest测试
In [1]: %run dtest8.py
In [2]: %run dtest8.py -v
Trying:
[factorial(n) for n in range(6)]
Expecting:
[1, 1, 2, 6, 24, 120]
ok
Trying:
factorial(30)
Expecting:
265252859812191058636308480000000
ok
Trying:
factorial(-1)
Expecting:
Traceback (most recent call last):
...
ValueError: n must be >= 0
ok
Trying:
factorial(30.1)
Expecting:
Traceback (most recent call last):
...
ValueError: n must be exact integer
ok
Trying:
factorial(30.0)
Expecting:
265252859812191058636308480000000
ok
Trying:
factorial(1e100)
Expecting:
Traceback (most recent call last):
...
OverflowError: n too large
ok
2 items had no tests:
__main__
__main__._test
1 items passed all tests:
6 tests in __main__.factorial
6 tests in 3 items.
6 passed and 0 failed.
Test passed.