Christoph Zwerschke
2011-06-05 487f7e763935caf4ff9f2b6518512b9e915fe2c2
Make tests in the Wiki2 tutorial a separate chapter, as for Wiki1.

Also add functional tests to the Wiki2 tutorial, similar to Wiki1.
2 files added
7 files modified
408 ■■■■ changed files
docs/tutorials/wiki2/definingviews.rst 43 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/index.rst 1 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/tutorial/models.py 3 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/tutorial/views.py 8 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/models/tutorial/models.py 3 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/tests/tutorial/tests.py 267 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/views/tutorial/models.py 1 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/views/tutorial/views.py 8 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/tests.rst 74 ●●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/definingviews.rst
@@ -341,46 +341,3 @@
in a browser.  You should see an interactive exception handler in the
browser which allows you to examine values in a post-mortem mode.
Adding Tests
============
Since we've added a good bit of imperative code here, it's useful to
define tests for the views we've created.  We'll change our tests.py
module to look like this:
.. literalinclude:: src/views/tutorial/tests.py
   :linenos:
   :language: python
We can then run the tests using something like:
.. code-block:: text
   :linenos:
    $ python setup.py test -q
The expected output is something like:
.. code-block:: text
   :linenos:
   running test
   running egg_info
   writing requirements to tutorial.egg-info/requires.txt
   writing tutorial.egg-info/PKG-INFO
   writing top-level names to tutorial.egg-info/top_level.txt
   writing dependency_links to tutorial.egg-info/dependency_links.txt
   writing entry points to tutorial.egg-info/entry_points.txt
   unrecognized .svn/entries format in
   reading manifest file 'tutorial.egg-info/SOURCES.txt'
   writing manifest file 'tutorial.egg-info/SOURCES.txt'
   running build_ext
   ......
   ----------------------------------------------------------------------
   Ran 6 tests in 0.181s
   OK
docs/tutorials/wiki2/index.rst
@@ -23,6 +23,7 @@
   definingmodels
   definingviews
   authorization
   tests
   distributing
docs/tutorials/wiki2/src/authorization/tutorial/models.py
@@ -34,8 +34,9 @@
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)
    try:
        transaction.begin()
        session = DBSession()
        page = Page('FrontPage', 'initial data')
        page = Page('FrontPage', 'This is the front page')
        session.add(page)
        transaction.commit()
    except IntegrityError:
docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -2,7 +2,7 @@
from docutils.core import publish_parts
from pyramid.httpexceptions import HTTPFound
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.security import authenticated_userid
from pyramid.url import route_url
@@ -19,7 +19,9 @@
def view_page(request):
    pagename = request.matchdict['pagename']
    session = DBSession()
    page = session.query(Page).filter_by(name=pagename).one()
    page = session.query(Page).filter_by(name=pagename).first()
    if page is None:
        return HTTPNotFound('No such page')
    def check(match):
        word = match.group(1)
@@ -51,7 +53,7 @@
    page = Page('', '')
    logged_in = authenticated_userid(request)
    return dict(page=page, save_url=save_url, logged_in=logged_in)
def edit_page(request):
    name = request.matchdict['pagename']
    session = DBSession()
docs/tutorials/wiki2/src/models/tutorial/models.py
@@ -32,8 +32,9 @@
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)
    try:
        transaction.begin()
        session = DBSession()
        page = Page('FrontPage', 'initial data')
        page = Page('FrontPage', 'This is the front page')
        session.add(page)
        transaction.commit()
    except IntegrityError:
docs/tutorials/wiki2/src/tests/tutorial/tests.py
New file
@@ -0,0 +1,267 @@
import unittest
from pyramid import testing
def _initTestingDB():
    from tutorial.models import DBSession
    from tutorial.models import Base
    from sqlalchemy import create_engine
    engine = create_engine('sqlite:///:memory:')
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)
    return DBSession
def _registerRoutes(config):
    config.add_route('view_page', '{pagename}')
    config.add_route('edit_page', '{pagename}/edit_page')
    config.add_route('add_page', 'add_page/{pagename}')
class PageModelTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
    def tearDown(self):
        self.session.remove()
    def _getTargetClass(self):
        from tutorial.models import Page
        return Page
    def _makeOne(self, name='SomeName', data='some data'):
        return self._getTargetClass()(name, data)
    def test_constructor(self):
        instance = self._makeOne()
        self.assertEqual(instance.name, 'SomeName')
        self.assertEqual(instance.data, 'some data')
