26.8. test — Python 的回归测试包 — Python 文档

来自菜鸟教程
Python/docs/3.6/library/test
跳转至:导航、​搜索

26.8. 测试 — Python 的回归测试包

笔记

test 包仅供 Python 内部使用。 为了 Python 的核心开发人员的利益,它被记录下来。 不鼓励在 Python 标准库之外使用此包,因为此处提到的代码可能会在 Python 版本之间更改或删除,恕不另行通知。



test 包包含 Python 的所有回归测试以及模块 test.supporttest.regrtesttest.support 用于增强您的测试,而 test.regrtest 驱动测试套件。

test 包中名称以 test_ 开头的每个模块都是特定模块或功能的测试套件。 所有新测试都应使用 unittestdoctest 模块编写。 一些较旧的测试是使用“传统”测试风格编写的,将打印的输出与 sys.stdout 进行比较; 这种测试风格被认为是过时的。

也可以看看

模块 unittest
编写 PyUnit 回归测试。
模块 doctest
嵌入在文档字符串中的测试。


26.8.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 ...

if __name__ == '__main__':
    unittest.main()

此代码模式允许测试套件由 test.regrtest 自行运行,作为支持 unittest CLI 的脚本,或通过 python -m unittest CLI。

回归测试的目标是尝试破解代码。 这导致需要遵循一些准则:

  • 测试套件应该测试所有的类、函数和常量。 这不仅包括要呈现给外界的外部 API,还包括“私有”代码。

  • 首选白盒测试(在编写测试时检查正在测试的代码)。 黑盒测试(仅测试已发布的用户界面)不足以确保测试所有边界和边缘情况。

  • 确保测试所有可能的值,包括无效值。 这确保不仅所有有效值都是可接受的,而且不正确的值也得到正确处理。

  • 用尽尽可能多的代码路径。 测试分支发生的位置,从而调整输入以确保通过代码采用尽可能多的不同路径。

  • 为测试代码发现的任何错误添加显式测试。 这将确保如果将来更改代码,错误不会再次出现。

  • 确保在测试后进行清理(例如关闭并删除所有临时文件)。

  • 如果测试依赖于操作系统的特定条件,则在尝试测试之前验证该条件已经存在。

  • 导入尽可能少的模块并尽快完成。 这最大限度地减少了测试的外部依赖性,也最大限度地减少了导入模块的副作用可能导致的异常行为。

  • 尝试最大化代码重用。 有时,测试会因使用的输入类型而有所不同。 通过使用指定输入的类对基本测试类进行子类化来最小化代码重复:

    class TestFuncAcceptsSequencesMixin:
    
        func = mySuperWhammyFunction
    
        def test_func(self):
            self.func(self.arg)
    
    class AcceptLists(TestFuncAcceptsSequencesMixin, unittest.TestCase):
        arg = [1, 2, 3]
    
    class AcceptStrings(TestFuncAcceptsSequencesMixin, unittest.TestCase):
        arg = 'abc'
    
    class AcceptTuples(TestFuncAcceptsSequencesMixin, unittest.TestCase):
        arg = (1, 2, 3)

    使用此模式时,请记住从 unittest.TestCase 继承的所有类都作为测试运行。 上例中的 Mixin 类没有任何数据,因此无法自行运行,因此它不继承自 unittest.TestCase

也可以看看

测试驱动开发
Kent Beck 关于在编写代码之前编写测试的书。


26.8.2. 使用命令行界面运行测试

test 包可以作为脚本运行来驱动 Python 的回归测试套件,这要归功于 -m 选项:python -m test。 在引擎盖下,它使用 test.regrtest; 在以前的 Python 版本中使用的调用 python -m test.regrtest 仍然有效。 单独运行脚本会自动开始运行 test 包中的所有回归测试。 它通过查找包中名称以 test_ 开头的所有模块,导入它们并执行函数 test_main()(如果存在)或通过 unittest.TestLoader.loadTestsFromModule if test_main 不存在。 要执行的测试的名称也可以传递给脚本。 指定单个回归测试 (python -m test test_spam) 将最小化输出并且只打印测试是通过还是失败。

