单元、集成和功能测试¶
单元测试 在应用程序中测试“单元”的行为并不奇怪。在这种情况下,“单元”通常是类实例的函数或方法。该单元也被称为“测试单元”。
单个单元测试的目标是测试 only “被测单元”的一些排列。如果编写一个旨在通过python函数验证特定代码路径结果的单元测试,那么只需关注测试 生活在功能体本身 . 如果函数接受表示复杂应用程序“域对象”(如资源、数据库连接或SMTP服务器)的参数,则在单元测试期间提供给此函数的参数 不必 而且有可能 不应该 一个“真实”的实现对象。例如,尽管一个特定的函数实现可能接受一个表示SMTP服务器对象的参数,并且当系统正常运行时,该函数可能调用该对象的方法,这将导致发送电子邮件,但是该函数的代码路径的单元测试确实 not 需要测试是否实际发送了电子邮件。它只需要确保函数调用作为参数提供的对象的方法, 将 如果参数恰好是SMTP服务器对象的“真实”实现,则发送电子邮件。
安 集成测试 另一方面,是一种不同的测试形式,其中明确测试了两个或多个“单元”之间的交互。集成测试验证应用程序的组件是否可以协同工作。你 可以 确保在集成测试中实际发送了一封电子邮件。
A 功能测试 是集成测试的一种形式,应用程序在其中“逐字”运行。你会 不得不 确保一封电子邮件实际上是在功能测试中发送的,因为它会端到端地测试代码。
为任何给定的代码库编写每种类型的测试通常被认为是最佳实践。单元测试通常提供了获得更好“覆盖”的机会:通常可以向被测试单元提供参数和/或导致 all 要执行的潜在代码路径。对于一组集成或功能测试来说,这通常不是那么容易做到,但是集成和功能测试提供了一种保证您的“单元”协同工作的度量,正如在生产环境中运行应用程序时所期望的那样。
单元和集成测试的建议机制 Pyramid 应用程序是python unittest
模块。尽管此模块已命名 unittest
它实际上能够驱动单元测试和集成测试。好的 unittest
教程在 Dive Into Python 3 马克·朝圣者。
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()
函数允许您为测试用例中的每个单元测试方法提供一个环境,该环境在单个测试期间具有一个独立的注册表和一个独立的请求。下面是使用此功能的示例:
1import unittest
2from pyramid import testing
3
4class MyTest(unittest.TestCase):
5 def setUp(self):
6 self.config = testing.setUp()
7
8 def tearDown(self):
9 testing.tearDown()
以上将确保 get_current_registry()
在的测试用例方法中调用 MyTest
将返回 application registry 与 config
配置器实例。附加到的每个测试用例方法 MyTest
将使用独立的注册表。
这个 setUp()
和 tearDown()
函数接受影响测试环境的各种参数。见 pyramid.testing 有关这些函数支持的额外参数的信息。
如果你还想 get_current_request()
返回除 None
在单个测试过程中,您可以通过 request 对象进入 pyramid.testing.setUp()
内 setUp
测试方法:
1import unittest
2from pyramid import testing
3
4class MyTest(unittest.TestCase):
5 def setUp(self):
6 request = testing.DummyRequest()
7 self.config = testing.setUp(request=request)
8
9 def tearDown(self):
10 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()
之后。
这种样式对于小型的独立测试很有用。例如:
1import unittest
2
3class MyTest(unittest.TestCase):
4
5 def test_my_function(self):
6 from pyramid import testing
7 with testing.testConfig() as config:
8 config.add_route('bar', '/bar/{id}')
9 my_function_which_needs_route_bar()
什么?¶
线程本地数据结构总是有点混乱,特别是当它们被框架使用时。对不起的。所以这里有一个经验法则:如果你不 know 是否调用使用 get_current_registry()
或 get_current_request()
函数,或者你不关心这些,但是你仍然想写测试代码,只是总是调用 pyramid.testing.setUp()
在你的测试中 setUp
方法及 pyramid.testing.tearDown()
在你的测试中 tearDown
方法。如果正在测试的应用程序不调用任何 get_current*
功能。
使用 Configurator
和 pyramid.testing
单元测试中的API¶
这个 Configurator
API与 pyramid.testing
模块提供了许多功能,可以在单元测试期间使用。这些功能使 configuration declaration 呼叫当前 application registry 但通常注册一个“存根”或“虚拟”特性来代替代码正常运行时调用的“真实”特性。
例如,假设您想要单元测试A Pyramid 查看函数。
1from pyramid.httpexceptions import HTTPForbidden
2
3def view_fn(request):
4 if request.has_permission('edit'):
5 raise HTTPForbidden
6 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。
1import unittest
2from pyramid import testing
3
4class MyTest(unittest.TestCase):
5 def setUp(self):
6 self.config = testing.setUp()
7
8 def tearDown(self):
9 testing.tearDown()
10
11 def test_view_fn_forbidden(self):
12 from pyramid.httpexceptions import HTTPForbidden
13 from my.package import view_fn
14 self.config.testing_securitypolicy(userid='hank',
15 permissive=False)
16 request = testing.DummyRequest()
17 request.context = testing.DummyResource()
18 self.assertRaises(HTTPForbidden, view_fn, request)
19
20 def test_view_fn_allowed(self):
21 from my.package import view_fn
22 self.config.testing_securitypolicy(userid='hank',
23 permissive=True)
24 request = testing.DummyRequest()
25 request.context = testing.DummyResource()
26 response = view_fn(request)
27 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
在测试过程中,请确保在测试用例中使用此模式 setUp
和 tearDown
;这些方法确保您使用的是“新的” application registry 每次测试运行。
见 pyramid.testing 整个章节 Pyramid -特定测试API。本章介绍用于注册安全策略、在路径上注册资源、注册事件侦听器、注册视图和视图权限的API,以及表示请求和资源的“虚拟”实现的类。
参见
另请参见 Configurator 记录在 pyramid.config 首先是 testing_
前缀。
创建集成测试¶
在 Pyramid ,A 单元测试 通常依赖“mock”或“dummy”实现为测试中的代码提供足够的上下文来运行。
“集成测试”意味着另一种测试。在 Pyramid 集成测试,测试逻辑练习被测代码的功能。 and 它与其他 Pyramid 框架。
为创建集成测试 Pyramid 应用程序通常意味着调用应用程序的 includeme
功能通过 pyramid.config.Configurator.include()
在测试的设置代码中。这导致了整个 Pyramid 要设置的环境,模拟“真实”运行应用程序时发生的情况。这是一个重量级的方法,可以确保测试有足够的上下文来正常运行,并测试代码与 Pyramid .
参见
也见 包括来自外部源的配置
编写使用 Configurator
与创建集成测试相比,设置正确的“模拟”注册的API通常更受欢迎。单元测试将运行得更快(因为它们对每个测试做的更少),并且通常更容易解释。
创建功能测试¶
功能测试测试文字应用程序。
在金字塔中,通常使用 WebTest 包,它提供用于向应用程序调用HTTP请求的API。我们也喜欢 pytest
和 pytest-cov
提供简单的测试和覆盖报告。
不管是哪种测试 package 使用时,请确保添加 tests_require
依赖于应用程序的包 setup.py
文件。使用项目 myproject
由Starter CookieCutter生成,如中所述 创建一个 Pyramid 项目 ,我们将在 requires
在文件中阻止 myproject/setup.py
.
11requires = [
12 'plaster_pastedeploy',
13 'pyramid',
14 'pyramid_jinja2',
15 'pyramid_debugtoolbar',
16 'waitress',
17]
18
19tests_require = [
20 'WebTest',
21 'pytest',
22 'pytest-cov',
23]
记住改变依赖关系。
42 zip_safe=False,
43 extras_require={
44 'testing': tests_require,
45 },
46 install_requires=requires,
和往常一样,每当您更改依赖项时,请确保运行正确的 pip install -e
命令。
$VENV/bin/pip install -e ".[testing]"
在你 myproject
项目,你的 package 被命名 myproject
其中包含 views
包含一个 default.py
模块,依次包含 view 功能 my_view
当调用根URL时返回HTML正文:
1from pyramid.view import view_config 2 3 4@view_config(route_name='home', renderer='myproject:templates/mytemplate.jinja2') 5def my_view(request): 6 return {'project': 'myproject'}
测试配置和夹具在中定义 conftest.py
. 在下面的示例中,我们定义了一个测试夹具。
1@pytest.fixture 2def testapp(app): 3 testapp = webtest.TestApp(app, extra_environ={ 4 'HTTP_HOST': 'example.com', 5 }) 6 7 return testapp
此夹具用于以下功能测试示例,以演示调用上述功能测试 view :
1def test_root(testapp): 2 res = testapp.get('/', status=200) 3 assert b'Pyramid' in res.body 4 5def test_notfound(testapp): 6 res = testapp.get('/badurl', status=404) 7 assert res.status_code == 404
当这些测试运行时,每个测试方法都会创建一个“real” WSGI 应用程序使用 main
在您的 myproject.__init__
模块,使用 WebTest 包装那个WSGI应用程序。它将结果分配给 res
.
在名为 test_root
, the TestApp
的 GET
方法用于调用根URL。声明返回的HTML包含文本 Pyramid
.
在名为 test_notfound
, the TestApp
的 GET
方法用于调用错误的URL /badurl
. 断言响应中返回的状态代码是 404
.
见 WebTest 有关可用于 webtest.app.TestApp
实例。