class InitializeSqlTests(unittest.TestCase):
    def setUp(self):
        from tutorial.models import DBSession
        DBSession.remove()
    def tearDown(self):
        from tutorial.models import DBSession
        DBSession.remove()
    def _callFUT(self, engine):
        from tutorial.models import initialize_sql
        return initialize_sql(engine)
    def test_it(self):
        from sqlalchemy import create_engine
        engine = create_engine('sqlite:///:memory:')
        self._callFUT(engine)
        from tutorial.models import DBSession, Page
        self.assertEqual(DBSession.query(Page).one().data,
            'This is the front page')
class ViewWikiTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def _callFUT(self, request):
        from tutorial.views import view_wiki
        return view_wiki(request)
    def test_it(self):
        _registerRoutes(self.config)
        request = testing.DummyRequest()
        response = self._callFUT(request)
        self.assertEqual(response.location, 'http://example.com/FrontPage')
class ViewPageTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
        self.config = testing.setUp()
    def tearDown(self):
        self.session.remove()
        testing.tearDown()
    def _callFUT(self, request):
        from tutorial.views import view_page
        return view_page(request)
    def test_it(self):
        from tutorial.models import Page
        request = testing.DummyRequest()
        request.matchdict['pagename'] = 'IDoExist'
        page = Page('IDoExist', 'Hello CruelWorld IDoExist')
        self.session.add(page)
        _registerRoutes(self.config)
        info = self._callFUT(request)
        self.assertEqual(info['page'], page)
        self.assertEqual(
            info['content'],
            '<div class="document">\n'
            '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
            'CruelWorld</a> '
            '<a href="http://example.com/IDoExist">'
            'IDoExist</a>'
            '</p>\n</div>\n')
        self.assertEqual(info['edit_url'],
            'http://example.com/IDoExist/edit_page')
class AddPageTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
        self.config = testing.setUp()
        self.config.begin()
    def tearDown(self):
        self.session.remove()
        testing.tearDown()
    def _callFUT(self, request):
        from tutorial.views import add_page
        return add_page(request)
    def test_it_notsubmitted(self):
        _registerRoutes(self.config)
        request = testing.DummyRequest()
        request.matchdict = {'pagename':'AnotherPage'}
        info = self._callFUT(request)
        self.assertEqual(info['page'].data,'')
        self.assertEqual(info['save_url'],
                         'http://example.com/add_page/AnotherPage')
    def test_it_submitted(self):
        from tutorial.models import Page
        _registerRoutes(self.config)
        request = testing.DummyRequest({'form.submitted':True,
                                        'body':'Hello yo!'})
        request.matchdict = {'pagename':'AnotherPage'}
        self._callFUT(request)
        page = self.session.query(Page).filter_by(name='AnotherPage').one()
        self.assertEqual(page.data, 'Hello yo!')
class EditPageTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
        self.config = testing.setUp()
    def tearDown(self):
        self.session.remove()
        testing.tearDown()
    def _callFUT(self, request):
        from tutorial.views import edit_page
        return edit_page(request)
    def test_it_notsubmitted(self):
        from tutorial.models import Page
        _registerRoutes(self.config)
        request = testing.DummyRequest()
        request.matchdict = {'pagename':'abc'}
        page = Page('abc', 'hello')
        self.session.add(page)
        info = self._callFUT(request)
        self.assertEqual(info['page'], page)
        self.assertEqual(info['save_url'],
            'http://example.com/abc/edit_page')
    def test_it_submitted(self):
        from tutorial.models import Page
        _registerRoutes(self.config)
        request = testing.DummyRequest({'form.submitted':True,
            'body':'Hello yo!'})
        request.matchdict = {'pagename':'abc'}
        page = Page('abc', 'hello')
        self.session.add(page)
        response = self._callFUT(request)
        self.assertEqual(response.location, 'http://example.com/abc')
        self.assertEqual(page.data, 'Hello yo!')
