diff options
| -rw-r--r-- | CHANGES.txt | 3 | ||||
| -rw-r--r-- | docs/glossary.rst | 5 | ||||
| -rw-r--r-- | docs/index.rst | 2 | ||||
| -rw-r--r-- | docs/latexindex.rst | 2 | ||||
| -rw-r--r-- | docs/narr/project.rst | 4 | ||||
| -rw-r--r-- | docs/narr/testing.rst | 407 | ||||
| -rw-r--r-- | docs/narr/unittesting.rst | 382 | ||||
| -rw-r--r-- | pyramid/testing.py | 4 |
8 files changed, 421 insertions, 388 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index caeaa943b..5c3e5c1fe 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -114,6 +114,9 @@ Documentation - Many changes to support terminological renaming of "model" to "resource" and "resource" to "asset". +- Added an example of ``WebTest`` functional testing to the testing narrative + chapter. + Paster Templates ---------------- diff --git a/docs/glossary.rst b/docs/glossary.rst index fc6eda328..a3aacebce 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -841,3 +841,8 @@ Glossary ``request.registry.settings`` dictionary. Deployment settings can be used as global application values. + WebTest + `WebTest <http://pythonpaste.org/webtest/>`_ is a package which can help + you write functional tests for your WSGI application. + + diff --git a/docs/index.rst b/docs/index.rst index 188c2b518..d1b256e43 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -54,7 +54,7 @@ Narrative documentation in chapter form explaining how to use narr/vhosting narr/events narr/environment - narr/unittesting + narr/testing narr/hooks narr/declarative narr/extending diff --git a/docs/latexindex.rst b/docs/latexindex.rst index 52389e421..6855a1d23 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -47,7 +47,7 @@ Narrative Documentation narr/vhosting narr/events narr/environment - narr/unittesting + narr/testing narr/hooks narr/declarative narr/extending diff --git a/docs/narr/project.rst b/docs/narr/project.rst index b9d49335e..ccc79ee3d 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -901,8 +901,8 @@ tests here as you build your application. You are not required to write tests to use :app:`Pyramid`, this file is simply provided as convenience and example. -See :ref:`unittesting_chapter` for more information about writing -:app:`Pyramid` unit tests. +See :ref:`testing_chapter` for more information about writing :app:`Pyramid` +unit tests. Modifying Package Structure ---------------------------- diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst new file mode 100644 index 000000000..dd7ec103f --- /dev/null +++ b/docs/narr/testing.rst @@ -0,0 +1,407 @@ +.. index:: + single: unit testing + single: integration testing + single: functional testing + +.. _testing_chapter: + +Unit, Integration, and Functional Testing +========================================= + +*Unit testing* is, not surprisingly, the act of testing a "unit" in your +application. In this context, a "unit" is often a function or a method of a +class instance. The unit is also referred to as a "unit under test". + +The goal of a single unit test is to test **only** some permutation of the +"unit under test". If you write a unit test that aims to verify the result +of a particular codepath through a Python function, you need only be +concerned about testing the code that *lives in the function body itself*. +If the function accepts a parameter that represents a complex application +"domain object" (such as a resource, a database connection, or an SMTP +server), the argument provided to this function during a unit test *need not +be* and likely *should not be* a "real" implementation object. For example, +although a particular function implementation may accept an argument that +represents an SMTP server object, and the function may call a method of this +object when the system is operating normally that would result in an email +being sent, a unit test of this codepath of the function does *not* need to +test that an email is actually sent. It just needs to make sure that the +function calls the method of the object provided as an argument that *would* +send an email if the argument happened to be the "real" implementation of an +SMTP server object. + +An *integration test*, on the other hand, is a different form of testing in +which the interaction between two or more "units" is explicitly tested. +Integration tests verify that the components of your application work +together. You *might* make sure that an email was actually sent in an +integration test. + +A *functional test* is a form of integration test in which the application is +run "literally". You would *have to* make sure that an email was actually +sent in a functional test, because it tests your code end to end. + +It is often considered best practice to write each type of tests for any +given codebase. Unit testing often provides the opportunity to obtain better +"coverage": it's usually possible to supply a unit under test with arguments +and/or an environment which causes *all* of its potential codepaths to be +executed. This is usually not as easy to do with a set of integration or +functinal tests, but integration and functional testing provides a measure of +assurance that your "units" work together, as they will be expected to when +your application is run in production. + +The suggested mechanism for unit and integration testing of a :app:`Pyramid` +application is the Python :mod:`unittest` module. Although this module is +named :mod:`unittest`, it is actually capable of driving both unit and +integration tests. A good :mod:`unittest` tutorial is available within `Dive +Into Python <http://diveintopython.org/unit_testing/index.html>`_ by Mark +Pilgrim. + +:app:`Pyramid` provides a number of facilities that make unit, integration, +and cunctional tests easier to write. The facilities become particularly +useful when your code calls into :app:`Pyramid` -related framework functions. + +.. index:: + single: test setup + single: test tear down + single: unittest + +.. _test_setup_and_teardown: + +Test Set Up and Tear Down +-------------------------- + +:app:`Pyramid` uses a "global" (actually :term:`thread local`) data structure +to hold on to two items: the current :term:`request` and the current +:term:`application registry`. These data structures are available via the +:func:`pyramid.threadlocal.get_current_request` and +:func:`pyramid.threadlocal.get_current_registry` functions, respectively. +See :ref:`threadlocals_chapter` for information about these functions and the +data structures they return. + +If your code uses these ``get_current_*`` functions or calls :app:`Pyramid` +code which uses ``get_current_*`` functions, you will need to construct a +:term:`Configurator` and call its ``begin`` method within the ``setUp`` +method of your unit test and call the same Configurator's ``end`` method +within the ``tearDown`` method of your unit test. + +We'll also instruct the Configurator we use during testing to *autocommit*. +Normally when a Configurator is used by an application, it defers performing +any "real work" until its ``.commit`` method is called (often implicitly by +the :meth:`pyramid.config.Configurator.make_wsgi_app` method). Passing +``autocommit=True`` to the Configurator constructor causes the Configurator +to perform all actions implied by methods called on it immediately, which is +more convenient for unit-testing purposes than needing to call +:meth:`pyramid.config.Configurator.commit` in each test. + +The use of a Configurator and its ``begin`` and ``end`` methods allows you to +supply each unit test method in a test case with an environment that has an +isolated registry and an isolated request for the duration of a single test. +Here's an example of using this feature: + +.. code-block:: python + :linenos: + + import unittest + from pyramid.config import Configurator + + class MyTest(unittest.TestCase): + def setUp(self): + self.config = Configurator(autocommit=True) + self.config.begin() + + def tearDown(self): + self.config.end() + +The above will make sure that +:func:`pyramid.threadlocal.get_current_registry` will return the +:term:`application registry` associated with the ``config`` Configurator +instance when :func:`pyramid.threadlocal.get_current_registry` is called in a +test case method attached to ``MyTest``. Each test case method attached to +``MyTest`` will use an isolated registry. + +The :meth:`pyramid.config.Configurator.begin` method accepts various +arguments that influence the code run during the test. See the +:ref:`configuration_module` chapter for information about the API of a +:term:`Configurator`, including its ``begin`` and ``end`` methods. + +If you also want to make :func:`pyramid.get_current_request` return something +other than ``None`` during the course of a single test, you can pass a +:term:`request` object into the :meth:`pyramid.config.Configurator.begin` +method of the Configurator within the ``setUp`` method of your test: + +.. code-block:: python + :linenos: + + import unittest + from pyramid.config import Configurator + from pyramid import testing + + class MyTest(unittest.TestCase): + def setUp(self): + self.config = Configurator(autocommit=True) + request = testing.DummyRequest() + self.config.begin(request=request) + + def tearDown(self): + self.config.end() + +If you pass a :term:`request` object into the ``begin`` method of the +configurator within your test case's ``setUp``, any test method attached to +the ``MyTest`` test case that directly or indirectly calls +:func:`pyramid.threadlocal.get_current_request` will receive the request you +passed into the ``begin`` method. Otherwise, during testing, +:func:`pyramid.threadlocal.get_current_request` will return ``None``. We use +a "dummy" request implementation supplied by +:class:`pyramid.testing.DummyRequest` because it's easier to construct than a +"real" :app:`Pyramid` request object. + +What? +~~~~~ + +Thread local data structures are always a bit confusing, especially when +they're used by frameworks. Sorry. So here's a rule of thumb: if you don't +*know* whether you're calling code that uses the +:func:`pyramid.threadlocal.get_current_registry` or +:func:`pyramid.threadlocal.get_current_request` functions, or you don't care +about any of this, but you still want to write test code, just always create +an autocommitting Configurator instance and call its ``begin`` method within +the ``setUp`` of a unit test, then subsequently call its ``end`` method in +the test's ``tearDown``. This won't really hurt anything if the application +you're testing does not call any ``get_current*`` function. + +.. index:: + single: pyramid.testing + single: Configurator testing API + +Using the ``Configurator`` and ``pyramid.testing`` APIs in Unit Tests +------------------------------------------------------------------------ + +The ``Configurator`` API and the ``pyramid.testing`` module provide a number +of functions which can be used during unit testing. These functions make +:term:`configuration declaration` calls to the current :term:`application +registry`, but typically register a "stub" or "dummy" feature in place of the +"real" feature that the code would call if it was being run normally. + +For example, let's imagine you want to unit test a :app:`Pyramid` view +function. + +.. code-block:: python + :linenos: + + def view_fn(request): + from pyramid.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 startup code or using the testing API, an attempt to run +this view function in a unit test will result in an error. When a +:app:`Pyramid` application starts normally, it will populate a +:term:`application registry` using :term:`configuration declaration` calls +made against a :term:`Configurator` (sometimes deferring to the application's +``configure.zcml`` :term:`ZCML` file via ``load_zcml``). But if this +application registry is not created and populated (e.g. with an +:meth:`pyramid.config.Configurator.add_view` :term:`configuration +declaration` or ``view`` declarations in :term:`ZCML`), like when you invoke +application code via a unit test, :app:`Pyramid` API functions will tend to +fail. + +The testing API provided by :app:`Pyramid` allows you to simulate various +application registry registrations for use under a unit testing framework +without needing to invoke the actual application configuration implied by its +``run.py``. For example, if you wanted to test the above ``view_fn`` +(assuming it lived in the package named ``my.package``), you could write a +:class:`unittest.TestCase` that used the testing API. + +.. code-block:: python + :linenos: + + import unittest + from pyramid.config import Configurator + from pyramid import testing + + class MyTest(unittest.TestCase): + def setUp(self): + self.config = Configurator(autocommit=True) + self.config.begin() + + def tearDown(self): + self.config.end() + + def test_view_fn_not_submitted(self): + from my.package import view_fn + renderer = self.config.testing_add_renderer('templates/show.pt') + request = testing.DummyRequest() + response = view_fn(request) + renderer.assert_(say='Hello') + + def test_view_fn_submitted(self): + from my.package import view_fn + renderer = self.config.testing_add_renderer( + 'templates/submitted.pt') + request = testing.DummyRequest() + request.params['say'] = 'Yo' + response = view_fn(request) + renderer.assert_(say='Yo') + +In the above example, we create a ``MyTest`` test case that inherits from +:mod:`unittest.TestCase`. If it's in our :app:`Pyramid` 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 +:meth:`pyramid.config.Configurator.testing_add_renderer` method; this method +returns a :class:`pyramid.testing.DummyTemplateRenderer` instance which we +hang on to for later. + +We then create a :class:`pyramid.testing.DummyRequest` object which simulates +a WebOb request object API. A :class:`pyramid.testing.DummyRequest` is a +request object that requires less setup than a "real" :app:`Pyramid` request. +We call the function being tested with the manufactured request. When the +function is called, :func:`pyramid.chameleon_zpt.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 +:func:`pyramid.chameleon_zpt.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 :exc:`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 :meth:`pyramid.config.Configurator.begin` method +in its ``setUp`` method and the ``end`` method of the same in its +``tearDown`` method. If you use any of the +:class:`pyramid.config.Configurator` APIs during testing, be sure to use this +pattern in your test case's ``setUp`` and ``tearDown``; these methods make +sure you're using a "fresh" :term:`application registry` per test run. + +See the :ref:`testing_module` chapter for the entire :app:`Pyramid` -specific +testing API. This chapter describes APIs for registering a security policy, +registering resources at paths, registering event listeners, registering +views and view permissions, and classes representing "dummy" implementations +of a request and a resource. + +See also the various methods of the :term:`Configurator` documented in +:ref:`configuration_module` that begin with the ``testing_`` prefix. + +.. index:: + single: integration tests + +.. _integration_tests: + +Creating Integration Tests +-------------------------- + +In :app:`Pyramid`, 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 +:app:`Pyramid`, integration test, the test logic tests the functionality of +some code *and* its integration with the rest of the :app:`Pyramid` +framework. + +In :app:`Pyramid` applications that use :term:`ZCML`, you can create an +integration test by *loading its ZCML* in the test's setup code. This causes +the entire :app:`Pyramid` 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 :app:`Pyramid`. + +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 pyramid.config import Configurator + from pyramid 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 pyramid itself). + """ + import myapp + self.config = Configurator(package=myapp, autocommit=True) + self.config.begin() + self.config.load_zcml('myapp:configure.zcml') + + def tearDown(self): + """ Clear out the application registry """ + self.config.end() + + def test_my_view(self): + from myapp.views import my_view + request = testing.DummyRequest() + result = my_view(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 +:class:`pyramid.config.Configurator` API to set up the right "mock" +registrations rather than creating an integration test. Unit tests will run +faster (because they do less for each test) and the result of a unit test is +usually easier to make assertions about. + +.. index:: + single: functional tests + +.. _functional_tests: + +Creating Functional Tests +------------------------- + +Functional tests test your literal application. + +The below test assumes that your application's package name is ``myapp``, and +that there is view that returns an HTML body when the root URL is invoked. +It further assumes that you've added a ``tests_require`` dependency on the +``WebTest`` package within your ``setup.py`` file. :term:`WebTest` is a +functional testing package written by Ian Bicking. + +.. code-block:: python + :linenos: + + import unittest + + class FunctionalTests(unittest.TestCase): + def setUp(self): + from myapp import main + app = main({}) + from webtest import TestApp + self.testapp = TestApp(app) + + def test_root(self): + res = self.testapp.get('/', status=200) + self.failUnless('Pyramid' in res.body) + +When this test is run, each test creates a "real" WSGI application using the +``main`` function in your ``myapp.__init__`` module and uses :term:`WebTest` +to wrap that WSGI application. It assigns the result to ``self.testapp``. +In the test named ``test_root``, we use the testapp's ``get`` method to +invoke the root URL. We then assert that the returned HTML has the string +``Pyramid`` in it. + +See the :term:`WebTest` documentation for further information about the +methods available to a :class:`webtest.TestApp` instance. diff --git a/docs/narr/unittesting.rst b/docs/narr/unittesting.rst deleted file mode 100644 index 6a721f403..000000000 --- a/docs/narr/unittesting.rst +++ /dev/null @@ -1,382 +0,0 @@ -\.. index:: - single: unit testing - single: integration testing - -.. _unittesting_chapter: - -Unit and Integration Testing -============================ - -*Unit testing* is, not surprisingly, the act of testing a "unit" in -your application. In this context, a "unit" is often a function or a -method of a class instance. The unit is also referred to as a "unit -under test". - -The goal of a single unit test is to test **only** some permutation of the -"unit under test". If you write a unit test that aims to verify the result -of a particular codepath through a Python function, you need only be -concerned about testing the code that *lives in the function body itself*. -If the function accepts a parameter that represents a complex application -"domain object" (such as a resource, a database connection, or an SMTP -server), the argument provided to this function during a unit test *need not -be* and likely *should not be* a "real" implementation object. For example, -although a particular function implementation may accept an argument that -represents an SMTP server object, and the function may call a method of this -object when the system is operating normally that would result in an email -being sent, a unit test of this codepath of the function does *not* need to -test that an email is actually sent. It just needs to make sure that the -function calls the method of the object provided as an argument that *would* -send an email if the argument happened to be the "real" implementation of an -SMTP server object. - -An *integration test*, on the other hand, is a different form of -testing in which the interaction between two or more "units" is -explicitly tested. Integration tests verify that the components of -your application work together. You *might* make sure that an email -was actually sent in an integration test. - -It is often considered best practice to write both types of tests for -any given codebase. Unit testing often provides the opportunity to -obtain better "coverage": it's usually possible to supply a unit under -test with arguments and/or an environment which causes *all* of its -potential codepaths to be executed. This is usually not as easy to do -with a set of integration tests, but integration testing provides a -measure of assurance that your "units" work together, as they will be -expected to when your application is run in production. - -The suggested mechanism for unit and integration testing of a -:app:`Pyramid` application is the Python :mod:`unittest` module. -Although this module is named :mod:`unittest`, it is actually capable -of driving both unit and integration tests. A good :mod:`unittest` -tutorial is available within `Dive Into Python -<http://diveintopython.org/unit_testing/index.html>`_ by Mark Pilgrim. - -:app:`Pyramid` provides a number of facilities that make unit and -integration tests easier to write. The facilities become particularly -useful when your code calls into :app:`Pyramid` -related framework -functions. - -.. index:: - single: test setup - single: test tear down - single: unittest - -.. _test_setup_and_teardown: - -Test Set Up and Tear Down --------------------------- - -:app:`Pyramid` uses a "global" (actually :term:`thread local`) data -structure to hold on to two items: the current :term:`request` and the -current :term:`application registry`. These data structures are -available via the :func:`pyramid.threadlocal.get_current_request` -and :func:`pyramid.threadlocal.get_current_registry` functions, -respectively. See :ref:`threadlocals_chapter` for information about -these functions and the data structures they return. - -If your code uses these ``get_current_*`` functions or calls -:app:`Pyramid` code which uses ``get_current_*`` functions, you -will need to construct a :term:`Configurator` and call its ``begin`` -method within the ``setUp`` method of your unit test and call the same -Configurator's ``end`` method within the ``tearDown`` method of your -unit test. - -We'll also instruct the Configurator we use during testing to *autocommit*. -Normally when a Configurator is used by an application, it defers performing -any "real work" until its ``.commit`` method is called (often implicitly by -the :meth:`pyramid.config.Configurator.make_wsgi_app` method). Passing -``autocommit=True`` to the Configurator constructor causes the Configurator -to perform all actions implied by methods called on it immediately, which is -more convenient for unit-testing purposes than needing to call -:meth:`pyramid.config.Configurator.commit` in each test. - -The use of a Configurator and its ``begin`` and ``end`` methods allows -you to supply each unit test method in a test case with an environment -that has an isolated registry and an isolated request for the duration -of a single test. Here's an example of using this feature: - -.. code-block:: python - :linenos: - - import unittest - from pyramid.config import Configurator - - class MyTest(unittest.TestCase): - def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() - - def tearDown(self): - self.config.end() - -The above will make sure that -:func:`pyramid.threadlocal.get_current_registry` will return the -:term:`application registry` associated with the ``config`` Configurator -instance when :func:`pyramid.threadlocal.get_current_registry` is called in a -test case method attached to ``MyTest``. Each test case method attached to -``MyTest`` will use an isolated registry. - -The :meth:`pyramid.config.Configurator.begin` method accepts -various arguments that influence the code run during the test. See -the :ref:`configuration_module` chapter for information about the API -of a :term:`Configurator`, including its ``begin`` and ``end`` -methods. - -If you also want to make :func:`pyramid.get_current_request` -return something other than ``None`` during the course of a single -test, you can pass a :term:`request` object into the -:meth:`pyramid.config.Configurator.begin` method of the -Configurator within the ``setUp`` method of your test: - -.. code-block:: python - :linenos: - - import unittest - from pyramid.config import Configurator - from pyramid import testing - - class MyTest(unittest.TestCase): - def setUp(self): - self.config = Configurator(autocommit=True) - request = testing.DummyRequest() - self.config.begin(request=request) - - def tearDown(self): - self.config.end() - -If you pass a :term:`request` object into the ``begin`` method of the -configurator within your test case's ``setUp``, any test method -attached to the ``MyTest`` test case that directly or indirectly calls -:func:`pyramid.threadlocal.get_current_request` will receive the -request you passed into the ``begin`` method. Otherwise, during -testing, :func:`pyramid.threadlocal.get_current_request` will -return ``None``. We use a "dummy" request implementation supplied by -:class:`pyramid.testing.DummyRequest` because it's easier to -construct than a "real" :app:`Pyramid` request object. - -What? -~~~~~ - -Thread local data structures are always a bit confusing, especially when -they're used by frameworks. Sorry. So here's a rule of thumb: if you don't -*know* whether you're calling code that uses the -:func:`pyramid.threadlocal.get_current_registry` or -:func:`pyramid.threadlocal.get_current_request` functions, or you don't care -about any of this, but you still want to write test code, just always create -an autocommitting Configurator instance and call its ``begin`` method within -the ``setUp`` of a unit test, then subsequently call its ``end`` method in -the test's ``tearDown``. This won't really hurt anything if the application -you're testing does not call any ``get_current*`` function. - -.. index:: - single: pyramid.testing - single: Configurator testing API - -Using the ``Configurator`` and ``pyramid.testing`` APIs in Unit Tests ------------------------------------------------------------------------- - -The ``Configurator`` API and the ``pyramid.testing`` module -provide a number of functions which can be used during unit testing. -These functions make :term:`configuration declaration` calls to the -current :term:`application registry`, but typically register a "stub" -or "dummy" feature in place of the "real" feature that the code would -call if it was being run normally. - -For example, let's imagine you want to unit test a :app:`Pyramid` -view function. - -.. code-block:: python - :linenos: - - def view_fn(request): - from pyramid.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 startup code or using the testing API, an attempt -to run this view function in a unit test will result in an error. -When a :app:`Pyramid` application starts normally, it will populate -a :term:`application registry` using :term:`configuration declaration` -calls made against a :term:`Configurator` (sometimes deferring to the -application's ``configure.zcml`` :term:`ZCML` file via ``load_zcml``). -But if this application registry is not created and populated -(e.g. with an :meth:`pyramid.config.Configurator.add_view` -:term:`configuration declaration` or ``view`` declarations in -:term:`ZCML`), like when you invoke application code via a unit test, -:app:`Pyramid` API functions will tend to fail. - -The testing API provided by :app:`Pyramid` allows you to simulate -various application registry registrations for use under a unit -testing framework without needing to invoke the actual application -configuration implied by its ``run.py``. For example, if you wanted -to test the above ``view_fn`` (assuming it lived in the package named -``my.package``), you could write a :class:`unittest.TestCase` that -used the testing API. - -.. code-block:: python - :linenos: - - import unittest - from pyramid.config import Configurator - from pyramid import testing - - class MyTest(unittest.TestCase): - def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() - - def tearDown(self): - self.config.end() - - def test_view_fn_not_submitted(self): - from my.package import view_fn - renderer = self.config.testing_add_renderer('templates/show.pt') - request = testing.DummyRequest() - response = view_fn(request) - renderer.assert_(say='Hello') - - def test_view_fn_submitted(self): - from my.package import view_fn - renderer = self.config.testing_add_renderer( - 'templates/submitted.pt') - request = testing.DummyRequest() - request.params['say'] = 'Yo' - response = view_fn(request) - renderer.assert_(say='Yo') - -In the above example, we create a ``MyTest`` test case that inherits -from :mod:`unittest.TestCase`. If it's in our :app:`Pyramid` -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 -:meth:`pyramid.config.Configurator.testing_add_renderer` -method; this method returns a -:class:`pyramid.testing.DummyTemplateRenderer` instance which we -hang on to for later. - -We then create a :class:`pyramid.testing.DummyRequest` object which -simulates a WebOb request object API. A -:class:`pyramid.testing.DummyRequest` is a request object that -requires less setup than a "real" :app:`Pyramid` request. We call -the function being tested with the manufactured request. When the -function is called, -:func:`pyramid.chameleon_zpt.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 -:func:`pyramid.chameleon_zpt.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 -:exc:`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 -:meth:`pyramid.config.Configurator.begin` method in its -``setUp`` method and the ``end`` method of the same in its -``tearDown`` method. If you use any of the -:class:`pyramid.config.Configurator` APIs during testing, be -sure to use this pattern in your test case's ``setUp`` and -``tearDown``; these methods make sure you're using a "fresh" -:term:`application registry` per test run. - -See the :ref:`testing_module` chapter for the entire :app:`Pyramid` --specific testing API. This chapter describes APIs for registering a -security policy, registering resources at paths, registering event -listeners, registering views and view permissions, and classes -representing "dummy" implementations of a request and a resource. - -See also the various methods of the :term:`Configurator` documented in -:ref:`configuration_module` that begin with the ``testing_`` prefix. - -.. index:: - single: integration tests - -.. _integration_tests: - -Creating Integration Tests --------------------------- - -In :app:`Pyramid`, 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 :app:`Pyramid`, integration test, the test logic tests the -functionality of some code *and* its integration with the rest of the -:app:`Pyramid` framework. - -In :app:`Pyramid` applications that use :term:`ZCML`, you can -create an integration test by *loading its ZCML* in the test's setup -code. This causes the entire :app:`Pyramid` 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 :app:`Pyramid`. - -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 pyramid.config import Configurator - from pyramid 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 pyramid itself). - """ - import myapp - self.config = Configurator(package=myapp, autocommit=True) - self.config.begin() - self.config.load_zcml('myapp:configure.zcml') - - def tearDown(self): - """ Clear out the application registry """ - self.config.end() - - def test_my_view(self): - from myapp.views import my_view - request = testing.DummyRequest() - result = my_view(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 :class:`pyramid.config.Configurator` API to set up -the right "mock" registrations rather than creating an integration -test. Unit tests will run faster (because they do less for each test) -and the result of a unit test is usually easier to make assertions -about. - - diff --git a/pyramid/testing.py b/pyramid/testing.py index 4bcda906b..61bb1843a 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -635,7 +635,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True): ``Configurator`` are preferred to using ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown``. See - :ref:`unittesting_chapter` for more information. + :ref:`testing_chapter` for more information. """ manager.clear() if registry is None: @@ -681,7 +681,7 @@ def tearDown(unhook_zca=True): ``Configurator`` are preferred to using ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown``. See - :ref:`unittesting_chapter` for more information. + :ref:`testing_chapter` for more information. """ if unhook_zca: |
