Python 高手编程系列三千三百七十九:文档构建与持续集成 从消费者的角度看Sphinx 真的提高了文档的可读性和用户体验。如前所述当一部分以 dosctrings 或模块助手的形式的文档与代码紧密耦合时文档就显得特别有用。虽然这种方法确保文档的源版本与其文档中的代码相匹配但并不能保证文档读者能够访问最新的编译版本。如果文档的目标读者不够熟练地使用命令行工具并且不知道如何将其构建为可浏览和可读的形式则仅具有最小的源表示也是不够的。当提交/推送任何更改到代码仓库时自动将你的文档构建为友好的用户界面就显得非常重要。托管使用 Sphinx 构建的文档的最佳方式是生成 HTML 构建并将其作为你选择的 Web服务器的静态资源。Sphinx 提供了合适的 Makefile 使用 make html 命令来构建 HTML文件。因为 make 是一个非常常见的实用程序应该很容易将这个过程与第 8 章中讨论的任何持续集成系统集成。如果你用 Sphinx 文档化一个开源项目那么使用 Read the Docshttps://readthedocs.org/会让你的开发更加轻松。它是一个免费的服务用于托管使用 Sphinx 的开源 Python 项目的文档。配置可以轻而易举的完成它很容易与两个流行的代码托管服务GitHub 和Bitbucket 集成。在实践中如果你的账户已正确连接并正确设置了代码仓库则只需单击几下即可启用 Read the Docs 上的文档托管功能。小结本章详细解释了以下几个问题。● 如何使用几个规则来有效地写作。● 如何使用 reStructuredTextPython 程序员的 LaTeX。● 如何构建文档集和文档格局。● 如何使用 Sphinx 生成有用的 Web 文档。文档化项目最难的事情是保持准确和最新。让文档成为代码仓库的一部分使得它变得容易得多。从那里每当开发人员更改模块时他或她也应该更改相应的文档。这在大项目中可能相当困难而在模块的头部中添加相关文档的列表可以在这种情况下会有所帮助。确保文档始终准确的补充方法是通过 doctests 将文档与测试相结合。这将在下一章中介绍其中介绍了测试驱动开发的原则以及文档驱动开发。测试驱动开发测试驱动开发Test-Driven DevelopmentTDD是一种生产高质量软件的简单技术。它在 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 Falsereturn True一个新的用例会导致一个 bug 或意外的结果需要对程序进一步修改如下所示assert not is_prime(1)Traceback (most recent call last):File “”, line 1, inAssertionError对代码进行相应地修改直到新测试通过如下所示def is_prime(number):if number in (0, 1):return Falsefor element in range(2, number):if number % element 0:return Falsereturn True更多的测试用例表明当前的实现仍然不完备如下所示assert not is_prime(-3)Traceback (most recent call last):File “”, line 1, inAssertionError更新过的代码如下def is_prime(number):if number 0 or number in (0, 1):return Falsefor element in range(2, number):if number % element 0:return Falsereturn 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 服务器交互时编写测试根本不明显这在本章有介绍。