class FunctionalTests(unittest.TestCase):
    viewer_login = '/login?login=viewer&password=viewer' \
                   '&came_from=FrontPage&form.submitted=Login'
    viewer_wrong_login = '/login?login=viewer&password=incorrect' \
                   '&came_from=FrontPage&form.submitted=Login'
    editor_login = '/login?login=editor&password=editor' \
                   '&came_from=FrontPage&form.submitted=Login'
    def setUp(self):
        from tutorial import main
        settings = { 'sqlalchemy.url': 'sqlite:///:memory:'}
        app = main({}, **settings)
        from webtest import TestApp
        self.testapp = TestApp(app)
    def tearDown(self):
        del self.testapp
        from tutorial.models import DBSession
        DBSession.remove()
    def test_root(self):
        res = self.testapp.get('/', status=302)
        self.assertTrue(not res.body)
    def test_FrontPage(self):
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue('FrontPage' in res.body)
    def test_unexisting_page(self):
        res = self.testapp.get('/SomePage', status=404)
        self.assertTrue('No such page' in res.body, res.body)
    def test_successful_log_in(self):
        res = self.testapp.get(self.viewer_login, status=302)
        self.assertTrue(res.location == 'FrontPage')
    def test_failed_log_in(self):
        res = self.testapp.get(self.viewer_wrong_login, status=200)
        self.assertTrue('login' in res.body)
    def test_logout_link_present_when_logged_in(self):
        self.testapp.get(self.viewer_login, status=302)
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue('Logout' in res.body)
    def test_logout_link_not_present_after_logged_out(self):
        self.testapp.get(self.viewer_login, status=302)
        self.testapp.get('/FrontPage', status=200)
        res = self.testapp.get('/logout', status=302)
        self.assertTrue('Logout' not in res.body)
    def test_anonymous_user_cannot_edit(self):
        res = self.testapp.get('/FrontPage/edit_page', status=200)
        self.assertTrue('Login' in res.body)
    def test_anonymous_user_cannot_add(self):
        res = self.testapp.get('/add_page/NewPage', status=200)
        self.assertTrue('Login' in res.body)
    def test_viewer_user_cannot_edit(self):
        self.testapp.get(self.viewer_login, status=302)
        res = self.testapp.get('/FrontPage/edit_page', status=200)
        self.assertTrue('Login' in res.body)
    def test_viewer_user_cannot_add(self):
        self.testapp.get(self.viewer_login, status=302)
        res = self.testapp.get('/add_page/NewPage', status=200)
        self.assertTrue('Login' in res.body)
    def test_editors_member_user_can_edit(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/FrontPage/edit_page', status=200)
        self.assertTrue('Editing' in res.body)
    def test_editors_member_user_can_add(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/add_page/NewPage', status=200)
        self.assertTrue('Editing' in res.body)
    def test_editors_member_user_can_view(self):
        self.testapp.get(self.editor_login, status=302)
        res = self.testapp.get('/FrontPage', status=200)
        self.assertTrue('FrontPage' in res.body)
docs/tutorials/wiki2/src/views/tutorial/models.py
@@ -31,6 +31,7 @@
    Base.metadata.bind = engine
    Base.metadata.create_all(engine)
    try:
        transaction.begin()
        session = DBSession()
        page = Page('FrontPage', 'initial data')
        session.add(page)
docs/tutorials/wiki2/src/views/tutorial/views.py
@@ -2,7 +2,7 @@
from docutils.core import publish_parts
from pyramid.httpexceptions import HTTPFound
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.url import route_url
from tutorial.models import DBSession
@@ -18,7 +18,9 @@
def view_page(request):
    matchdict = request.matchdict
    session = DBSession()
    page = session.query(Page).filter_by(name=matchdict['pagename']).one()
    page = session.query(Page).filter_by(name=pagename).first()
    if page is None:
        return HTTPNotFound('No such page')
    def check(match):
        word = match.group(1)
@@ -48,7 +50,7 @@
    save_url = route_url('add_page', request, pagename=name)
    page = Page('', '')
    return dict(page=page, save_url=save_url)
def edit_page(request):
    name = request.matchdict['pagename']
    session = DBSession()
docs/tutorials/wiki2/tests.rst
New file
@@ -0,0 +1,74 @@
============
Adding Tests
============
We will now add tests for the models and the views and a few functional
tests in the ``tests.py``.  Tests ensure that an application works, and
that it continues to work after some changes are made in the future.
Testing the Models
==================
We write a test class for the model class ``Page`` and another test class
for the ``initialize_sql`` function.
To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a
result of the ``pyramid_routesalchemy`` project generator.  We'll add two
test classes: one for the ``Page`` model named ``PageModelTests``, one for the
``initialize_sql`` function named ``InitializeSqlTests``.
Testing the Views
=================
We'll modify our ``tests.py`` file, adding tests for each view function we
added above.  As a result, we'll *delete* the ``ViewTests`` test in the file,
and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``,
``AddPageTests``, and ``EditPageTests``.  These test the ``view_wiki``,
``view_page``, ``add_page``, and ``edit_page`` views respectively.
Functional tests
================
We 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.
Viewing the results of all our edits to ``tests.py``
====================================================
Once we're done with the ``tests.py`` module, it will look a lot like the
below:
.. literalinclude:: src/tests/tutorial/tests.py
   :linenos:
   :language: python
Running the Tests
=================
We can run these tests by using ``setup.py test`` in the same way we did in
:ref:`running_tests`.  Assuming our shell's current working directory is the
"tutorial" distribution directory:
On UNIX:
.. code-block:: text
   $ ../bin/python setup.py test -q
On Windows:
.. code-block:: text
   c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
The expected result looks something like:
.. code-block:: text
   ......................
   ----------------------------------------------------------------------
   Ran 22 tests in 2.700s
   OK