Python基础教程——测试基础
测试基础
先测试再编码
测试在先,编码在后。这也称为测试驱动的编程。
准确的需求说明
需求说明,也就是描述程序必须满足何种需求的文档(或便条)。
功能需求,即程序必须提供哪些功能。
理念是先编写测试,再编写让测试通过的程序。
测试程序就是需求说明,可帮助确保程序开发过程紧扣这些需求。
简单的测试程序。
from area import rect_area
height = 3
width = 4
correct_answer = 12
answer = rect_area(height, width)
if answer == correct_answer:
print('Test passed ')
else:
print('Test failed ')
做好应对变化的准备
自动化测试不仅可在编写程序时提供极大的帮助,还有助于在修改代码时避免累积错误,这在程序规模很大时尤其重要。
代码覆盖率
覆盖率(coverage)是一个重要的测试概念。
运行测试时,很可能达不到运行所有代码的理想状态。
可使用覆盖率工具,它们测量测试期间实际运行的代码所占的比例。
要确保较高的 测试覆盖率,方法之一是秉承测试驱动开发的理念。
只要能确保先编写测试再编写函数,就能肯定每个函数都是经过测试的。
测试四步曲
测试驱动开发过程的各个阶段。
- (1) 确定需要实现的新功能。可将其记录下来,再为之编写一个测试。
- (2) 编写实现功能的框架代码,让程序能够运行(不存在语法错误之类的问题),但测试依然无法通过。
- (3) 编写让测试刚好能够通过的代码。
- (4) 改进(重构)代码以全面而准确地实现所需的功能,同时确保测试依然能够成功。
测试工具
- unittest : 一个通用的测试框架。
- doctest : 一个更简单的模块,是为检查文档而设计的,但也非常适合用来编写单元测试。
doctest
假设编写了一个计算平方的函数,并在其文档字符串中添加了一个示例。
def square(x):
'''
计算平方并返回结果
>>> square(2)
4
>>> square(3)
9
'''
return x * x
假设函数 square是在模块 my_math (即文件my_math.py)中定义的,就可在模块末尾添加如下代码:
if name =='__main__':
import doctest, my_math
doctest.testmod(my_math)
函数 doctest.testmod 读取模块中的所有文档字符串,查找看起来像是从交互式解释器中摘取的示例,再检查这些示例是否反映了实际情况。
为获得更多的输出,可在运行脚本时指定开关 -v (verbose,意为详尽)。
$ python my_math.py -v
生成如下输出:
Running my_math.__doc__
0 of 0 examples failed in my_math.__doc__
Running my_math.square.__doc__
Trying: square(2)
Expecting: 4
Ok
Trying: square(3)
Expecting: 9
ok
0 of 2 examples failed in my_math.square.__doc__
1 items had no tests:
test
1 items passed all tests:
2 tests in my_math.square
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
函数 testmod 检查模块的文档字符串和函数的文档字符串。
unittest
unittest (基于流行的Java测试框架JUnit)更灵活、更强大。
假设你要编写一个名为 my_math 的模块,其中包含一个计算乘积的函数 product 。
一个使用框架 unittest 的简单测试。
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()
函数 unittest.main 负责替你运行测试:实例化所有的 TestCase 子类,并运行所有名称以 test打头的方法。
运行这个测试脚本将引发异常,指出模块 my_math 不存在。
TestCase 类还包含很多与之类似的方法,如 assertTrue 、 assertIsNotNone 和 assertAlmostEqual 。
模块 unittest 区分错误和失败。
错误指的是引发了异常,而失败是调用 failUnless 等方法的结果。
超越单元测试
两个工具:源代码检查和性能分析。
源代码检查是一种发现代码中常见错误或问题的方式。
性能分析指的是搞清楚程序的运行速度到底有多快。
单元测试可让程序管用,源代码检查可让程序更好,而性能分析可让程序更快。
使用 PyChecker 和 PyLint 检查源代码
PyChecker(pychecker.sf.net)是用于检查Python源代码的唯一工具,能够找出诸如给函数提供的参数不对等错误。
PyLint(pylint.org),它支持PyChecker提供的大部分功能,还有很多其他的功能,如变量名是否符合指定的命名约定、你是否遵守了自己的编码标准等。
可以命令行脚本的方式运行它们(PyChecker和PyLint对应的脚本分别为pychecker 和 pylint ),也可将其作为Python模块(名称与前面相同)。
使用PyChecker来检查文件,可运行这个脚本并将文件名作为参数,如下所示:
pychecker file1.py file2.py ...
使用PyLint检查文件时,需要将模块(或包)名作为参数:
pylint module
PyChecker和PyLint都可作为模块(分别是 pychecker.checker 和 pylint.lint )导入。
导入 pychecker.checker 时,它会检查后续代码(包括导入
的模块),并将警告打印到标准输出。
模块 pylint.lint 包含一个函数 Run ,这个函数是供脚本 pylint 本身使用的。它也将警告打印出来,而不是以某种方式将其返回。
在Python中,可通过模块 subprocess 来使用命令行工具。
使用模块 subprocess 调用外部检查器。
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()
调用检查器脚本时,指定了一些命令行开关,以免无关的输出干扰测试。
对于 pychecker ,指定了开关 -Q (quiet,意为静默);
对于 pylint ,指定了开关 -rn (其中 n 表示no)以关闭报告,这意味着将只显示警告和错误。
性能分析
标准库包含一个性能分析模块 profile ,还有一个速度更快C语言版本,名为 cProfile 。
这个性能分析模块,只需调用其方法 run 并提供一个字符串参数。
>>> import cProfile
>>> from my_math import product
>>> cProfile.run('product(1, 2)')
然后,使用模块 pstats 来研究分析结果。
>>> import pstats
>>> p = pstats.Stats('my_math.profile')
小结
本章介绍的新函数
学习参考资料:
《Python基础教程》 第3版