测试基础
先测试再编码
测试驱动的编程
测试四部曲
- 确定需要实现的新功能。可将其记录下来,再为之编写一个测试。
- 编写实现功能的框架代码,让程序能够运行(不存在语法错误之类的问题),但测试依然无法通过。
- 编写让测试刚好能够通过的代码。
- 改进(重构)代码以全面而准确地实现所需的功能,同时确保测试依然能够成功。
测试工具
- unittest:一个通用的测试框架。
- doctest:一个更简单的模块,是为检查文档而设计的,但也非常适合用来编写单元测试。
doctest
#my_math.py
def square(x):
'''
计算平方并返回结果
>>> square(2)
4
>>> square(3)
9
'''
return x * x
if __name__=='__main__':
import doctest, my_math
doctest.testmod(my_math)
运行结果如下:
# Window PowerShell
PS C:\Users\15735\Desktop> python my_math.py
# -v(verbose,意为详尽)
PS C:\Users\15735\Desktop> python my_math.py -v
Trying:
square(2)
Expecting:
4
ok
Trying:
square(3)
Expecting:
9
ok
1 items had no tests:
my_math
1 items passed all tests:
2 tests in my_math.square
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
PS:
- 需要到cmd执行命令 python my_math.py
- if
__name__
而不是name - 4和9后面不能有空格,否则会报错,如下
PS C:\Users\15735\Desktop> python my_math.py
**********************************************************************
File "C:\Users\15735\Desktop\my_math.py", line 4, in my_math.square
Failed example:
square(2)
Expected:
4
Got:
4
**********************************************************************
File "C:\Users\15735\Desktop\my_math.py", line 6, in my_math.square
Failed example:
square(3)
Expected:
9
Got:
9
**********************************************************************
1 items had failures:
2 of 2 in my_math.square
***Test Failed*** 2 failures.
unittest
一个使用框架unittest的简单测试
# test_my_math.py
import unittest, my_math
class ProductTestCase(unittest.TestCase):
def test_integers(self):
for x in range(-10, 10):
for y in range(-10, 10):
p = my_math.product(x, y)
self.assertEqual(p, x * y, 'Integer multiplication failed')
def test_floats(self):
for x in range(-10, 10):
for y in range(-10, 10):
x = x / 10
y = y / 10
p = my_math.product(x, y)
self.assertEqual(p, x * y, 'Float multiplication failed')
if __name__ == '__main__': unittest.main()
# my_math.py
def product(x, y):
pass
执行test_my_math.py,输出如下:
## FF 表示两次失败
PS C:\Users\15735\desktop> python test_my_math.py
FF
======================================================================
FAIL: test_floats (__main__.ProductTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\15735\desktop\test_my_math.py", line 16, in test_floats
self.assertEqual(p, x * y, 'Float multiplication failed')
AssertionError: None != 1.0 : Float multiplication failed
======================================================================
FAIL: test_integers (__main__.ProductTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\15735\desktop\test_my_math.py", line 8, in test_integers
self.assertEqual(p, x * y, 'Integer multiplication failed')
AssertionError: None != 100 : Integer multiplication failed
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=2)
PS:
如果你定义了方法setUp和tearDown,它们将分别在每个测试方法之前和之后执行。你可使用这些方法来执行适用于所有测试的初始化代码和清理代码,这些代码称为测试夹具(test fixture)。
修改示例
def product(x, y):
return x * y
输出如下:
## ..表示测试
PS C:\Users\15735\desktop> python test_my_math.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
超越单元测试
源代码检查和性能分析:单元测试可让程序管用,源代码检查可让程序更好,而性能分析可让程序更快。
使用PyChecker和PyLint检查源代码
要使用PyChecker来检查文件,可运行这个脚本并将文件名作为参数,如下所示:
pychecker file1.py file2.py …
使用PyLint检查文件时,需要将模块(或包)名作为参数:
pylint module
使用模块subprocess调用外部检查器
# pylint 指定了开关-rn(其中n表示no)以关闭报告,这意味着将只显示警告和错误。
import unittest, my_math
from subprocess import Popen, PIPE
class ProductTestCase(unittest.TestCase):
#在这里插入以前的测试
#def test_with_PyChecker(self):
# cmd = 'pychecker', '-Q', my_math.__file__.rstrip('c')
# pychecker = Popen(cmd, stdout=PIPE, stderr=PIPE)
# self.assertEqual(pychecker.stdout.read(), '')
def test_with_PyLint(self):
cmd = 'pylint', '-rn', 'my_math'
pylint = Popen(cmd, stdout=PIPE, stderr=PIPE)
self.assertEqual(pylint.stdout.read(), '')
if __name__ == '__main__': unittest.main()
"""
一个简单的数学模块
"""
__revision__ = '0.1'
def product(factor1, factor2):
'The product of two numbers'
return factor1 * factor2
运行结果如下:
PS C:\Users\15735\desktop> python .\test_my_math.py
FD:\Program Files\Python\lib\subprocess.py:1072: ResourceWarning: subprocess 15416 is still running
_warn("subprocess %s is still running" % self.pid,
ResourceWarning: Enable tracemalloc to get the object allocation traceback
D:\Program Files\Python\lib\unittest\case.py:613: ResourceWarning: unclosed file <_io.BufferedReader name=3>
outcome.errors.clear()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
D:\Program Files\Python\lib\unittest\case.py:613: ResourceWarning: unclosed file <_io.BufferedReader name=4>
outcome.errors.clear()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
======================================================================
FAIL: test_with_PyLint (__main__.ProductTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\15735\desktop\test_my_math.py", line 15, in test_with_PyLint
self.assertEqual(pylint.stdout.read(), '')
AssertionError: b'' != ''
----------------------------------------------------------------------
Ran 1 test in 0.260s
FAILED (failures=1)
性能分析
标准库包含一个卓越的性能分析模块profile,还有一个速度更快C语言版本,名为cProfile。这个性能分析模块使用起来很简单,只需调用其方法run并提供一个字符串参数。
>>> import cProfile
>>> from my_math import product
>>> cProfile.run('product(1, 2)')
4 function calls in 0.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 my_math.py:6(product)
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
第二个参数向run提供一个文件名(如’my_math.profile’),分析结果将保存到这个文件中。然后,就可使用模块pstats来研究分析结果了。
>>> cProfile.run('product(1, 2)', 'my_math.profile')
>>> p = pstats.Stats('my_math.profile')
>>> p
<pstats.Stats object at 0x00000201869BACE0>
PS:
标准库还包含一个名为timeit的模块,提供了一种对小段Python代码的运行时间进行测试的简单方式。
小结
测试驱动编程:大致而言,测试驱动编程意味着先测试再编码。有了测试,你就能信心满满地修改代码,这让开发和维护工作更加灵活。
模块doctest和unittest:需要在Python中进行单元测试时,这些工具必不可少。模块doctest设计用于检查文档字符串中的示例,但也可轻松地使用它来设计测试套件。为让测试套件更灵活、结构化程度更高,框架unittest很有帮助。
PyChecker和PyLint:这两个工具查看源代码并指出潜在(和实际)的问题。它们检查代码的方方面面——从变量名太短到永远不会执行的代码段。你只需编写少量的代码,就可将它们加入测试套件,从而确保所有修改和重构都遵循了你采用的编码标准。
性能分析:如果你很在乎速度,并想对程序进行优化(仅当绝对必要时才这样做),应首先进行性能分析:使用模块profile或cProfile来找出代码中的瓶颈。