透析Python 项目测试全过程
对于一般新入手的开发人员,如果只想在Python 项目测试或添加新特性之前检查包是否能够在他自己的平台上进行工作,那么 test_suite 入口点是非常方便的,这也避免程序员多走的弯路。
本文讨论先进的测试框架如何提供健壮的应用程序测试自动发现,以及这如何替代过去维护的集中式测试列表。Python 编程社区非常重视单元测试和功能性测试。这种风气不但有助于确保组件和应用程序最初的质量,还促使程序员不断调整和改进代码。
本文是讨论现代 Python 测试框架的 三篇系列文章 的第二篇。本系列中的 第一篇文章 介绍了 zope.testing、py.test 和 nose,介绍它们如何影响 Python 项目编写和维护测试的方式。
本文介绍如何调用这三种框架、它们如何在项目中发现测试以及如何选择并运行测试。最后一篇文章将讨论如何通过各种报告特性让测试支持更强大的技术。Python 测试的黑暗时代Python 项目测试曾经是非常特殊化、个人化的活动。开发人员可能先在单独的 Python 脚本中编写每组测试。
然后,编写一个名为 test_all.py 或 tests.py 的脚本,这个脚本导入并运行他的所有测试。但是,无论这个过程的自动化做得多么好,这种方式仍然是特殊化的:参与项目的每个开发人员都必须知道测试脚本放在哪里以及如何调用它们。
如果某个 Python 开发人员从事十几个项目,他就必须记住十几个测试命令。test_all.py(或项目采用的其他名称)还可能手工导入所有其他测试,这可能导致风险。如果这个集中的测试列表过时了(常常是由于开发人员添加了新的测试套件。
手工运行它,但是忘了把它添加到中心脚本中),那么在 Python 包投入生产之前的最后一次测试就会遗漏许多测试。这种无政府状态的另一个缺点是,它要求每个测试文件包含样板代码,从而能够作为单独的命令运行。如果查看 Python 项目测试或当今的一些 Python 项目,会看到许多这样的测试示例:
# test_old.py - The old way of doing things import unittest class TruthTest(unittest.TestCase): def testTrue(self): assert True == 1 def testFalse(self): assert False == 0 if __name__ == '__main__': unittest.main()
本系列的 第一篇文章 已经讨论过基于 TestCase 类的测试在现代环境中为什么常常是不必要的。但是,现在注意最后两行:它们起什么作用?答案是,它们检测什么时候从命令行单独运行这个 test_old.py 脚本。
在这种情况下,它们运行一个 unittest 简便函数,这个函数在模块中搜索测试并运行它们。它们使这个测试文件可以独立于项目范围的测试脚本单独运行。显然,在数十甚至数百个测试模块中复制相同的代码非常麻烦。
另一个不太明显的缺点是这种做法不利于标准化。如果 test_main() 函数不够完善,无法检测出某个模块的测试,那么这个模块的行为可能与其他测试套件不匹配。因此,每个模块在测试类的名称、操作方式和运行方式方面稍有差异。
由于主流 Python 测试框架的出现,上述的所有问题已经解决了,而且每种框架解决这些问题的方式大致相同。首先,这三种测试框架都提供了从操作系统命令行运行测试的标准方法。这样,每个 Python 项目测试就不再需要在代码基中维护全局测试脚本。
zope.testing 包运行测试的机制是最特殊化的:因为 Zope 开发人员常常使用 buildout 设置他们的项目,常常通过 buildout.cfg 文件中的 zc.recipe.testrunner recipe 安装测试脚本。但是,结果在不同的项目上相当一致:在我遇到的每个 Zope 项目中,开发 buildout 都会创建一个 ./bin/test 脚本,可以通过它调用项目的测试。
py.test 和 nose 项目的做法更意思。它们都提供一个命令行工具,所以每个项目完全不需要有自己的测试命令: