05:单元测试和 pytest

为项目的python代码提供单元测试。

背景

正如咒语所说,“未经测试的代码就是被破坏的代码。”Python社区有一个很长的编写测试脚本的文化,它可以确保您的代码在编写时正确工作,并在将来维护它。Pyramid一直致力于测试,从最早的预发行版开始,测试覆盖率达到100%。

python包含 unit testing framework 在它的标准库中。多年来,许多Python项目,例如 pytest ,已将此框架扩展为提供更多便利和功能的可选测试运行程序。Pyramid开发者使用 pytest ,我们将在本教程中使用。

别担心,本教程不会对“测试驱动开发”(TDD)太学究。我们将做足够的工作来确保在每一步中,我们没有主要地破坏代码。在编写代码时,您可能会发现这比经常更改浏览器并单击“重新加载”更方便。

我们还将讨论 pytest-cov 另一部分。

目标

  • 编写单元测试以确保代码的质量。

  • 安装python包 (pytest )这有助于我们的测试。

步骤

  1. 首先,我们复制上一步的结果。

    cd ..; cp -r debugtoolbar unit_testing; cd unit_testing
    
  2. 添加 pytest 对我们项目的依赖 setup.py 作为一个 Setuptools “额外”:

     1from setuptools import setup
     2
     3# List of dependencies installed via `pip install -e .`
     4# by virtue of the Setuptools `install_requires` value below.
     5requires = [
     6    'pyramid',
     7    'waitress',
     8]
     9
    10# List of dependencies installed via `pip install -e ".[dev]"`
    11# by virtue of the Setuptools `extras_require` value in the Python
    12# dictionary below.
    13dev_requires = [
    14    'pyramid_debugtoolbar',
    15    'pytest',
    16]
    17
    18setup(
    19    name='tutorial',
    20    install_requires=requires,
    21    extras_require={
    22        'dev': dev_requires,
    23    },
    24    entry_points={
    25        'paste.app_factory': [
    26            'main = tutorial:main'
    27        ],
    28    },
    29)
    
  3. 安装我们的项目及其新添加的依赖项。注意,我们使用了额外的说明符 [dev] 为开发安装测试需求,并用双引号将其和周期包围起来。

    $VENV/bin/pip install -e ".[dev]"
    
  4. 现在我们编写一个简单的单元测试 unit_testing/tutorial/tests.py

     1import unittest
     2
     3from pyramid import testing
     4
     5
     6class TutorialViewTests(unittest.TestCase):
     7    def setUp(self):
     8        self.config = testing.setUp()
     9
    10    def tearDown(self):
    11        testing.tearDown()
    12
    13    def test_hello_world(self):
    14        from tutorial import hello_world
    15
    16        request = testing.DummyRequest()
    17        response = hello_world(request)
    18        self.assertEqual(response.status_code, 200)
    
  5. 现在运行测试:

    $VENV/bin/pytest tutorial/tests.py -q
    .
    1 passed in 0.14 seconds
    

分析

我们的 tests.py 导入python标准单元测试框架。为了使编写面向Pyramid的测试更加方便,Pyramid提供了一些 pyramid.testing 我们在测试设置和拆卸中使用的帮助程序。我们的一个测试导入视图,发出一个虚拟请求,并查看视图是否返回我们期望的结果。

这个 tests.TutorialViewTests.test_hello_world 测试是单元测试的一个小例子。首先,我们在每个测试中导入视图。为什么不在顶部导入,就像在普通的Python代码中一样?因为导入会导致破坏测试的结果。我们希望我们的测试 单位 因此得名 unit 测试。每个测试都应该将自身隔离到正确的程度。

然后,我们的测试生成一个假的传入Web请求,然后调用我们的Pyramid视图。我们在响应上测试HTTP状态代码,以确保它符合我们的期望。

注意我们使用 pyramid.testing.setUp()pyramid.testing.tearDown() 这里实际上不需要;只有当测试需要使用 config 对象(它是一个配置程序),在调用视图之前将内容添加到配置状态。

额外credit

  1. 更改测试以断言响应状态代码应为 404 (意思是找不到)。跑 pytest 再一次。阅读错误报告,看看你是否能破译它告诉你的。

  2. 作为一个更现实的例子,把 tests.py 回到找到它时,在视图中放一个错误,例如对一个不存在的变量的引用。运行测试,看看这比重新加载浏览器和返回代码更方便。

  3. 最后,对于最现实的测试,阅读Pyramid Response 对象,并查看如何更改响应代码。运行测试,看看测试是如何确认代码声称支持的“契约”的。

  4. 我们如何添加单元测试断言来测试响应主体的HTML值?

  5. 我们为什么要进口 hello_world 视图函数 里面 这个 test_hello_world 方法而不是在模块顶部?