Skip to content

Latest commit

 

History

History
160 lines (102 loc) · 12.3 KB

File metadata and controls

160 lines (102 loc) · 12.3 KB

七、介绍 pytest

自动化测试被认为是生产高质量软件必不可少的工具和方法。测试应该是每个专业软件开发人员工具箱的一部分,但同时,许多人认为它是工作中枯燥和重复的一部分。但当您使用 pytest 作为测试框架时,情况并非如此。

从第一章开始,本书将向您介绍各种关键特性,并将教您如何在日常编码任务中有效地使用 pytest,重点是使您尽快提高工作效率。然后,写测试应该成为一种乐趣,而不是枯燥的工作。

我们首先来看一下为什么自动化测试很重要。我还将试图说服你,这不是你应该拥有的东西,因为这是正确的事情。自动化测试是你想要的东西,因为它会使你的工作更简单,更愉快。我们将简要介绍 Python 的标准unittest模块,并介绍 pytest,以及为什么它具有如此多的冲击力,同时又非常容易入门。然后,我们将介绍如何编写测试,如何将它们组织到类和目录中,以及如何有效地使用 pytest 的命令行。从这里,我们将了解如何使用标记来控制跳过测试或预期测试失败,如何利用自定义标记,以及如何使用相同的测试代码参数化来测试多个输入,以避免复制/粘贴代码。这将帮助我们了解如何使用 pytest 最受欢迎的特性之一:fixture 来管理和重用测试资源和环境。之后,我们将参观 pytest 提供的庞大插件生态系统中一些更流行、更有用的插件。最后,我们将探讨更高级的主题,即如何逐渐将基于unittest的测试套件转换为 pytest 样式,以利用其在现有代码库中的诸多优势。

在本章中,我们将快速了解为什么要进行测试、内置的unittest模块以及 pytest 的概述。以下是将要介绍的内容:

  • 为什么要花时间写测试?
  • 快速查看unittest模块
  • 为什么要测试?

让我们先退一步,想想为什么写作测试被认为如此重要。

为什么要花时间写测试

手动测试程序是很自然的;编写自动化测试并非易事。

