summaryrefslogtreecommitdiff
path: root/docs/narr/unittesting.rst
blob: 580ec4ca1429d37a905c210b28b3c619197a3372 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
.. _unittesting_chapter:

Unit and Integration Testing
============================

The suggested mechanism for unit testing :mod:`repoze.bfg`
applications is the Python ``unittest`` module.  :mod:`repoze.bfg`
provides a number of facilities that make unit tests easier to write.
The facilities become particularly useful when your code calls into
:mod:`repoze.bfg` -related framework functions.

Using the ``repoze.bfg.testing`` API in Unit Tests
--------------------------------------------------

The ``repoze.bfg.testing`` module provides a number of functions which
can be used during unit testing.  For example, let's imagine you want
to unit test a :mod:`repoze.bfg` view function.

.. code-block:: python
   :linenos:

   def view_fn(context, request):
       from repoze.bfg.chameleon_zpt import render_template_to_response
       if 'say' in request.params:
           return render_template_to_response('templates/submitted.pt',
                                               say=request.params['say'])
       return render_template_to_response('templates/show.pt', say='Hello')

Without invoking any ZCML or using the testing API, an attempt to run
this view function will result in an error.  When a :mod:`repoze.bfg`
application starts normally, it will create an application registry
from the information it finds in the application's ``configure.zcml``
file.  But if this application registry is not created and populated
(e.g. with ``view`` declarations in ZCML), like when you invoke
application code via a unit test, :mod:`repoze.bfg` API functions will
tend to fail.

The testing API provided by ``repoze.bfg`` allows you to simulate
various application registry registrations for use under a unit
testing framework without needing to invoke the actual application
ZCML configuration.  For example, if you wanted to test the above
``view_fn`` (assuming it lived in ``my.package``), you could write a
unittest TestCase that used the testing API.

.. code-block:: python
   :linenos:

   import unittest
   from repoze.bfg import testing

   class MyTest(unittest.TestCase):
       def setUp(self):
           testing.setUp()

       def tearDown(self):
           testing.tearDown()
       
       def test_view_fn_not_submitted(self):
           from my.package import view_fn
           renderer = testing.registerTemplateRenderer('templates/show.pt')
           context = testing.DummyModel()
           request = testing.DummyRequest()
           response = view_fn(context, request)
           renderer.assert_(say='Hello')

       def test_view_fn_submitted(self):
           from my.package import view_fn
           renderer = testing.registerTemplateRenderer('templates/submitted.pt')
           context = testing.DummyModel()
           request = testing.DummyRequest()
           request.params['say'] = 'Yo'
           response = view_fn(context, request)
           renderer.assert_(say='Yo')

In the above example, we create a ``MyTest`` test case that inherits
from ``unittest.TestCase``.  If it's in our :mod:`repoze.bfg`
application, it will be found when ``setup.py test`` is run.  It has
two test methods.

The first test method, ``test_view_fn_not_submitted`` tests the
``view_fn`` function in the case that no "form" values (represented by
request.params) have been submitted.  Its first line registers a
"dummy template renderer" named ``templates/show.pt`` via the
``registerTemplateRenderer`` function (a ``repoze.bfg.testing`` API);
this function returns a DummyTemplateRenderer instance which we hang
on to for later.  We then create a ``DummyRequest`` object (it
simulates a WebOb request object), and we create a ``DummyModel``
context object.  We call the function being tested with the
manufactured context and request.  When the function is called,
``render_template_to_response`` will call the "dummy" template
renderer object instead of the real template renderer object.  When
the dummy renderer is called, it will set attributes on itself
corresponding to the non-path keyword arguments provided to the
``render_template_to_response`` function.  We check that the ``say``
parameter sent into the template rendering function was ``Hello`` in
this specific example.  The ``assert_`` method of the renderer we've
created will raise an ``AssertionError`` if the value passed to the
renderer as ``say`` does not equal ``Hello`` (any number of keyword
arguments are supported).

The second test method, named ``test_view_fn_submitted`` tests the
alternate case, where the ``say`` form value has already been set in
the request and performs a similar template registration and
assertion.  We assert at the end of this that the renderer's ``say``
attribute is ``Yo``, as this is what is expected of the view function
in the branch it's testing.

Note that the test calls the ``repoze.bfg.testing.setUp`` function in
its ``setUp`` method and the ``repoze.bfg.testing.tearDown`` function
in its ``tearDown`` method.  Use of this pattern is required to
perform cleanup between the test runs.  If you use any of the testing
API, be sure to call ``repoze.bfg.testing.setUp`` in the test setup
and ``repoze.bfg.testing.tearDown`` in the test teardown.

See the :ref:`testing_module` chapter for the entire :mod:`repoze.bfg`
-specific testing API.  This chapter describes APIs for registering a
security policy, registering models at paths, registering event
listeners, registering views and view permissions, and classes
representing "dummy" implementations of a request and a model.

.. _integration_tests:

Creating Integration Tests
--------------------------

In :mod:`repoze.bfg`, a unit test typically relies on "mock" or
"dummy" implementations to give the code under test only enough
context to run.

"Integration testing" implies another sort of testing.  In the context
of a :mod:`repoze.bfg`, integration test, the test logic tests the
functionality of some code *and* its integration with the rest of the
:mod:`repoze.bfg` framework.

In :mod:`repoze.bfg`, you create an integration test by *loading its
ZCML* in the test's setup code.  This causes the entire
:mod:`repoze.bfg` environment to be set up and torn down as if your
application was running "for real".  This is a heavy-hammer way of
making sure that your tests have enough context to run properly, and
it tests your code's integration with the rest of :mod:`repoze.bfg`.

Let's demonstrate this by showing an integration test for a view.  The
below test assumes that your application's package name is ``myapp``,
and that there is a ``views`` module in the app with a function with
the name ``my_view`` in it that returns the response 'Welcome to this
application' after accessing some values that require a fully set up
environment.

.. code-block:: python
   :linenos:

   import unittest

   from repoze.bfg import testing

   class ViewIntegrationTests(unittest.TestCase):
       def setUp(self):
           """ This sets up the application registry with the
           registrations your application declares in its configure.zcml
           (including dependent registrations for repoze.bfg itself).
           """
           testing.setUp()
           import myapp
           testing.zcml_configure('configure.zcml', package=myapp)

       def tearDown(self):
           """ Clear out the application registry """
           testing.tearDown()

       def test_my_view(self):
           from myapp.views import my_view
           context = testing.DummyModel()
           request = testing.DummyRequest()
           result = my_view(context, request)
           self.assertEqual(result.status, '200 OK')
           body = result.app_iter[0]
           self.failUnless('Welcome to' in body)
           self.assertEqual(len(result.headerlist), 2)
           self.assertEqual(result.headerlist[0],
                            ('Content-Type', 'text/html; charset=UTF-8'))
           self.assertEqual(result.headerlist[1], ('Content-Length',
                                                   str(len(body))))

Unless you cannot avoid it, you should prefer writing unit tests that
use the :mod:`repoze.bfg.testing` API to set up the right "mock"
registrations rather than creating an integration test.  Unit tests
will run faster (because they don't have to parse and execute ZCML for
each test) and the result of a unit test is usually easier to make
assertions about.