添加测试

我们现在将在 tests 包裹。在以后的测试中,确保它继续工作。

测试线束

这个项目通过一些测试和一个基本的工具来启动。它们位于 tests 项目顶层的包。把测试放在一个 tests 与应用程序包一起打包,特别是随着项目规模和复杂性的增加。一个有用的约定是应用程序中的每个模块在 tests 包裹。测试模块的名称与前缀相同 test_ .

线束包括以下设置:

  • pytest.ini -基本控件 pytest 配置,包括在哪里找到测试。我们已经配置 pytest 在应用程序包和 tests 包裹。

  • .coveragerc -控制覆盖范围配置。在我们的设置中,它与 pytest-cov 我们通过 --cov 选项到 pytest 命令。

  • testing.ini -镜子 development.iniproduction.ini 包含用于执行测试套件的设置。最重要的是,它包含需要数据库的测试所使用的数据库连接信息。

  • tests_require 在里面 setup.py -控制测试时安装的依赖项。当列表更改时,需要重新运行 $VENV/bin/pip install -e ".[testing]" 以确保安装新的依赖项。

  • tests/conftest.py -在我们整个测试过程中都可以使用核心夹具。下面几节将更详细地解释这些固定装置。正常开放 tests/conftest.py 跟着走。

会话范围测试夹具

  • app_settings -设置 dicttesting.ini 通常被传递的文件 pserve 应用程序的 main 功能。

  • app - Pyramid WSGI应用程序,实现 pyramid.interfaces.IRouter 接口。这通常用于功能测试。

每个测试夹具

  • tm -A transaction.TransactionManager 对象控制事务生命周期。一般来说,其他固定装置会连接到 tm fixture来控制它们的生命周期,并确保它们在测试结束时被中止。

  • testapp -A webtest.TestApp 实例包装 app 用于向应用程序发送请求并返回可检查的完整响应对象。这个 testapp 能够改变请求环境以便 tm 被注入的任何代码所使用 request.tm . 这应该加入 request.root 事务管理器的ZODB模型,以允许回滚对数据库的更改。这个 testapp 维护一个cookiejar,因此它可以用于在请求之间共享状态,以及事务数据库连接。

  • app_request -A pyramid.request.Request 对象,该对象可用于更轻量级的测试,而不是完整的 testapp . 这个 app_request 可以传递给需要一个完全功能的请求对象的视图函数和其他代码。

  • dummy_request -A pyramid.testing.DummyRequest 非常轻量的对象。这是一个很好的对象,可以传递给视图函数,这些函数的副作用最小,因为它将是快速和简单的。

单元测试

我们可以在代码库中测试各个api,以确保它们满足应用程序其余部分所期望的契约。例如,我们将测试添加到的密码哈希特性 tutorial.security 还有我们其他的模特。

创造 tests/test_models.py 如下所示:

 1from tutorial import models
 2
 3def test_page_model():
 4    instance = models.Page(data='some data')
 5    assert instance.data == 'some data'
 6
 7def test_wiki_model():
 8    wiki = models.Wiki()
 9    assert wiki.__parent__ is None
10    assert wiki.__name__ is None
11
12def test_appmaker():
13    root = {}
14    models.appmaker(root)
15    assert root['app_root']['FrontPage'].data == 'This is the front page'
16
17def test_password_hashing():
18    from tutorial.security import hash_password, check_password
19
20    password = 'secretpassword'
21    hashed_password = hash_password(password)
22    assert check_password(hashed_password, password)
23    assert not check_password(hashed_password, 'attackerpassword')
24    assert not check_password(None, password)

集成测试

我们可以直接执行视图代码,绕过 Pyramid 只测试我们写的代码。这些测试使用我们将适当准备的伪请求来设置每个视图期望的条件。

更新 tests/test_views.py 如下所示:

 1from pyramid import testing
 2
 3
 4class Test_view_wiki:
 5    def test_it_redirects_to_front_page(self):
 6        from tutorial.views.default import view_wiki
 7        context = testing.DummyResource()
 8        request = testing.DummyRequest()
 9        response = view_wiki(context, request)
