
如果你已经读完我不测试部分现在确信做测试驱动开发那么祝贺你已经学习了测试驱动开发的基础知识但在你能够有效地使用这种方法之前还有一些你应该学习的东西。本节介绍开发人员在编写测试时遇到的一些问题以及解决这些问题的方法。同时还对Python 社区中可用的流行测试运行器和工具做了快速回顾。unittest 陷阱unittest 模块在 Python 2.1 中引入自那以后开发人员大量使用它。但是在社区中一些对单元测试的弱点和限制感到失望的人创建了一些替代的测试框架。这是一些常见批评。● 框架有些臃肿难以使用原因如下。○ 你必须在 TestCase 的子类中编写所有的测试。○ 你必须在方法名前加上 test。○ 我们鼓励使用 TestCase 中提供的断言方法而不是单纯的断言语句而现有的方法可能不能覆盖所有的用例。● 框架很难扩展因为它需要大量的子类化其基类或者技巧如装饰器。● 测试固件有时很难组织因为 setUp 和 tearDown 被绑定到 TestCase 级别虽然它们在每次测试只运行一次。换句话说如果测试固件涉及许多测试模块则组织其创建和清理并不容易。● 在 Python 软件上运行测试活动并不容易。默认测试运行器python -m unittest确实提供了一些测试发现功能但不提供足够的过滤功能。在实践中必须编写额外的脚本来收集测试聚合它们然后以适当的方式运行它们。需要一种更轻量级的方法来编写测试而不需要一个看起来太像其 Java 兄弟JUnit的刻板的框架。由于 Python 不需要使用 100%的基于类的环境因此最好提供一个不基于子类化的更 Python 化的测试框架。常见的方法如下。● 提供一种简单的方法来标记任何作为测试的函数或类。● 通过插件系统扩展框架。● 为所有测试级别提供完整的测试固件环境。整个活动在测试级别上以模块进行分组。● 为基于测试发现的测试运行器提供具有丰富的选项。unittest 的替代品一些第三方工具尝试通过以单元测试扩展的形式提供额外的特性来解决刚刚提到的问题。Python 维基百科页面上提供了很多测试实用程序和框架列表参考 https://wiki.python.org/moin/ PythonTestingToolsTaxonomy但只有两个项目特别受欢迎。● nose请参阅 http://nose.readthedocs.org。● py.test请参阅 http://pytest.org。nosenose 主要是一个具有强大的发现功能的测试运行器。它有丰富的选项允许在 Python应用程序中运行所有类型的测试活动。它不是标准库的一部分但在 PyPI 上可用可以很容易地用 pip 安装pip install nose测试运行器安装 nose 后在命令提示符下使用一个名为 nosetests 的新命令。可以直接使用以下命令运行本章第一部分介绍的测试nosetests -vtest_true (test_primes.OtherTests) … oktest_is_prime (test_primes.PrimesTests) … okbuilds the test suite. … okRan 3 tests in 0.009sOKnose 通过递归地浏览当前目录进行测试发现并且自己构建一个测试套件。相比简单的 python -m unittest 命令上面的例子初看似乎没有任何改进。如果使用–help开关运行此命令差异将显而易见。你会注意到nose 提供了几十个参数允许你控制测试发现和执行。编写测试nose 进一步运行所有类和函数它们的名称匹配正则表达式?:^|[b_.-][Tt]est并且它们所在的模块也要匹配。大致上所有以 test 开始并且位于与模式匹配的模块中的可调用项也将被作为测试执行。例如下面这个 test_ok.py 模块将被识别并由 nose 运行$ more test_ok.pydef test_ok():print(‘my test’)$ nosetests -vtest_ok.test_ok … okRan 1 test in 0.071sOK常规的 TestCase 类和 doctests 也会被执行。最后nose 提供了类似于 TestCase 方法的断言函数assertion functions。这些函数遵循 PEP 8 命名约定而不是使用 Java 单元测试中的约定参考 http://nose.readthedocs.org/。编写测试固件nose 支持 3 个级别的测试固件。● 包级别可以把 setup 和 teardown 函数添加到__init__.py 模块中这是一个包含所有测试模块的测试包。● 模块级测试模块可以有自己的 setup 和 teardown 函数。● 测试级别通过使用 with_setup 装饰器使固件函数可调用。例如要在模块和测试级别设置测试固件请使用以下代码def setup():启动函数的代码为整个模块加载…def teardown():卸载函数的代码为整个模块加载…def set_ok():启动函数的代码只为 test_ok 函数加载…with_setup(set_ok)def test_ok():print(‘my test’)与 setuptools 和插件系统集成最后nose 可以与 setuptools 平滑地集成所以可以使用 test 命令进行测试python setup.py test。此集成是通过向 setup.py 脚本中添加 test_suite 元数据来完成的如下所示setup(#…test_suite‘nose.collector’,)nose 也使用 setuptool 的入口点机制为开发人员写 nose 插件。你可以覆盖或修改工具的各个方面从测试发现到格式化输出。nose 小结nose 是一个完整的测试工具修复了很多 unittest 的问题。它也对测试使用隐式前缀名称这对于一些开发人员来说仍然是一个约束。虽然这个前缀可以自定义但你仍然必须遵循这个约定。这个约定优于配置的语句并不糟糕比 unittest 中需要的样板代码好多了。但是使用显式装饰器例如这是一个摆脱测试前缀的很好的方法。此外使用插件扩展 nose 的功能使它变的非常灵活开发人员可以定制工具以满足他/她的需求。如果你的测试工作流需要覆写很多 nose 参数你可以在主目录或项目根目录中添加.noserc 或 nose.cfg 文件来完成。它将为 nosetests 命令指定默认的选项集。例如一个好的做法是在测试运行期间自动查找 doctests。启用运行 doctests 的 nose配置文件的示例如下[nosetests]with-doctest1doctest-extension.txtpy.testpy.test 非常类似于 nose。事实上后者是受到 py.test 的启发所以我们将主要集中在些工具彼此不同的细节上。该工具诞生为一个更大的名为 py 包的一部分但现在这些是单独开发的。像本书中提到的每个第三方包一样py.test 在 PyPI 上也是可用的所以可以使用pip 安装 pytest代码如下$ pip install pytest从那里一个新的 py.test 命令可以在命令提示符中使用可以完全像 nosetests那样使用它。该工具使用类似的模式匹配和测试发现算法捕获要运行的测试。这种模式比nose 使用的模式更严格并且只会捕获● 以 test 开头的文件中的以 test 开头的类● 以 test 开头的文件中的以 test 开始的函数。py.test 的优点如下。● 能够轻松禁用某些测试类。● 一个灵活和新颖的处理测试固件的机制。● 在多台计算机之间分发测试的能力。编写测试固件py.test 支持两种机制来处理固件。第一个在 xUnit 框架之后建模类似于nose。当然语义有所不同。py.test 将在每个测试模块中查找 3 个级别的固件如下例所示def setup_module(module):“” 设置任何给定模块的执行的特定的状态。“”def teardown_module(module):“” 卸载之前使用 setup_module 方法设置的任何状态。“”def setup_class(cls):“” 设置任何给定类通常包含测试的执行的特定的状态。“”def teardown_class(cls):“” 卸载之前使用 setup_class 方法设置的任何状态。“”def setup_method(self, method):“” 设置任何绑定到类中给定方法的执行的状态。对类的每个测试方法调用 setup_method。“”def teardown_method(self, method):“” 卸载之前使用 setup_method 方法设置的任何状态“”每个函数将获取当前模块类或方法作为参数。因此测试固件将能够在上下文中工作而不必像 nose 那样寻找它。使用 py.test 编写测试固件的另一种机制是建立在依赖注入的概念上的允许以更加模块化和可扩展的方式维护测试状态。非 xUnit 风格的固件setup / teardown 过程总是具有唯一的名称并且需要通过在类中的测试函数方法和模块中的声明它们的使用来显式激活。最简单的固件实现形式是采用 pytest.fixture()装饰器声明的命名函数。要标记测试中使用的固件它需要声明为一个函数或方法参数。为了使它更清楚把前面的例子使用 py.test 固件重写 is_prime 函数的测试模块如下所示import pytestfrom primes import is_primepytest.fixture()def prime_numbers():return [3, 5, 7]pytest.fixture()def non_prime_numbers():return [8, 0, 1]pytest.fixture()def negative_numbers():return [-1, -3, -6]def test_is_prime_true(prime_numbers):for number in prime_numbers:assert is_prime(number)def test_is_prime_false(non_prime_numbers, negative_numbers):for number in non_prime_numbers:assert not is_prime(number)for number in non_prime_numbers:assert not is_prime(number)禁用测试函数和类py.test 提供了一个简单的机制可以在某些条件下禁用一些测试。这被称为跳过skipping并且 pytest 包提供了.skipif 装饰器支持这种机制。如果在某些条件下需要跳过单个测试函数或整个测试类装饰器则需要使用此装饰器并且提供的一些用于验证是否满足预期条件的值来定义。下面是一个官方文档的实例在 Windows 上跳过运行整个测试用例类如下所示import pytestpytest.mark.skipif(sys.platform ‘win32’,reason“does not run on windows”)class TestPosixCalls:def test_function(self):“”“在 win32 平台上不会启动运行”“”当然你可以预定义跳过条件以便在测试模块中共享它们如下所示import pytestskipwindows pytest.mark.skipif(sys.platform ‘win32’,reason“does not run on windows”)skip_windowsclass TestPosixCalls:def test_function(self):“”“在 win32 平台上不会启动运行”“”如果以这种方式标记测试它将不会被执行。但是在某些情况下你想要运行这样的测试并想要执行它但是你知道在已知条件下测试预期是失败的。为此提供了一个不同的装饰器。它是mark.xfail并确保测试始终运行但如果发生预定义的条件它将在某个点失败如下所示import pytestpytest.mark.xfail(sys.platform ‘win32’,reason“does not run on windows”)class TestPosixCalls:def test_function(self):“”“在 windows 下一定运行失败”“”使用 xfail 比 skipif 更严格。测试总是执行如果它不是预期的失败那么整个py.test 运行将导致失败。自动化的分布式测试py.test 的一个有趣的特性是它能够跨多台计算机分发测试。只要计算机可以通过SSH 访问py.test 就能够驱动每台计算机发送测试并执行。但是此功能依赖于网络如果连接断开服务器将无法继续工作因为它完全由主服务器驱动的。当项目有长时间的测试活动时Buildbot 或其他持续集成工具则是更好的选择。但是当你在需要消耗很多资源来运行测试的应用程序上工作时py.test 分布式模型可以用于随机分布测试。py.test 小结py.test 非常类似于 nose它也不需要样板代码来聚合它的测试。它也有一个很好的插件系统在 PyPI 上有大量可用的扩展。最后py.test 重点在于使测试运行得更快并且与该领域中的其他工具相比它真的更出色。另一个值得注意的特性是新颖的固件方法真正有助于管理可重用的固件库。有些人可能认为它有太多的魔法但它真的简化了测试套件的开发。py.test 的这一个优势使其成为我的首选工具所以我真的非常推荐使用它。