单元、集成和功能测试

单元测试 在应用程序中测试“单元”的行为并不奇怪。在这种情况下,“单元”通常是类实例的函数或方法。该单元也被称为“测试单元”。

单个单元测试的目标是测试 only “被测单元”的一些排列。如果编写一个旨在通过python函数验证特定代码路径结果的单元测试,那么只需关注测试 生活在功能体本身 . 如果函数接受表示复杂应用程序“域对象”(如资源、数据库连接或SMTP服务器)的参数,则在单元测试期间提供给此函数的参数 不必 而且有可能 不应该 一个“真实”的实现对象。例如,尽管一个特定的函数实现可能接受一个表示SMTP服务器对象的参数,并且当系统正常运行时,该函数可能调用该对象的方法,这将导致发送电子邮件,但是该函数的代码路径的单元测试确实 not 需要测试是否实际发送了电子邮件。它只需要确保函数调用作为参数提供的对象的方法, 如果参数恰好是SMTP服务器对象的“真实”实现,则发送电子邮件。

集成测试 另一方面,是一种不同的测试形式,其中明确测试了两个或多个“单元”之间的交互。集成测试验证应用程序的组件是否可以协同工作。你 可以 确保在集成测试中实际发送了一封电子邮件。

A 功能测试 是集成测试的一种形式,应用程序在其中“逐字”运行。你会 不得不 确保一封电子邮件实际上是在功能测试中发送的,因为它会端到端地测试代码。

为任何给定的代码库编写每种类型的测试通常被认为是最佳实践。单元测试通常提供了获得更好“覆盖”的机会:通常可以向被测试单元提供参数和/或导致 all 要执行的潜在代码路径。对于一组集成或功能测试来说,这通常不是那么容易做到,但是集成和功能测试提供了一种保证您的“单元”协同工作的度量,正如在生产环境中运行应用程序时所期望的那样。

单元和集成测试的建议机制 Pyramid 应用程序是python unittest 模块。尽管此模块已命名 unittest 它实际上能够驱动单元测试和集成测试。好的 unittest 教程在 Dive Into Python 马克·朝圣者。

Pyramid 提供了许多使单元、集成和功能测试更容易编写的工具。当您的代码调用 Pyramid -相关框架功能。

测试设置和拆卸

Pyramid 使用“全局”(实际上 thread local )保存两个项的数据结构:当前 request 和当前 application registry . 这些数据结构可通过 pyramid.threadlocal.get_current_request()pyramid.threadlocal.get_current_registry() 分别是函数。见 线程局部 有关这些函数及其返回的数据结构的信息。

如果代码使用这些 get_current_* 函数或调用 Pyramid 使用的代码 get_current_* 函数,您需要调用 pyramid.testing.setUp() 在测试设置中,您需要调用 pyramid.testing.tearDown() 在你的测试拆卸。 setUp() 将注册表推送到 thread local 叠加,这使得 get_current_* 功能正常。它返回一个 Configurator 对象,可用于执行测试代码所需的额外配置。 tearDown() 弹出线程本地堆栈。

通常,当配置程序直接与 main 金字塔应用程序的块,它将执行任何“真正的工作”推迟到 .commit 方法(通常由 pyramid.config.Configurator.make_wsgi_app() 方法)。由返回的配置程序 setUp() 是一个 自动提交 但是,配置器会立即执行调用它的方法所隐含的所有操作。这对于单元测试来说比需要调用更方便 pyramid.config.Configurator.commit() 在每个测试中添加额外的配置语句之后。

使用的 setUp()tearDown() 函数允许您为测试用例中的每个单元测试方法提供一个环境,该环境在单个测试期间具有一个独立的注册表和一个独立的请求。下面是使用此功能的示例:

1
2
3
4
5
6
7
8
9
import unittest
from pyramid import testing

