从消费者的角度看,Sphinx 真的提高了文档的可读性和用户体验。如前所述,当一部
分以 dosctrings 或模块助手的形式的文档与代码紧密耦合时,文档就显得特别有用。虽然这
种方法确保文档的源版本与其文档中的代码相匹配,但并不能保证文档读者能够访问最新
的编译版本。
如果文档的目标读者不够熟练地使用命令行工具,并且不知道如何将其构建为可浏览
和可读的形式,则仅具有最小的源表示也是不够的。当提交/推送任何更改到代码仓库时,
自动将你的文档构建为友好的用户界面,就显得非常重要。
托管使用 Sphinx 构建的文档的最佳方式是生成 HTML 构建,并将其作为你选择的 Web
服务器的静态资源。Sphinx 提供了合适的 Makefile 使用 make html 命令来构建 HTML
文件。因为 make 是一个非常常见的实用程序,应该很容易将这个过程与第 8 章中讨论的
任何持续集成系统集成。
如果你用 Sphinx 文档化一个开源项目,那么使用 Read the Docs(https://readthedocs.org/)
会让你的开发更加轻松。它是一个免费的服务,用于托管使用 Sphinx 的开源 Python 项目的文档。配置可以轻而易举的完成,它很容易与两个流行的代码托管服务:GitHub 和
Bitbucket 集成。在实践中,如果你的账户已正确连接并正确设置了代码仓库,则只需单击
几下即可启用 Read the Docs 上的文档托管功能。
小结
本章详细解释了以下几个问题。
● 如何使用几个规则来有效地写作。
● 如何使用 reStructuredText,Python 程序员的 LaTeX。
● 如何构建文档集和文档格局。
● 如何使用 Sphinx 生成有用的 Web 文档。
文档化项目最难的事情是保持准确和最新。让文档成为代码仓库的一部分使得它变得
容易得多。从那里,每当开发人员更改模块时,他或她也应该更改相应的文档。
这在大项目中可能相当困难,而在模块的头部中添加相关文档的列表可以在这种情况
下会有所帮助。
确保文档始终准确的补充方法是通过 doctests 将文档与测试相结合。这将在下一章中
介绍,其中介绍了测试驱动开发的原则,以及文档驱动开发。
测试驱动开发
测试驱动开发(Test-Driven Development,TDD)是一种生产高质量软件的简单技术。
它在 Python 社区中被广泛使用,并且在其他社区也非常受欢迎。
由于 Python 语言的动态性质,测试在 Python 中尤其重要。它缺少静态类型,所以不
到每行代码执行的那一刻,很多错误都无法发现。但问题不仅是 Python 中的类型如何工作。
记住,大多数的 bug 与错误的语法使用无关,而是与逻辑错误和细微的误解有关,它们可
能导致重大的失败。
本章分为两部分:
● 我不测试,主张 TDD,并快速地描述如何使用标准库进行。
● 我做测试,这是为那些做测试且希望充分利用测试的开发人员准备的。
我不测试
如果你已经对 TDD 深信不疑,你应该转到下一部分。它将专注于高级技术和工具,使
用这些工具和技术可以更轻松地进行测试。这部分主要是为那些没有使用这种方法的开发
人员准备的,并尝试倡导他们使用。
测试开发的原则
最简单形式的测试驱动开发的过程,包括 3 个步骤。
● 为未实现的新功能或者改进编写自动化测试。
● 提供通过所有定义的测试的最小代码量。
● 重构代码以满足所需的质量标准。
记住这个开发周期的最重要事情是,测试应该在实现之前编写。对于没有经验的开
发人员来说,这不是一件容易的任务,但它是唯一的方法,它保证你要编写的代码是可
测试的。
例如,要求开发人员编写一个检查给定数字是否为质数的函数,写一些关于如何使用
它的示例以及预期结果如下:
assert is_prime(5)
assert is_prime(7)
assert not is_prime(8)
实现该功能的开发人员不需要是负责提供测试的唯一人员。这些示例也可以由另一个
人提供。例如,网络协议或密码算法的官方规范经常提供旨在验证实现的正确性的测试向
量。这些是测试用例的完美基础。
该功能可以实现,直到通过前面的测试用例,如下所示:
def is_prime(number):
for element in range(2, number):
if number % element == 0:
return False
return True
一个新的用例会导致一个 bug 或意外的结果,需要对程序进一步修改,如下所示:
assert not is_prime(1)
Traceback (most recent call last):
File “”, line 1, in
AssertionError
对代码进行相应地修改,直到新测试通过,如下所示:
def is_prime(number):
if number in (0, 1):
return False
for element in range(2, number):
if number % element == 0:
return False
return True
更多的测试用例表明,当前的实现仍然不完备,如下所示:
assert not is_prime(-3)
Traceback (most recent call last):
File “”, line 1, in
AssertionError
更新过的代码如下:
def is_prime(number):
if number < 0 or number in (0, 1):
return False
for element in range(2, number):
if number % element == 0:
return False
return True
从那里,所有测试可以收集到一个测试函数中,每次代码演变时运行,如下所示:
def test_is_prime():
assert is_prime(5)
assert is_prime(7)
assert not is_prime(8)
assert not is_prime(0)
assert not is_prime(1)
assert not is_prime(-1)
assert not is_prime(-3)
assert not is_prime(-6)
每次我们提出一个新的需求,test_is_prime()函数应该首先更新以定义 is_prime()
函数的预期行为。然后,运行测试以检查实现是否得到预期的结果。只有在测试已知失败
的情况下,才需要更新已测试函数的代码。
测试驱动开发提供了很多好处。
● 它有助于防止软件回归。
● 提高软件质量。
● 它提供了一种底层的代码行为文档。
● 它允许你在较短的开发周期中更快地写出健壮的代码。
处理测试的最佳约定是将它们放在一个模块或包(通常命名为 tests)中,并使用一
个简单的 shell 命令运行整个套件。幸运的是,没有必要自己构建整个测试工具链。Python
标准库和 Python 包索引都有大量的测试框架和实用程序,你可以很方便地构建,发现和运
行测试。我们将在本章后面讨论这些包和模块中的最最值得一提的例子。
防止软件回归
我们都会在我们的开发者生活中面临软件回归的问题。软件回归是由变化引入的一个
新的 bug。这体现为一些功能和特性在以前版本的软件中正常工作,而在项目开发中的某
个时间点上,它们被破坏了并且无法正常工作。
回归的主要原因是软件的高度复杂性。在某些时候,无法预测代码库中的单个更改可
能导致什么问题。更改一些代码可能会破坏一些其他的功能,有时会导致恶性副作用,例
如悄悄损坏数据。高复杂性不仅是巨大代码库的问题。当然,代码量和其复杂性之间存在
明显的相关性,但是即使是小项目(几百/几千行代码)也可能具有这样复杂的架构,难以
预测相对小的变化的所有后果。
为了避免回归,应该在每次发生变化时,对软件提供的整套功能进行测试。没有这个,
你无法可靠地区别软件中一直存在的 bug 之间的区别,有的是新引入的,有的部分在前一
段时间还正常工作。
给几个开发人员开放代码库权限将会放大问题,因为不是每个人都会完全清楚所有的
开发活动。虽然版本控制系统可以防止冲突,但它并不能防止所有不必要的交互。
TDD 有助于减少软件回归。整个软件可以在每次更改后自动测试。只要每个功能都有
适当的测试集,它就会工作。当 TDD 正确完成时,测试库与代码库一起增长。
由于一个完整的测试活动可能持续相当长的时间,一个好的做法是将其委托给一些可
以在后台工作的持续集成系统。我们在第 8 章中已经讨论过这样的解决方案。然而,测试
的本地重启应该由开发人员手动执行,至少对于相关的模块。仅依赖于持续集成将对开发
人员的生产力产生负面影响。程序员应该能够在其环境中轻松地运行选择的测试。这就是
为什么你应该仔细为项目选择测试工具。
提高代码质量
当写一个新的模块,类或函数时,开发人员专注于如何编写它以及如何写出他或她的
最好的代码。但是当他或她专注于算法时,他或她可能会失去用户的关注点:他或她的功
能如何使用和何时使用?参数是否简单并且逻辑可用?API 的名称是否正确?
这些可以通过使用前面章节中描述的提示来完成,例如第 4 章。但是唯一有效的做法
是编写使用示例。此时此刻,开发人员就会意识到他或她写的代码是否合理且易于使用。
通常,第一次重构发生在模块、类或函数完成之后。
编写代码的测试用例,有助于从用户的角度思考问题。因此,开发人员在使用 TDD 时
通常会写出更好的代码。庞大的功能和整体类通常难以测试。在测试中写的代码往往被设
计得更干净和模块化。
提供最好的开发文档
测试是开发人员了解软件工作原理的最佳场所。它们是使用的实例,代码就是主要
为它而建。阅读它们可以快速深入地了解代码的工作原理。有时一个测试用例胜过千言
万语。
事实上,这些测试应该始终与代码库一样保持更新,使它们成为一个软件可以拥有的
最好的开发人员文档。测试不会像文档那样失效,它们只会失败。
更快地编写健壮的代码
无测试的写代码会导致长时间的调试会话。一个模块中的 bug 的结果可能表现在软件
中的一个完全不同的部分。因为你不知道去责怪谁,你花费过多的时间调试。当测试失败
时,最好一次解决一个小错误,因为你会有一个更好的线索,这是真正的问题的所在地方。
测试通常比调试更有趣,因为它是编码。
如果你测量修复代码所花费的时间以及编写它所花费的时间,这通常会比 TDD 方法花
费的时间更长。当你开始一段新的代码时,不太明显。这是因为与写第一段代码所用的时
间相比,设置测试环境和编写前几个测试所花费的时间非常长。
但有一些测试环境真的很难设置。例如,当你的代码与 LDAP 或 SQL 服务器交互时,
编写测试根本不明显,这在本章有介绍。