Adding Tests¶
We will now add tests for the models and the views and a few functional tests
in tests.py
. Tests ensure that an application works, and that it
continues to work when changes are made in the future.
Test the models¶
We write tests for the model
classes and the appmaker
. Changing
tests.py
, we'll write a separate test class for each model
class, and
we'll write a test class for the appmaker
.
To do so, we'll retain the tutorial.tests.ViewTests
class that was
generated from choosing the zodb
backend option. We'll add three test
classes: one for the Page
model named PageModelTests
, one for the
Wiki
model named WikiModelTests
, and one for the appmaker named
AppmakerTests
.
Test the views¶
We'll modify our tests.py
file, adding tests for each view function we
added previously. As a result, we'll delete the ViewTests
class that the
zodb
backend option provided, and add four other test classes:
ViewWikiTests
, ViewPageTests
, AddPageTests
, and EditPageTests
.
These test the view_wiki
, view_page
, add_page
, and edit_page
views.
Functional tests¶
We'll test the whole application, covering security aspects that are not
tested in the unit tests, like logging in, logging out, checking that
the viewer
user cannot add or edit pages, but the editor
user
can, and so on.
View the results of all our edits to tests.py
¶
Open the tutorial/tests.py
module, and edit it such that it appears as
follows:
1import unittest
2
3from pyramid import testing
4
5class PageModelTests(unittest.TestCase):
6
7 def _getTargetClass(self):
8 from .models import Page
9 return Page
10
11 def _makeOne(self, data=u'some data'):
12 return self._getTargetClass()(data=data)
13
14 def test_constructor(self):
15 instance = self._makeOne()
16 self.assertEqual(instance.data, u'some data')
17
18class WikiModelTests(unittest.TestCase):
19
20 def _getTargetClass(self):
21 from .models import Wiki
22 return Wiki
23
24 def _makeOne(self):
25 return self._getTargetClass()()
26
27 def test_it(self):
28 wiki = self._makeOne()
29 self.assertEqual(wiki.__parent__, None)
30 self.assertEqual(wiki.__name__, None)
31
32class AppmakerTests(unittest.TestCase):
33
34 def _callFUT(self, zodb_root):
35 from .models import appmaker
36 return appmaker(zodb_root)
37
38 def test_it(self):
39 root = {}
40 self._callFUT(root)
41 self.assertEqual(root['app_root']['FrontPage'].data,
42 'This is the front page')
43
44class ViewWikiTests(unittest.TestCase):
45 def test_it(self):
46 from .views import view_wiki
47 context = testing.DummyResource()
48 request = testing.DummyRequest()
49 response = view_wiki(context, request)
50 self.assertEqual(response.location, 'http://example.com/FrontPage')
51
52class ViewPageTests(unittest.TestCase):
53 def _callFUT(self, context, request):
54 from .views import view_page
55 return view_page(context, request)
56
57 def test_it(self):
58 wiki = testing.DummyResource()
59 wiki['IDoExist'] = testing.DummyResource()
60 context = testing.DummyResource(data='Hello CruelWorld IDoExist')
61 context.__parent__ = wiki
62 context.__name__ = 'thepage'
63 request = testing.DummyRequest()
64 info = self._callFUT(context, request)
65 self.assertEqual(info['page'], context)
66 self.assertEqual(
67 info['content'],
68 '<div class="document">\n'
69 '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
70 'CruelWorld</a> '
71 '<a href="http://example.com/IDoExist/">'
72 'IDoExist</a>'
73 '</p>\n</div>\n')
74 self.assertEqual(info['edit_url'],
75 'http://example.com/thepage/edit_page')
76
77
78class AddPageTests(unittest.TestCase):
79 def _callFUT(self, context, request):
80 from .views import add_page
81 return add_page(context, request)
82
83 def test_it_notsubmitted(self):
84 context = testing.DummyResource()
85 request = testing.DummyRequest()
86 request.subpath = ['AnotherPage']
87 info = self._callFUT(context, request)
88 self.assertEqual(info['page'].data,'')
89 self.assertEqual(
90 info['save_url'],
91 request.resource_url(context, 'add_page', 'AnotherPage'))
92
93 def test_it_submitted(self):
94 context = testing.DummyResource()
95 request = testing.DummyRequest({'form.submitted':True,
96 'body':'Hello yo!'})
97 request.subpath = ['AnotherPage']
98 self._callFUT(context, request)
99 page = context['AnotherPage']
100 self.assertEqual(page.data, 'Hello yo!')
101 self.assertEqual(page.__name__, 'AnotherPage')
102 self.assertEqual(page.__parent__, context)
103
104class EditPageTests(unittest.TestCase):
105 def _callFUT(self, context, request):
106 from .views import edit_page
107 return edit_page(context, request)
108
109 def test_it_notsubmitted(self):
110 context = testing.DummyResource()
111 request = testing.DummyRequest()
112 info = self._callFUT(context, request)
113 self.assertEqual(info['page'], context)
114 self.assertEqual(info['save_url'],
115 request.resource_url(context, 'edit_page'))
116
117 def test_it_submitted(self):
118 context = testing.DummyResource()
119 request = testing.DummyRequest({'form.submitted':True,
120 'body':'Hello yo!'})
121 response = self._callFUT(context, request)
122 self.assertEqual(response.location, 'http://example.com/')
123 self.assertEqual(context.data, 'Hello yo!')
124
125class SecurityTests(unittest.TestCase):
126 def test_hashing(self):
127 from .security import hash_password, check_password
128 password = 'secretpassword'
129 hashed_password = hash_password(password)
130 self.assertTrue(check_password(hashed_password, password))
131
132 self.assertFalse(check_password(hashed_password, 'attackerpassword'))
133
134 self.assertFalse(check_password(None, password))
135
136class FunctionalTests(unittest.TestCase):
137
138 viewer_login = '/login?login=viewer&password=viewer' \
139 '&came_from=FrontPage&form.submitted=Login'
140 viewer_wrong_login = '/login?login=viewer&password=incorrect' \
141 '&came_from=FrontPage&form.submitted=Login'
142 editor_login = '/login?login=editor&password=editor' \
143 '&came_from=FrontPage&form.submitted=Login'
144
145 def setUp(self):
146 import tempfile
147 import os.path
148 from . import main
149 self.tmpdir = tempfile.mkdtemp()
150
151 dbpath = os.path.join( self.tmpdir, 'test.db')
152 uri = 'file://' + dbpath
153 settings = { 'zodbconn.uri' : uri ,
154 'pyramid.includes': ['pyramid_zodbconn', 'pyramid_tm'] }
155
156 app = main({}, **settings)
157 self.db = app.registry._zodb_databases['']
158 from webtest import TestApp
159 self.testapp = TestApp(app)
160
161 def tearDown(self):
162 import shutil
163 self.db.close()
164 shutil.rmtree( self.tmpdir )
165
166 def test_root(self):
167 res = self.testapp.get('/', status=302)
168 self.assertEqual(res.location, 'http://localhost/FrontPage')
169
170 def test_FrontPage(self):
171 res = self.testapp.get('/FrontPage', status=200)
172 self.assertTrue(b'FrontPage' in res.body)
173
174 def test_unexisting_page(self):
175 res = self.testapp.get('/SomePage', status=404)
176 self.assertTrue(b'Not Found' in res.body)
177
178 def test_referrer_is_login(self):
179 res = self.testapp.get('/login', status=200)
180 self.assertTrue(b'name="came_from" value="/"' in res.body)
181
182 def test_successful_log_in(self):
183 res = self.testapp.get( self.viewer_login, status=302)
184 self.assertEqual(res.location, 'http://localhost/FrontPage')
185
186 def test_failed_log_in(self):
187 res = self.testapp.get( self.viewer_wrong_login, status=200)
188 self.assertTrue(b'login' in res.body)
189
190 def test_logout_link_present_when_logged_in(self):
191 res = self.testapp.get( self.viewer_login, status=302)
192 res = self.testapp.get('/FrontPage', status=200)
193 self.assertTrue(b'Logout' in res.body)
194
195 def test_logout_link_not_present_after_logged_out(self):
196 res = self.testapp.get( self.viewer_login, status=302)
197 res = self.testapp.get('/FrontPage', status=200)
198 res = self.testapp.get('/logout', status=302)
199 self.assertTrue(b'Logout' not in res.body)
200
201 def test_anonymous_user_cannot_edit(self):
202 res = self.testapp.get('/FrontPage/edit_page', status=200)
203 self.assertTrue(b'Login' in res.body)
204
205 def test_anonymous_user_cannot_add(self):
206 res = self.testapp.get('/add_page/NewPage', status=200)
207 self.assertTrue(b'Login' in res.body)
208
209 def test_viewer_user_cannot_edit(self):
210 res = self.testapp.get( self.viewer_login, status=302)
211 res = self.testapp.get('/FrontPage/edit_page', status=200)
212 self.assertTrue(b'Login' in res.body)
213
214 def test_viewer_user_cannot_add(self):
215 res = self.testapp.get( self.viewer_login, status=302)
216 res = self.testapp.get('/add_page/NewPage', status=200)
217 self.assertTrue(b'Login' in res.body)
218
219 def test_editors_member_user_can_edit(self):
220 res = self.testapp.get( self.editor_login, status=302)
221 res = self.testapp.get('/FrontPage/edit_page', status=200)
222 self.assertTrue(b'Editing' in res.body)
223
224 def test_editors_member_user_can_add(self):
225 res = self.testapp.get( self.editor_login, status=302)
226 res = self.testapp.get('/add_page/NewPage', status=200)
227 self.assertTrue(b'Editing' in res.body)
228
229 def test_editors_member_user_can_view(self):
230 res = self.testapp.get( self.editor_login, status=302)
231 res = self.testapp.get('/FrontPage', status=200)
232 self.assertTrue(b'FrontPage' in res.body)
Running the tests¶
We can run these tests by using pytest
similarly to how we did in
Run the tests. Courtesy of the cookiecutter, our testing dependencies have
already been satisfied and pytest
and coverage have already been
configured, so we can jump right to running tests.
On Unix:
$VENV/bin/pytest -q
On Windows:
%VENV%\Scripts\pytest -q
The expected result should look like the following:
.........................
25 passed in 6.87 seconds