添加测试¶
我们现在将在 tests
包裹。在以后的测试中,确保它继续工作。
测试线束¶
这个项目通过一些测试和一个基本的工具来启动。它们位于 tests
项目顶层的包。把测试放在一个 tests
与应用程序包一起打包,特别是随着项目规模和复杂性的增加。一个有用的约定是应用程序中的每个模块在 tests
包裹。测试模块的名称与前缀相同 test_
.
线束包括以下设置:
pytest.ini
-基本控件pytest
配置,包括在哪里找到测试。我们已经配置pytest
在应用程序包和tests
包裹。.coveragerc
-控制覆盖范围配置。在我们的设置中,它与pytest-cov
我们通过--cov
选项到pytest
命令。testing.ini
-镜子development.ini
和production.ini
包含用于执行测试套件的设置。最重要的是,它包含需要数据库的测试所使用的数据库连接信息。tests_require
在里面setup.py
-控制测试时安装的依赖项。当列表更改时,需要重新运行$VENV/bin/pip install -e ".[testing]"
以确保安装新的依赖项。tests/conftest.py
-在我们整个测试过程中都可以使用核心夹具。下面几节将更详细地解释这些固定装置。正常开放tests/conftest.py
跟着走。
会话范围测试夹具¶
app_settings
-设置dict
从testing.ini
通常被传递的文件pserve
应用程序的main
功能。app
- Pyramid WSGI应用程序,实现pyramid.interfaces.IRouter
接口。这通常用于功能测试。
每个测试夹具¶
tm
-Atransaction.TransactionManager
对象控制事务生命周期。一般来说,其他固定装置会连接到tm
fixture来控制它们的生命周期,并确保它们在测试结束时被中止。testapp
-Awebtest.TestApp
实例包装app
用于向应用程序发送请求并返回可检查的完整响应对象。这个testapp
能够改变请求环境以便tm
被注入的任何代码所使用request.tm
. 这应该加入request.root
事务管理器的ZODB模型,以允许回滚对数据库的更改。这个testapp
维护一个cookiejar,因此它可以用于在请求之间共享状态,以及事务数据库连接。app_request
-Apyramid.request.Request
对象,该对象可用于更轻量级的测试,而不是完整的testapp
. 这个app_request
可以传递给需要一个完全功能的请求对象的视图函数和其他代码。dummy_request
-Apyramid.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