class MyTest(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()

    def tearDown(self):
        testing.tearDown()

以上将确保 get_current_registry() 在的测试用例方法中调用 MyTest 将返回 application registryconfig 配置器实例。附加到的每个测试用例方法 MyTest 将使用独立的注册表。

这个 setUp()tearDown() 函数接受影响测试环境的各种参数。见 pyramid.testing 有关这些函数支持的额外参数的信息。

如果你还想 get_current_request() 返回除 None 在单个测试过程中,您可以通过 request 对象进入 pyramid.testing.setUp()setUp 测试方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import unittest
from pyramid import testing

class MyTest(unittest.TestCase):
    def setUp(self):
        request = testing.DummyRequest()
        self.config = testing.setUp(request=request)

    def tearDown(self):
        testing.tearDown()

如果你通过 request 对象进入 pyramid.testing.setUp() 在测试用例中 setUp ,任何附加到 MyTest 直接或间接调用的测试用例 get_current_request() 将接收请求对象。否则,在测试期间, get_current_request() 将返回 None . 我们使用由提供的“虚拟”请求实现 pyramid.testing.DummyRequest 因为它比“真实”更容易构造 Pyramid 请求对象。

使用上下文管理器测试安装程序

设置测试配置的另一种方式是使用 with 语句和 pyramid.testing.testConfig() 创建一个 context manager . 上下文管理器将调用 pyramid.testing.setUp() 在测试代码之前 pyramid.testing.tearDown() 之后。

这种样式对于小型的独立测试很有用。例如:

1
2
3
4
5
6
7
8
9
import unittest

class MyTest(unittest.TestCase):

    def test_my_function(self):
        from pyramid import testing
        with testing.testConfig() as config:
            config.add_route('bar', '/bar/{id}')
            my_function_which_needs_route_bar()

什么?

线程本地数据结构总是有点混乱,特别是当它们被框架使用时。对不起的。所以这里有一个经验法则:如果你不 know 是否调用使用 get_current_registry()get_current_request() 函数,或者你不关心这些,但是你仍然想写测试代码,只是总是调用 pyramid.testing.setUp() 在你的测试中 setUp 方法及 pyramid.testing.tearDown() 在你的测试中 tearDown 方法。如果正在测试的应用程序不调用任何 get_current* 功能。

使用 Configuratorpyramid.testing 单元测试中的API

这个 Configurator API与 pyramid.testing 模块提供了许多功能,可以在单元测试期间使用。这些功能使 configuration declaration 呼叫当前 application registry 但通常注册一个“存根”或“虚拟”特性来代替代码正常运行时调用的“真实”特性。

例如,假设您想要单元测试A Pyramid 查看函数。

1
2
3
4
5
6
from pyramid.httpexceptions import HTTPForbidden

def view_fn(request):
    if request.has_permission('edit'):
        raise HTTPForbidden
    return {'greeting':'hello'}

注解

此代码意味着您已在相关的 pyramid.config.Configurator 实例,否则在正常运行时会失败。

在单元测试期间不做任何特殊的操作, has_permission() 在此视图中,函数将始终返回 True 价值。当A Pyramid 应用程序正常启动,它将填充 application registry 使用 configuration declaration 针对A的呼叫 Configurator . 但是,如果未创建和填充此应用程序注册表(例如,通过使用授权策略初始化配置程序),例如通过单元测试调用应用程序代码时, Pyramid API函数将趋向于失败或返回默认结果。那么如何测试这个视图函数中的代码分支 HTTPForbidden 是吗?

由提供的测试API Pyramid 允许您模拟在单元测试框架下使用的各种应用程序注册表注册,而无需调用其所隐含的实际应用程序配置。 main 功能。例如,如果您想测试以上内容 view_fn (假设它存在于名为 my.package )你可以写一个 unittest.TestCase 使用了测试API。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import unittest
from pyramid import testing

class MyTest(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()

    def tearDown(self):
        testing.tearDown()

    def test_view_fn_forbidden(self):
        from pyramid.httpexceptions import HTTPForbidden
        from my.package import view_fn
        self.config.testing_securitypolicy(userid='hank',
                                           permissive=False)
        request = testing.DummyRequest()
        request.context = testing.DummyResource()
        self.assertRaises(HTTPForbidden, view_fn, request)

    def test_view_fn_allowed(self):
        from my.package import view_fn
        self.config.testing_securitypolicy(userid='hank',
                                           permissive=True)
        request = testing.DummyRequest()
        request.context = testing.DummyResource()
        response = view_fn(request)
        self.assertEqual(response, {'greeting':'hello'})

在上面的示例中,我们创建一个 MyTest 继承自的测试用例 unittest.TestCase . 如果是我们的 Pyramid 应用程序,它将在 pytest 运行。它有两种测试方法。

第一种测试方法, test_view_fn_forbidden 测试 view_fn 当身份验证策略禁止当前用户时, edit 许可。其第三行使用 testing_securitypolicy() 方法,这是单元测试的一种特殊辅助方法。

然后我们创建一个 pyramid.testing.DummyRequest 对象,它模拟WebOB请求对象API。一 pyramid.testing.DummyRequest 是一个请求对象,它需要的设置少于“real” Pyramid 请求。我们调用正在测试的函数和已生成的请求。调用函数时, pyramid.request.Request.has_permission() 将调用我们注册的“虚拟”身份验证策略 testing_securitypolicy() 拒绝访问。我们检查查看函数是否引发 HTTPForbidden 错误。

第二种测试方法 test_view_fn_allowed ,测试验证策略允许访问的备用情况。注意,我们将不同的值传递给 testing_securitypolicy() 为了得到这个结果。最后,我们断言view函数返回一个值。

注意,测试调用 pyramid.testing.setUp() 在其功能 setUp 方法与 pyramid.testing.tearDown() 在其功能 tearDown 方法。我们将结果分配给 pyramid.testing.setUp() 作为 config 在UnitTest类上。这是一个 Configurator 在测试中,可以根据需要调用配置器的对象和所有方法。如果你使用 Configurator 在测试过程中,请确保在测试用例中使用此模式 setUptearDown ;这些方法确保您使用的是“新的” application registry 每次测试运行。

pyramid.testing 整个章节 Pyramid -特定测试API。本章介绍用于注册安全策略、在路径上注册资源、注册事件侦听器、注册视图和视图权限的API,以及表示请求和资源的“虚拟”实现的类。

参见

另请参见 Configurator 记录在 configuration_module 首先是 testing_ 前缀。

创建集成测试

Pyramid ,A 单元测试 通常依赖“mock”或“dummy”实现为测试中的代码提供足够的上下文来运行。

“集成测试”意味着另一种测试。在 Pyramid 集成测试,测试逻辑练习被测代码的功能。 and 它与其他 Pyramid 框架。

为创建集成测试 Pyramid 应用程序通常意味着调用应用程序的 includeme 功能通过 pyramid.config.Configurator.include() 在测试的设置代码中。这导致了整个 Pyramid 要设置的环境,模拟“真实”运行应用程序时发生的情况。这是一个重量级的方法,可以确保测试有足够的上下文来正常运行,并测试代码与 Pyramid .

编写使用 Configurator 与创建集成测试相比,设置正确的“模拟”注册的API通常更受欢迎。单元测试将运行得更快(因为它们对每个测试做的更少),并且通常更容易解释。

创建功能测试

功能测试测试文字应用程序。

在金字塔中,通常使用 WebTest 包,它提供用于向应用程序调用HTTP请求的API。我们也喜欢 pytestpytest-cov 提供简单的测试和覆盖报告。

不管是哪种测试 package 使用时,请确保添加 tests_require 依赖于应用程序的包 setup.py 文件。使用项目 myproject 由Starter CookieCutter生成,如中所述 创建一个 Pyramid 项目 ,我们将在 requires 在文件中阻止 myproject/setup.py .

11
12
13
14
15
16
17
18
19
20
21
22
23
requires = [
    'plaster_pastedeploy',
    'pyramid',
    'pyramid_jinja2',
    'pyramid_debugtoolbar',
    'waitress',
]

tests_require = [
    'WebTest',
    'pytest',
    'pytest-cov',
]

记住改变依赖关系。

42
43
44
45
46
    zip_safe=False,
    extras_require={
        'testing': tests_require,
    },
    install_requires=requires,

和往常一样,每当您更改依赖项时,请确保运行正确的 pip install -e 命令。

$VENV/bin/pip install -e ".[testing]"

在你 MyPackage 项目,你的 package 被命名 myproject 其中包含 views 包含一个 default.py 模块,依次包含 view 功能 my_view 当调用根URL时返回HTML正文:

1
2
3
4
5
6
from pyramid.view import view_config


@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
def my_view(request):
    return {'project': 'myproject'}

下面的函数测试示例演示如何调用上面的 view

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class FunctionalTests(unittest.TestCase):
    def setUp(self):
        from myproject import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)

    def test_root(self):
        res = self.testapp.get('/', status=200)
        self.assertTrue(b'Pyramid' in res.body)

运行此测试时,每个测试方法都会创建一个“real” WSGI 应用程序使用 main 在您的 myproject.__init__ 模块,使用 WebTest 包装该wsgi应用程序。它将结果分配给 self.testapp . 在名为的测试中 test_root , the TestAppGET 方法用于调用根URL。最后,声明返回的HTML包含文本 Pyramid .

WebTest 有关可用于 webtest.app.TestApp 实例。