技术开发 频道

Python 测试框架: 选择和运行测试

  setup 和 teardown

  在设计和编写测试套件时,一个大问题是如何处理共同的 setup 和 teardown 代码。许多真实的测试并不像本文给出的示例函数这么简单;它们必须执行一些复杂的操作,比如在 Firefox 中打开网页并单击 “Continue” 按钮,然后检查结果。在开始实际测试(比如打开页面并单击按钮)之前,测试必须先完成一些步骤。

  现在,考虑如果一百个功能性测试都要这样执行测试,会怎么样。它们都需要通过调用共同的 setup 例程运行 Firefox,然后才能执行自己的测试。与此相应,为了取消 setup 所做的操作,可能还有 teardown 代码。这样,在测试套件中就会增加两百个额外的函数调用。每个测试函数会像下面这样:

 

# How test functions look if they each do setup and teardown

def test_index_click_continue():
    do_big_setup()          #
<- the same in every test
    t
= TestBrowser(browser)
    t.load_page(
'index.html')
    t.click('#continue')
    t.check_status(200)
    do_big_teardown()       #
<- the same in every test

  为了消除这些重复的代码,许多测试框架提供了一次性指定每个测试都需要运行的 setup 和 teardown 代码的机制。

  本文讨论的三种框架 zope.testing、py.test 和 nose 都支持程序员编写的 unittest.TestCase 类中的标准 setUp() 和 tearDown() 例程。但是,除此之外,各个框架为共同 setup 代码提供的特性有显著差异。

  zope.testing 本身没有为 setup 和 teardown 提供额外支持,但是前面讨论过的 z3c.testsetup 扩展会对 doctest 做一些有意思的处理。它通过在文件中寻找 :Test-Layer: 字符串来寻找测试。doctest 中的层实际上可以指定两个值之一。如果把 doctest 标为属于 unit 层,就意味着运行它不需要任何特殊的 setup。但是,如果把它标为属于 functional 层,就意味着只能在调用框架 setup 函数之后运行它。

  通常情况下,:Test-Layer: functional 测试被设计为在完整地配置了 Zope Web 框架的情况下运行,因此它们可以创建测试浏览器实例、发送请求和查看 Web 框架返回的响应。通过代表 doctest 执行 setup,z3c.testsetup 可以避免在每个功能性 doctest 中复制大量样板代码。

  最后一项减少样板代码的便捷特性是,可以向 z3c.testsetup 提供一个预装载到每个单元 doctest 的名称空间中的变量列表,以及另一个预装载到每个功能性 doctest 中的变量列表。这样就不需要在每个 doctest 文件的开头复制一组相同的 import 语句。

  py.test 在默认情况下不提供对 setup 和 teardown 的支持。它甚至不运行标准 unittest.TestCase 类的 setUp() 和 tearDown() 方法,除非打开它的 unittest 插件。

  nose 在支持共同测试代码方面是最出色的。在寻找测试时,nose 跟踪记录找到测试的上下文。它认为 unittest.TestCase 子类内部的每个测试方法是这个类 “内部的”,因此由它的 setUp() 和 tearDown() 方法控制,它还认为测试存在于它们的模块、包含模块的包以及外层所有包的 “内部”。因此,对于多层 “同心” 容器内的测试,nose 会在运行测试之前运行所有容器中的 setup 代码,在运行测试之后运行所有容器中的 teardown 代码。

  关于包范围和模块范围的 setup 和 teardown 函数的更多信息,请参考 nose 文档;除了其他信息之外,您会发现在调用 setup 和 teardown 函数方面有许多选择。(同样,nose不太鼓励不同的项目以相同方式编写测试,因此不利于阅读其他项目的代码)。但是,它们提供非常强大的分组方式,不但可以按照结构,还可以按语义将函数分组到包和模块中(把在相同环境中运行的测试放在一起)。

  在一种情况下,nose 不考虑 setup 和 teardown 函数的名称:使用 @with_setup 修饰符为某一函数显式地指定它们。同样,详细信息请参考 nose 文档。在这里,我只想提醒您注意一点:因为在 Python 中函数是一级对象,所以可以给某个修饰符分配名称并反复使用它:

 

# Naming a with_setup decorator

firefox_test
= with_setup(firefox_setup, firefox_teardown)

@firefox_test
def test_index_click():
    ...

@firefox_test
def test_index_menu():
...

  最后,在 @with_setup 修饰符中指定的或作为 unittest.TestCase 子类中的方法提供的 setup 和 teardown 函数对于相关的每个函数或测试运行一次,而在模块级或包级向 nose 提供的 setup 和 teardown 代码对于整个测试集只运行一次。因此,不要认为这样的测试是完全互相隔离的:它们会共享在模块或包的 setup 例程中创建的资源拷贝。

  结束语

  恭喜!现在您了解了不同的测试框架在检测测试和安排运行测试方面提供的支持。本系列的最后一篇文章将讨论使用测试框架的好处:强大的测试选择选项、报告工具和调试支持帮助我们更好地利用测试结果。最后,我们将讨论如何在这三种框架中选择最适合自己的框架。

0
相关文章