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