25.5. test — Python 的回归测试包 — Python 文档
25.5. 测试 — Python 的回归测试包
笔记
test 包仅供 Python 内部使用。 为了 Python 的核心开发人员的利益,它被记录下来。 不鼓励在 Python 标准库之外使用此包,因为此处提到的代码可能会在 Python 版本之间更改或删除,恕不另行通知。
test 包包含 Python 的所有回归测试以及模块 test.support 和 test.regrtest
。 test.support 用于增强您的测试,而 test.regrtest
驱动测试套件。
test 包中名称以 test_
开头的每个模块都是特定模块或功能的测试套件。 所有新测试都应使用 unittest 或 doctest 模块编写。 一些较旧的测试是使用“传统”测试风格编写的,将打印的输出与 sys.stdout
进行比较; 这种测试风格被认为是过时的。
25.5.1. 编写单元测试测试包裹
使用 unittest 模块的测试最好遵循一些准则。 一种是通过以 test_
开头并以被测试模块的名称结束来命名测试模块。 测试模块中的测试方法应以 test_
开头,并以对方法测试内容的描述结束。 这是必要的,以便测试驱动程序将方法识别为测试方法。 此外,不应包含该方法的文档字符串。 应使用注释(例如 # Tests function returns only True or False
)来提供测试方法的文档。 这样做是因为文档字符串如果存在就会被打印出来,因此没有说明正在运行的测试。
通常使用基本样板:
import unittest
from test import support
class MyTestCase1(unittest.TestCase):
# Only use setUp() and tearDown() if necessary
def setUp(self):
... code to execute in preparation for tests ...
def tearDown(self):
... code to execute to clean up after tests ...
def test_feature_one(self):
# Test feature one.
... testing code ...
def test_feature_two(self):
# Test feature two.
... testing code ...
... more test methods ...
class MyTestCase2(unittest.TestCase):
... same structure as MyTestCase1 ...
... more test classes ...
def test_main():
support.run_unittest(MyTestCase1,
MyTestCase2,
... list other tests ...
)
if __name__ == '__main__':
test_main()
此样板代码允许测试套件由 test.regrtest
以及作为脚本单独运行。
回归测试的目标是尝试破解代码。 这导致需要遵循一些准则:
测试套件应该测试所有的类、函数和常量。 这不仅包括要呈现给外界的外部 API,还包括“私有”代码。
首选白盒测试(在编写测试时检查正在测试的代码)。 黑盒测试(仅测试已发布的用户界面)不足以确保测试所有边界和边缘情况。
确保测试所有可能的值,包括无效值。 这确保不仅所有有效值都是可接受的,而且不正确的值也得到正确处理。
用尽尽可能多的代码路径。 测试分支发生的位置,从而调整输入以确保通过代码采用尽可能多的不同路径。
为测试代码发现的任何错误添加显式测试。 这将确保如果将来更改代码,错误不会再次出现。
确保在测试后进行清理(例如关闭并删除所有临时文件)。
如果测试依赖于操作系统的特定条件,则在尝试测试之前验证该条件已经存在。
导入尽可能少的模块并尽快完成。 这最大限度地减少了测试的外部依赖性,也最大限度地减少了导入模块的副作用可能导致的异常行为。
尝试最大化代码重用。 有时,测试会因使用的输入类型而有所不同。 通过使用指定输入的类对基本测试类进行子类化来最小化代码重复:
class TestFuncAcceptsSequences(unittest.TestCase): func = mySuperWhammyFunction def test_func(self): self.func(self.arg) class AcceptLists(TestFuncAcceptsSequences): arg = [1, 2, 3] class AcceptStrings(TestFuncAcceptsSequences): arg = 'abc' class AcceptTuples(TestFuncAcceptsSequences): arg = (1, 2, 3)
也可以看看
- 测试驱动开发
- Kent Beck 关于在编写代码之前编写测试的书。
25.5.2. 使用命令行界面运行测试
test.regrtest
模块可以作为脚本运行来驱动 Python 的回归测试套件,这要归功于 -m 选项:python -m test.regrtest。 单独运行脚本会自动开始运行 test 包中的所有回归测试。 它通过查找包中名称以 test_
开头的所有模块、导入它们并执行函数 test_main()
(如果存在)来实现此目的。 要执行的测试的名称也可以传递给脚本。 指定单个回归测试 (python -m test.regrtest test_spam) 将最小化输出,只打印测试是通过还是失败,从而最小化输出。
直接运行 test.regrtest
可以设置哪些资源可用于测试使用。 您可以使用 -u
命令行选项执行此操作。 将 all
指定为 -u
选项的值会启用所有可能的资源:python -m test.regrtest -uall。 如果只需要一个资源(一种更常见的情况),则不需要的资源的逗号分隔列表可能会列在 all
之后。 命令 python -m test.regrtest -uall,-audio,-largefile 将使用除 audio
和 largefile
资源之外的所有资源运行 test.regrtest
。 有关所有资源和更多命令行选项的列表,请运行 python -m test.regrtest -h。
执行回归测试的其他一些方法取决于执行测试的平台。 在 Unix 上,您可以在构建 Python 的顶级目录中运行 make test。 在 Windows 上,从 PCBuild
目录执行 rt.bat 将运行所有回归测试。
2.7.14 版本更改: test 包可以作为脚本运行:python -m test。 这与运行 test.regrtest
模块的工作原理相同。
25.6. 测试支持 — 用于测试的实用函数
笔记
test.test_support
模块已在 Python 3.x 和 2.7.14 中重命名为 test.support。 名称 test.test_support
在 2.7 中作为别名保留。
test.support 模块为 Python 的回归测试提供支持。
该模块定义了以下异常:
- exception test.support.TestFailed
- 测试失败时引发的异常。 这已被弃用,以支持基于 unittest 的测试和 unittest.TestCase 的断言方法。
- exception test.support.ResourceDenied
- unittest.SkipTest 的子类。 当资源(例如网络连接)不可用时引发。 由 requires() 函数引发。
test.support 模块定义了以下常量:
- test.support.verbose
- True 启用详细输出时。 当需要有关运行测试的更多详细信息时,应进行检查。 verbose 由
test.regrtest
设置。
- test.support.have_unicode
- True 当 Unicode 支持可用时。
- test.support.is_jython
- True 如果正在运行的解释器是 Jython。
- test.support.TESTFN
- 设置为可安全用作临时文件名称的名称。 创建的任何临时文件都应关闭并取消链接(删除)。
- test.support.TEST_HTTP_URL
- 为网络测试定义专用 HTTP 服务器的 URL。
test.support 模块定义了以下函数:
- test.support.forget(module_name)
- 从
sys.modules
中删除名为 module_name 的模块,并删除该模块的所有字节编译文件。
- test.support.is_resource_enabled(resource)
- 如果 resource 已启用且可用,则返回 True。 可用资源列表仅在
test.regrtest
执行测试时设置。
- test.support.requires(resource[, msg])
- 如果 resource 不可用,则提高 ResourceDenied。 msg 是 ResourceDenied 如果它被引发的参数。 如果由
__name__
为'__main__'
的函数调用,则始终返回 True。 当测试由test.regrtest
执行时使用。
- test.support.findfile(filename)
- 返回名为 filename 的文件的路径。 如果未找到匹配项,则返回 文件名 。 这并不等于失败,因为它可能是文件的路径。
- test.support.run_unittest(*classes)
执行传递给函数的 unittest.TestCase 子类。 该函数扫描类以查找以前缀
test_
开头的方法并单独执行测试。将字符串作为参数传递也是合法的; 这些应该是
sys.modules
中的键。unittest.TestLoader.loadTestsFromModule()
将扫描每个关联的模块。 这通常出现在以下test_main()
函数中:def test_main(): support.run_unittest(__name__)
这将运行命名模块中定义的所有测试。
- test.support.check_warnings(*filters, quiet=True)
warnings.catch_warnings() 的便利包装器,可以更轻松地测试警告是否正确提出。 它大约相当于调用
warnings.catch_warnings(record=True)
并将 warnings.simplefilter() 设置为always
并带有自动验证记录结果的选项。check_warnings
接受("message regexp", WarningCategory)
形式的二元组作为位置参数。 如果提供了一个或多个 filters,或者可选的关键字参数 quiet 是 False,它会检查以确保警告符合预期:每个指定的过滤器必须至少匹配所附代码引发的警告之一,否则测试失败,如果引发的任何警告与任何指定的过滤器都不匹配,则测试失败。 要禁用这些检查中的第一个,请将 quiet 设置为 True。如果未指定参数,则默认为:
check_warnings(("", Warning), quiet=True)
在这种情况下,所有警告都会被捕获并且不会引发任何错误。
在进入上下文管理器时,会返回一个
WarningRecorder
实例。 来自 catch_warnings() 的底层警告列表可通过记录器对象的 warnings 属性获得。 为方便起见,也可以通过记录器对象直接访问表示最近警告的对象的属性(请参见下面的示例)。 如果没有发出警告,则表示警告的对象的任何其他属性都将返回 None。记录器对象还有一个
reset()
方法,用于清除警告列表。上下文管理器被设计成这样使用:
with check_warnings(("assertion is always true", SyntaxWarning), ("", UserWarning)): exec('assert(False, "Hey!")') warnings.warn(UserWarning("Hide me!"))
在这种情况下,如果没有发出警告,或者发出了其他警告,check_warnings() 将引发错误。
当测试需要更深入地查看警告,而不仅仅是检查它们是否发生时,可以使用这样的代码:
with check_warnings(quiet=True) as w: warnings.warn("foo") assert str(w.args[0]) == "foo" warnings.warn("bar") assert str(w.args[0]) == "bar" assert str(w.warnings[0].args[0]) == "foo" assert str(w.warnings[1].args[0]) == "bar" w.reset() assert len(w.warnings) == 0
这里会捕获所有警告,测试代码直接测试捕获的警告。
2.6 版中的新功能。
2.7 版更改: 新的可选参数 filters 和 quiet。
- test.support.check_py3k_warnings(*filters, quiet=False)
类似于 check_warnings(),但针对 Python 3 兼容性警告。 如果是
sys.py3kwarning == 1
,它会检查警告是否有效。 如果是sys.py3kwarning == 0
,它会检查是否没有发出警告。 它接受("message regexp", WarningCategory)
形式的 2 元组作为位置参数。 当可选关键字参数 quiet 是 True 时,如果过滤器什么也没捕获,它不会失败。 没有参数,它默认为:check_py3k_warnings(("", DeprecationWarning), quiet=False)
2.7 版中的新功能。
- test.support.captured_stdout()
这是一个上下文管理器,它使用 StringIO.StringIO 对象作为 sys.stdout 运行 with 语句主体。 可以使用 with 语句的
as
子句检索该对象。使用示例:
with captured_stdout() as s: print "hello" assert s.getvalue() == "hello\n"
2.6 版中的新功能。
- test.support.import_module(name, deprecated=False)
此函数导入并返回命名模块。 与普通导入不同,如果模块无法导入,此函数会引发 unittest.SkipTest。
如果 deprecated 是 True,则在此导入过程中会抑制模块和包弃用消息。
2.7 版中的新功能。
- test.support.import_fresh_module(name, fresh=(), blocked=(), deprecated=False)
此函数通过在执行导入之前从
sys.modules
中删除命名模块来导入并返回命名 Python 模块的新副本。 请注意,与 reload() 不同的是,原始模块不受此操作的影响。fresh 是附加模块名称的迭代,在执行导入之前也从
sys.modules
缓存中删除。blocked 是一个可迭代的模块名称,在导入期间在模块缓存中替换为
0
,以确保尝试导入它们会引发ImportError
。在开始导入之前保存命名模块以及在 fresh 和 blocked 参数中命名的任何模块,然后在新导入完成后重新插入到
sys.modules
中。如果 deprecated 是 True,则在此导入过程中会抑制模块和包弃用消息。
如果无法导入命名模块,此函数将引发 unittest.SkipTest。
使用示例:
# Get copies of the warnings module for testing without # affecting the version being used by the rest of the test suite # One copy uses the C implementation, the other is forced to use # the pure Python fallback implementation py_warnings = import_fresh_module('warnings', blocked=['_warnings']) c_warnings = import_fresh_module('warnings', fresh=['_warnings'])
2.7 版中的新功能。
test.support 模块定义了以下类:
- class test.support.TransientResource(exc[, **kwargs])
实例是一个上下文管理器,如果指定的异常类型被引发,它会引发 ResourceDenied。 任何关键字参数都被视为属性/值对,以与 with 语句中引发的任何异常进行比较。 仅当所有对与异常上的属性正确匹配时,才会引发 ResourceDenied。
2.6 版中的新功能。
- class test.support.EnvironmentVarGuard
用于临时设置或取消设置环境变量的类。 实例可以用作上下文管理器,并具有完整的字典接口,用于查询/修改底层
os.environ
。 从上下文管理器退出后,通过此实例对环境变量所做的所有更改都将回滚。2.6 版中的新功能。
2.7 版本变化:增加字典接口。
- EnvironmentVarGuard.set(envvar, value)
- 临时设置环境变量
envvar
为value
的值。
- EnvironmentVarGuard.unset(envvar)
- 暂时取消设置环境变量
envvar
。
- class test.support.WarningsRecorder
用于记录单元测试警告的类。 有关更多详细信息,请参阅上面 check_warnings() 的文档。
2.6 版中的新功能。