直接运行 test 可以设置哪些资源可用于测试使用。 您可以使用 -u 命令行选项执行此操作。 将 all 指定为 -u 选项的值将启用所有可能的资源:python -m test -uall。 如果只需要一个资源(一种更常见的情况),则不需要的资源的逗号分隔列表可能会列在 all 之后。 命令 python -m test -uall,-audio,-largefile 将使用除 audiolargefile 资源之外的所有资源运行 test。 有关所有资源和更多命令行选项的列表,请运行 python -m test -h

执行回归测试的其他一些方法取决于执行测试的平台。 在 Unix 上,您可以在构建 Python 的顶级目录中运行 make test。 在 Windows 上,从 PCBuild 目录执行 rt.bat 将运行所有回归测试。


26.9. 测试支持 — Python 测试套件的实用程序

test.support 模块为 Python 的回归测试套件提供支持。

笔记

test.support 不是公共模块。 它记录在此处以帮助 Python 开发人员编写测试。 此模块的 API 可能会发生变化,而不会考虑版本之间的向后兼容性问题。


该模块定义了以下异常:

exception test.support.TestFailed
测试失败时引发的异常。 这已被弃用,以支持基于 unittest 的测试和 unittest.TestCase 的断言方法。
exception test.support.ResourceDenied
unittest.SkipTest 的子类。 当资源(例如网络连接)不可用时引发。 由 requires() 函数引发。

test.support 模块定义了以下常量:

test.support.verbose
True 启用详细输出时。 当需要有关运行测试的更多详细信息时,应进行检查。 verbosetest.regrtest 设置。
test.support.is_jython
True 如果正在运行的解释器是 Jython。
test.support.TESTFN
设置为可安全用作临时文件名称的名称。 创建的任何临时文件都应关闭并取消链接(删除)。

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=None)
如果 resource 不可用,则提高 ResourceDeniedmsgResourceDenied 如果它被引发的参数。 如果由 __name__'__main__' 的函数调用,则始终返回 True。 当测试由 test.regrtest 执行时使用。
test.support.findfile(filename, subdir=None)

返回名为 filename 的文件的路径。 如果未找到匹配项,则返回 文件名 。 这并不等于失败,因为它可能是文件的路径。

设置 subdir 表示用于查找文件的相对路径,而不是直接在路径目录中查找。

test.support.run_unittest(\*classes)

执行传递给函数的 unittest.TestCase 子类。 该函数扫描类以查找以前缀 test_ 开头的方法并单独执行测试。

将字符串作为参数传递也是合法的; 这些应该是 sys.modules 中的键。 unittest.TestLoader.loadTestsFromModule() 将扫描每个关联的模块。 这通常出现在以下 test_main() 函数中:

def test_main():
    support.run_unittest(__name__)

这将运行命名模块中定义的所有测试。

test.support.run_doctest(module, verbosity=None)

在给定的 模块 上运行 doctest.testmod()。 返回 (failure_count, test_count)

如果 verbosityNone,则 doctest.testmod() 运行时详细设置为 verbose。 否则,它会在详细设置为 None 的情况下运行。

test.support.check_warnings(\*filters, quiet=True)

warnings.catch_warnings() 的便利包装器,可以更轻松地测试警告是否正确提出。 它大约相当于调用 warnings.catch_warnings(record=True) 并将 warnings.simplefilter() 设置为 always 并带有自动验证记录结果的选项。

check_warnings 接受 ("message regexp", WarningCategory) 形式的二元组作为位置参数。 如果提供了一个或多个 filters,或者可选关键字参数 quietFalse,它会检查以确保警告如预期:每个指定的过滤器必须匹配至少一个由封闭代码引发的警告或测试失败,如果引发的任何警告与任何指定的过滤器不匹配,则测试失败。 要禁用这些检查中的第一个,请将 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

这里会捕获所有警告,测试代码直接测试捕获的警告。

3.2 版更改: 新的可选参数 filtersquiet

test.support.captured_stdin()
test.support.captured_stdout()
test.support.captured_stderr()

使用 io.StringIO 对象临时替换命名流的上下文管理器。

与输出流一起使用的示例:

with captured_stdout() as stdout, captured_stderr() as stderr:
    print("hello")
    print("error", file=sys.stderr)
assert stdout.getvalue() == "hello\n"
assert stderr.getvalue() == "error\n"

使用输入流的示例:

with captured_stdin() as stdin:
    stdin.write('hello\n')
    stdin.seek(0)
    # call test code that consumes from sys.stdin
    captured = input()