10        assert response.location == 'http://example.com/FrontPage'
11
12class Test_view_page:
13    def _callFUT(self, context, request):
14        from tutorial.views.default import view_page
15        return view_page(context, request)
16
17    def test_it(self):
18        wiki = testing.DummyResource()
19        wiki['IDoExist'] = testing.DummyResource()
20        context = testing.DummyResource(data='Hello CruelWorld IDoExist')
21        context.__parent__ = wiki
22        context.__name__ = 'thepage'
23        request = testing.DummyRequest()
24        info = self._callFUT(context, request)
25        assert info['page'] == context
26        assert info['page_text'] == (
27            '<div class="document">\n'
28            '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
29            'CruelWorld</a> '
30            '<a href="http://example.com/IDoExist/">'
31            'IDoExist</a>'
32            '</p>\n</div>\n')
33        assert info['edit_url'] == 'http://example.com/thepage/edit_page'
34
35
36class Test_add_page:
37    def _callFUT(self, context, request):
38        from tutorial.views.default import add_page
39        return add_page(context, request)
40
41    def test_it_notsubmitted(self):
42        context = testing.DummyResource()
43        request = testing.DummyRequest()
44        request.subpath = ['AnotherPage']
45        info = self._callFUT(context, request)
46        assert info['page'].data == ''
47        assert info['save_url'] == request.resource_url(
48            context, 'add_page', 'AnotherPage')
49
50    def test_it_submitted(self):
51        context = testing.DummyResource()
52        request = testing.DummyRequest({
53            'form.submitted': True,
54            'body': 'Hello yo!',
55        })
56        request.subpath = ['AnotherPage']
57        self._callFUT(context, request)
58        page = context['AnotherPage']
59        assert page.data == 'Hello yo!'
60        assert page.__name__ == 'AnotherPage'
61        assert page.__parent__ == context
62
63class Test_edit_page:
64    def _callFUT(self, context, request):
65        from tutorial.views.default import edit_page
66        return edit_page(context, request)
67
68    def test_it_notsubmitted(self):
69        context = testing.DummyResource()
70        request = testing.DummyRequest()
71        info = self._callFUT(context, request)
72        assert info['page'] == context
73        assert info['save_url'] == request.resource_url(context, 'edit_page')
74
75    def test_it_submitted(self):
76        context = testing.DummyResource()
77        request = testing.DummyRequest({
78            'form.submitted': True,
79            'body': 'Hello yo!',
80        })
81        response = self._callFUT(context, request)
82        assert response.location == 'http://example.com/'
83        assert context.data == 'Hello yo!'

功能测试

我们将测试整个应用程序,包括单元测试和集成测试中未测试的安全方面,如登录、注销、检查 basic 用户无法编辑未创建的页面,但 editor 用户可以,等等。

更新 tests/test_functional.py 如下所示:

 1viewer_login = (
 2    '/login?login=viewer&password=viewer'
 3    '&came_from=FrontPage&form.submitted=Login'
 4)
 5viewer_wrong_login = (
 6    '/login?login=viewer&password=incorrect'
 7    '&came_from=FrontPage&form.submitted=Login'
 8)
 9editor_login = (
10    '/login?login=editor&password=editor'
11    '&came_from=FrontPage&form.submitted=Login'
12)
13
14def test_root(testapp):
15    res = testapp.get('/', status=303)
16    assert res.location == 'http://example.com/FrontPage'
17
18def test_FrontPage(testapp):
19    res = testapp.get('/FrontPage', status=200)
20    assert b'FrontPage' in res.body
21
22def test_missing_page(testapp):
23    res = testapp.get('/SomePage', status=404)
24    assert b'Not Found' in res.body
25
26def test_referrer_is_login(testapp):
27    res = testapp.get('/login', status=200)
28    assert b'name="came_from" value="/"' in res.body
29
30def test_successful_log_in(testapp):
31    res = testapp.get(viewer_login, status=303)
32    assert res.location == 'http://example.com/FrontPage'
33
34def test_failed_log_in(testapp):
35    res = testapp.get(viewer_wrong_login, status=400)
36    assert b'login' in res.body
37
38def test_logout_link_present_when_logged_in(testapp):
39    res = testapp.get(viewer_login, status=303)
40    res = testapp.get('/FrontPage', status=200)
41    assert b'Logout' in res.body
42
43def test_logout_link_not_present_after_logged_out(testapp):
44    res = testapp.get(viewer_login, status=303)
45    res = testapp.get('/FrontPage', status=200)
46    res = testapp.get('/logout', status=303)
47    assert b'Logout' not in res.body
48
49def test_anonymous_user_cannot_edit(testapp):
50    res = testapp.get('/FrontPage/edit_page', status=200)
51    assert b'Login' in res.body
52
53def test_anonymous_user_cannot_add(testapp):
54    res = testapp.get('/add_page/NewPage', status=200)
55    assert b'Login' in res.body
56
57def test_viewer_user_cannot_edit(testapp):
58    res = testapp.get(viewer_login, status=303)
59    res = testapp.get('/FrontPage/edit_page', status=200)
60    assert b'Login' in res.body
61
62def test_viewer_user_cannot_add(testapp):
63    res = testapp.get(viewer_login, status=303)
64    res = testapp.get('/add_page/NewPage', status=200)
65    assert b'Login' in res.body
66
67def test_editors_member_user_can_edit(testapp):
68    res = testapp.get(editor_login, status=303)
69    res = testapp.get('/FrontPage/edit_page', status=200)
70    assert b'Editing' in res.body
71
72def test_editors_member_user_can_add(testapp):
73    res = testapp.get(editor_login, status=303)
74    res = testapp.get('/add_page/NewPage', status=200)
75    assert b'Editing' in res.body
76
77def test_editors_member_user_can_view(testapp):
78    res = testapp.get(editor_login, status=303)
79    res = testapp.get('/FrontPage', status=200)
80    assert b'FrontPage' in res.body

运行测试

我们可以使用 pytest 类似于我们在 运行测试 . 在CookiCutter的帮助下,我们的测试依赖性已经得到了满足。 pytest 已经配置了覆盖范围。我们可以直接运行测试。

在UNIX上:

$VENV/bin/pytest -q

在Windows上:

%VENV%\Scripts\pytest -q

预期结果应如下所示:

.........................
25 passed in 3.87 seconds