程序员在学习编码或涉猎新技术和库时使用各种技术。编写短片段、遵循教程、在 REPL 中播放,甚至使用 Jupyter(是很常见的 http://jupyter.org/ )。通常,这涉及使用打印语句或绘图手动验证正在研究的结果。这是学习新事物的简单、自然和完全有效的方法。

然而,这种模式不应该延续到专业软件开发中。专业软件不简单;相反,它通常非常复杂。根据系统设计的好坏,各个部分可能以奇怪的方式交织在一起,添加新功能可能会破坏系统中另一个显然不相关的部分。修复一个 bug 可能会导致其他地方出现另一个 bug。

如何确保新功能正常工作或 bug 已被永久消除?同样重要的是,如何确保通过修复或引入新功能,系统的另一部分不会损坏?

答案是拥有一套健康的自动化测试套件,也称为测试套件。

简单地说,测试套件就是测试代码的代码。通常,它们创建一个或多个必要的资源,并调用被测试的应用程序代码。然后他们断言结果与预期一致。除了在开发人员的机器上执行之外,在大多数现代设置中,它们都是连续运行的,例如,由自动化系统(如 Jenkins)每小时或每次提交。因此,为一段代码添加测试意味着,从现在起,随着功能的添加和 bug 的修复,它将被一次又一次地测试。

自动化测试意味着您可以对程序进行更改,并立即查看这些更改是否破坏了系统的一部分,从而为开发人员提供了一个安全网。拥有一个好的测试套件是非常解放的:您不再害怕改进 8 年前编写的代码,如果您犯了任何错误,测试套件会告诉您。您可以添加一个新功能,并确信它不会破坏系统中您没有预料到的任何其他部分。能够自信地将大型库从 Python 2 转换为 Python 3 或进行大规模重构是绝对必要的。通过添加一个或多个自动测试来重现一个 bug,并证明您已经修复了它,您可以确保以后重构或其他编码错误不会再次引入该 bug。

一旦您习惯于享受将测试套件作为安全网的好处,您甚至可能决定为您所依赖的 API 编写测试,但要知道开发人员没有测试:能够向原始开发人员生成失败的测试,以证明他们的新版本是 bug 的罪魁祸首,而不是您的代码,这是一个难得的专业自豪时刻。

拥有一套编写良好且深入的测试套件将使您能够自信地进行大小更改,并帮助您在夜间睡得更好。

快速查看 unittest 模块

Python 附带了内置的unittest模块,这是一个基于 JUnit(Java 的单元测试框架)编写自动化测试的框架。通过从unittest.TestCase子类化并定义以test开头的方法来创建测试。下面是一个使用unittest的典型最小测试用例的示例:

    import unittest
    from fibo import fibonacci

    class Test(unittest.TestCase):

        def test_fibo(self):
            result = fibonacci(4)
            self.assertEqual(result, 3)

    if __name__ == '__main__':
        unittest.main()

本例的重点是展示测试本身,而不是测试代码,因此我们将使用一个简单的fibonacci函数。斐波那契序列是一个正整数的无限序列,序列中的下一个数是通过将前两个数相加得到的。以下是前 11 个数字:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

我们的fibonacci函数接收斐波那契序列的index,动态计算值并返回。

为了确保函数按预期工作,我们使用已知正确答案的值调用它(斐波那契级数的第四个元素是 3),然后调用self.assertEqual(a, b)方法检查a和b是否相等。如果函数存在错误且未返回预期结果,则框架将在执行时告诉我们:

 λ python3 -m venv .env
  source .env/bin/activate
 F
 ======================================================================
 FAIL: test_fibo (__main__.Test)
 ----------------------------------------------------------------------
 Traceback (most recent call last):
 File "test_fibo.py", line 8, in test_fibo
 self.assertEqual(result, 3)
 AssertionError: 5 != 3

 ----------------------------------------------------------------------
 Ran 1 test in 0.000s

 FAILED (failures=1)

似乎我们的fibonacci函数中有一个 bug,编写它的人忘记了n=0应该返回0。修复该功能并再次运行测试表明该功能现在是正确的:

    λ python test_fibo.py
 .
 ----------------------------------------------------------------------
 Ran 1 test in 0.000s

 OK

这是伟大的,当然是朝着正确方向迈出的一步。但是请注意,为了编写这个非常简单的检查代码,我们必须做一些与检查本身无关的事情:

  1. 进口unittest

  2. 从unittest.TestCase创建一个子类

  3. 使用self.assertEqual()进行检查;有很多self.assert*方法可以用于所有情况,比如self.assertGreaterEqual(例如≥ 比较)、self.assertLess(用于<比较)、self.assertAlmostEqual(用于浮点比较)、self.assertMultiLineEqual()(用于多行字符串比较)等等

上面的代码感觉像是不必要的样板文件,虽然它肯定不是世界末日,但有些人觉得代码不是 python 式的;编写代码只是为了安抚框架,让它做您需要的事情。

此外,unittest框架并没有提供太多电池来帮助您为现实世界编写测试。需要临时目录吗?您需要自己创建它,然后进行清理。需要连接到 PostgreSQL 数据库以测试 Flask 应用程序吗?您需要编写支持代码来连接到数据库,创建所需的表,并在测试结束时进行清理。需要在测试之间共享实用程序测试功能和资源吗?您将需要创建基类并通过子类化重用它们,在大型代码库中,子类化可能会演变为多重继承。一些框架提供自己的unittest支持代码(例如,Django,https://www.djangoproject.com/ ),但这些框架很少。

为什么要测试?

Pytest 是一个成熟且功能齐全的测试框架,从小型测试到应用程序和库的大规模功能测试都有。

Pytest 很容易开始使用。要编写测试,您不需要类;您可以编写以test开头的简单函数,并使用 Python 内置的assert语句:

    from fibo import fibonacci

    def test_fibo():
        assert fibonacci(4) == 3

就这样。您可以导入代码、编写函数并使用普通断言调用来确保它们按预期工作:无需创建子类并使用各种self.assert*方法进行测试。美妙的是,当断言失败时,它还提供了有用的输出:

 λ pytest test_fibo2.py -q
 F                                                              [100%]
 ============================= FAILURES ==============================
 _____________________________ test_fibo _____________________________

 def test_fibo():
 >       assert fibonacci(4) == 3
 E       assert 5 == 3
 E        + where 5 = fibonacci(4)

 test_fibo2.py:4: AssertionError
 1 failed in 0.03 seconds

请注意,将显示表达式中涉及的值及其周围的代码,以便更容易理解错误。

Pytest 不仅使编写测试变得简单,它还有许多命令行选项,可以提高生产率**,比如只运行最后一个失败的测试,或者运行一组特定的测试,或者因为它们被特别标记。**

创建和管理测试资源是在教程或测试框架概述中经常被忽略的一个重要方面。实际应用程序的测试通常需要复杂的设置,例如启动后台工作程序、填充数据库或初始化 GUI。使用 pytest,这些复杂的测试资源可以通过一个名为fixtures的强大机制进行管理。夹具使用简单,但同时功能强大,许多人将其称为*pytest 的杀手级功能。*详见第四章夹具。

定制很重要,pytest 更进一步,定义了一个非常强大的插件系统。插件可以改变测试运行的几个方面,从测试的执行方式到提供新的装置和功能,使测试许多类型的应用程序和框架变得容易。有些插件每次都以随机顺序执行测试,以确保测试不会改变可能影响其他测试的全局状态;有些插件会多次重复失败的测试,以消除不良行为;有些插件会在出现故障时显示故障,而不是仅在运行结束时显示故障,以及跨多个 CPU 执行测试以加快套件速度的插件。还有一些插件在测试 Django、Flask、Twisted 和 Qt 应用程序时非常有用,还有一些插件用于使用 Selenium 进行 web 应用程序验收测试。外部插件的数量真是惊人:在撰写本文时,有 500 多个 pytest 插件可以立即安装和使用(http://plugincompat.herokuapp.com/ )。

总结 pytest:

  • 您使用简单的assert报表来编写支票,并提供详细的报告
  • pytest 具有自动测试发现功能
  • 它具有用于管理测试资源的装置
  • 它有很多插件来扩展其内置功能,并帮助测试大量的框架和应用程序
  • 它开箱即用地运行基于unittest的测试套件,无需任何修改,因此您可以逐步迁移现有的测试套件

由于这些原因,很多人认为 pyTest-Python 是用 Python 编写测试的一种 Python 方法。它使编写简单测试变得容易,并且功能强大,足以编写非常复杂的功能测试。也许更重要的是,pytest 使测试变得有趣。

使用 pytest 编写自动化测试并享受其诸多好处将变得很自然。

总结

在本章中,我们介绍了为什么编写测试对于生产高质量的软件非常重要,并使您能够放心地引入更改。之后,我们看了一下内置的unittest模块,以及如何使用它来编写测试。最后,我们简要介绍了 pytest,发现了用它编写测试是多么简单,查看了它的关键特性,还查看了大量第三方插件,它们涵盖了广泛的用例和框架。

在下一章中,我们将学习如何安装 pytest,如何编写简单的测试,如何更好地将它们组织到项目中的文件和目录中,以及如何有效地使用命令行。