self.assertEqual(captured, "hello")
test.support.temp_dir(path=None, quiet=False)

path 处创建临时目录并生成该目录的上下文管理器。

如果 pathNone,则使用 tempfile.mkdtemp() 创建临时目录。 如果 quietFalse,则上下文管理器会在出错时引发异常。 否则,如果指定了 path 并且无法创建,则只会发出警告。

test.support.change_cwd(path, quiet=False)

将当前工作目录临时更改为 path 并生成目录的上下文管理器。

如果 quietFalse,则上下文管理器会在出错时引发异常。 否则,它只会发出警告并保持当前工作目录不变。

test.support.temp_cwd(name='tempcwd', quiet=False)

临时创建新目录并更改当前工作目录 (CWD) 的上下文管理器。

在临时更改当前工作目录之前,上下文管理器在当前目录中创建一个名为 name 的临时目录。 如果 nameNone,则使用 tempfile.mkdtemp() 创建临时目录。

如果 quietFalse 并且无法创建或更改 CWD,则会引发错误。 否则,只会发出警告并使用原始 CWD。

test.support.temp_umask(umask)
临时设置进程 umask 的上下文管理器。
test.support.can_symlink()
如果操作系统支持符号链接,则返回 True,否则返回 False
@test.support.skip_unless_symlink
用于运行需要支持符号链接的测试的装饰器。
@test.support.anticipate_failure(condition)
使用 unittest.expectedFailure() 有条件地标记测试的装饰器。 对该装饰器的任何使用都应该有一个相关的注释来标识相关的跟踪器问题。
@test.support.run_with_locale(catstr, *locales)
一个装饰器,用于在不同的语言环境中运行一个函数,在它完成后正确地重置它。 catstr 是字符串形式的语言环境类别(例如 "LC_ALL")。 传递的 locales 将依次尝试,并使用第一个有效的语言环境。
test.support.make_bad_fd()
通过打开和关闭临时文件并返回其描述符来创建无效的文件描述符。
test.support.import_module(name, deprecated=False)

此函数导入并返回命名模块。 与普通导入不同,如果模块无法导入,此函数会引发 unittest.SkipTest

如果 deprecatedTrue,则在此导入过程中会抑制模块和包弃用消息。

3.1 版中的新功能。

test.support.import_fresh_module(name, fresh=(), blocked=(), deprecated=False)

此函数通过在执行导入之前从 sys.modules 中删除命名模块来导入并返回命名 Python 模块的新副本。 注意与reload()不同的是,原模块不受此操作影响。

fresh 是附加模块名称的迭代,在执行导入之前也从 sys.modules 缓存中删除。

blocked 是一个可迭代的模块名称,在导入期间在模块缓存中替换为 None,以确保尝试导入它们会引发 ImportError

在开始导入之前保存命名模块以及在 freshblocked 参数中命名的任何模块,然后在新导入完成后重新插入到 sys.modules 中。

如果 deprecatedTrue,则在此导入过程中会抑制模块和包弃用消息。

如果无法导入命名模块,此函数将引发 ImportError

使用示例:

# 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'])

3.1 版中的新功能。

test.support.bind_port(sock, host=HOST)

将套接字绑定到一个空闲端口并返回端口号。 依赖临时端口以确保我们使用的是未绑定端口。 这很重要,因为许多测试可能同时运行,尤其是在 buildbot 环境中。 如果 sock.familyAF_INETsock.typeSOCK_STREAM,并且套接字具有 SO_REUSEADDRSO_REUSEPORT 设置就可以了。 测试不应为 TCP/IP 套接字设置这些套接字选项。 设置这些选项的唯一情况是通过多个 UDP 套接字测试多播。

此外,如果 SO_EXCLUSIVEADDRUSE 插座选项可用(即 在 Windows 上),它将在套接字上设置。 这将防止其他任何人在测试期间绑定到我们的主机/端口。

test.support.find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM)

返回应适合绑定的未使用端口。 这是通过创建与 sock 参数(默认为 AF_INETSOCK_STREAM)具有相同系列和类型的临时套接字,并将其绑定到指定主机来实现的地址(默认为 0.0.0.0),端口设置为 0,从操作系统中引出一个未使用的临时端口。 然后关闭并删除临时套接字,并返回临时端口。

此方法或 bind_port() 应该用于在测试期间需要将服务器套接字绑定到特定端口的任何测试。 使用哪个取决于调用代码是否正在创建python套接字,或者是否需要在构造函数中提供未使用的端口或传递给外部程序(即 openssl 的 s_server 模式的 -accept 参数)。 在可能的情况下,始终更喜欢 bind_port() 而不是 find_unused_port()。 不鼓励使用硬编码端口,因为它会使多个测试实例无法同时运行,这对构建机器人来说是一个问题。

test.support.load_package_tests(pkg_dir, loader, standard_tests, pattern)

unittest load_tests 协议的通用实现,用于测试包。 pkg_dir为包的根目录; loaderstandard_testspatternload_tests 期望的参数。 在简单的情况下,测试包的 __init__.py 可以如下:

import os
from test.support import load_package_tests

def load_tests(*args):
    return load_package_tests(os.path.dirname(__file__), *args)
test.support.detect_api_mismatch(ref_api, other_api, *, ignore=())

返回在 other_api 上找不到的 ref_api 的属性、函数或方法集,除了 ignore 中指定的要在此检查中忽略的已定义项目列表.

默认情况下,这会跳过以“_”开头的私有属性,但包括所有魔术方法,即 以'__'开头和结尾的那些。

3.5 版中的新功能。

test.support.check__all__(test_case, module, name_of_module=None, extra=(), blacklist=())

断言 模块__all__ 变量包含所有公共名称。

模块的公共名称(其 API)根据它们是否与公共名称约定匹配并在 module 中定义自动检测。

name_of_module 参数可以指定(作为字符串或元组)API 可以定义在哪个模块中以便被检测为公共 API。 一种情况是,当 module 从其他模块(可能是 C 后端(如 csv 及其 _csv)导入部分公共 API 时。

extra 参数可以是一组不会被自动检测为“公共”的名称,例如没有正确 __module__ 属性的对象。 如果提供,它将被添加到自动检测到的。

blacklist 参数可以是一组名称,即使它们的名称另有说明,也不能将其视为公共 API 的一部分。

使用示例:

import bar
import foo
import unittest
from test import support

class MiscTestCase(unittest.TestCase):
    def test__all__(self):
        support.check__all__(self, foo)

class OtherTestCase(unittest.TestCase):
    def test__all__(self):
        extra = {'BAR_CONST', 'FOO_CONST'}
        blacklist = {'baz'}  # Undocumented name.
        # bar imports part of its API from _bar.
        support.check__all__(self, bar, ('bar', '_bar'),
                             extra=extra, blacklist=blacklist)

3.6 版中的新功能。

test.support 模块定义了以下类:

class test.support.TransientResource(exc, **kwargs)
实例是一个上下文管理器,如果指定的异常类型被引发,它会引发 ResourceDenied。 任何关键字参数都被视为属性/值对,以与 with 语句中引发的任何异常进行比较。 仅当所有对与异常上的属性正确匹配时,才会引发 ResourceDenied
class test.support.EnvironmentVarGuard

用于临时设置或取消设置环境变量的类。 实例可以用作上下文管理器,并具有完整的字典接口,用于查询/修改底层 os.environ。 从上下文管理器退出后,通过此实例对环境变量所做的所有更改都将回滚。

3.1 版本变化: 增加字典接口。

EnvironmentVarGuard.set(envvar, value)
临时设置环境变量envvarvalue的值。
EnvironmentVarGuard.unset(envvar)
暂时取消设置环境变量 envvar
class test.support.SuppressCrashReport

一个上下文管理器,用于防止在预期会导致子进程崩溃的测试中弹出崩溃对话框。

在 Windows 上,它使用 SetErrorMode 禁用 Windows 错误报告对话框。

在 UNIX 上,resource.setrlimit() 用于将 resource.RLIMIT_CORE 的软限制设置为 0,以防止创建核心转储文件。

在两个平台上,旧值由 __exit__() 恢复。

class test.support.WarningsRecorder
用于记录单元测试警告的类。 有关更多详细信息,请参阅上面 check_warnings() 的文档。
class test.support.FakePath(path)
简单的 类路径对象 。 它实现了 __fspath__() 方法,该方法只返回 path 参数。 如果 path 是一个异常,它将在 __fspath__() 中引发。