Examples

Preparing a Package

Zope projects often use zc.buildout along with distutils and setuptools to declare their dependencies from other packages and create locally executable scripts (including testing scripts). This step is explained in Activating pytest and zope.pytest in your project.

Here we concentrate on the main Python code, i.e. we leave out the setup.py and zc.buildout stuff for a while.

A simple Zope-geared package now could be look like this:

rootdir
 !
 +--mypkg/
      !
      +---__init__.py
      !
      +---app.py
      !
      +---interfaces.py
      !
      +---ftesting.zcml
      !
      +---configure.zcml
      !
      +---tests/
            !
            +----__init__.py
            !
            +----test_app.py

We prepared several such projects in the sources of zope.pytest (see sample_fixtures/ in zope.pytest‘s tests/ directory). There we have different versions of a package called mypkg (or mypkg2 or similar) which we will use here.

The important files contained in the mypkg package (beside the real test modules, changing with each sample) look like this:

app.py:

from zope.interface import implements
from mypkg.interfaces import ISampleApp

class SampleApp(object):
    implements(ISampleApp)

interfaces.py:

import zope.interface

class ISampleApp(zope.interface.Interface):
    """A sample application.
    """
    pass

configure.zcml:

<configure
   xmlns="http://namespaces.zope.org/zope"
   i18n_domain="mypkg"
   package="mypkg"
   >

</configure>

ftesting.zcml:

<configure
   xmlns="http://namespaces.zope.org/zope"
   >

  <include package="mypkg" file="configure.zcml" />

</configure>

Writing Simple Tests

For simple tests we do not need any special setup at all. Instead we can just put modules starting with test_ into some Python package and ask pytest to run the tests.

In our package we add the following, pretty plain test file:

tests/test_app.py:

from zope.interface.verify import verifyClass, verifyObject
from mypkg.app import SampleApp
from mypkg.interfaces import ISampleApp

def test_app_create():
    # Assure we can create instances of `SampleApp`
    app = SampleApp()
    assert app is not None

def test_app_class_iface():
    # Assure the class implements the declared interface
    assert verifyClass(ISampleApp, SampleApp)

def test_app_instance_iface():
    # Assure instances of the class provide the declared interface
    assert verifyObject(ISampleApp, SampleApp())

All tests do the usual plain pytest stuff: they are named starting with test_ so that pytest can find them. The second and third tests check whether the specified interfaces are implemented by the SampleApp class and instances thereof.

For plain zope.interface related tests we need no special setup.

Then, we run py.test with this package as argument. In real-world usage we would call bin/py.test or even py.test (if pytest is installed globally in your system Python) from the commandline:

>>> import pytest
>>> pytest.main(mypkg_dir) 
=============...=== test session starts ====...================
platform ... -- Python 2... -- pytest-...
collecting ...collected 3 items
<BLANKLINE>
.../mypkg/tests/test_app.py ...
<BLANKLINE>
=============...=== 3 passed in ... seconds ===...=============
0

Excellent! py.test found our tests and executed them.

Apparently we didn’t really need zope.pytest in this example, as there was no Zope specific code to test.

Making Use of ZCML

To make real use of zope.pytest we now want to test some ZCML registrations we can make in (you guessed it) ZCML files.

Imagine our project had a certain utility defined that looks like this:

app.py:

from zope.interface import implements
from mypkg2.interfaces import ISampleApp, IFoo

class SampleApp(object):
    implements(ISampleApp)

class FooUtility(object):
    implements(IFoo)

    def do_foo(self):
        return "Foo!"

The FooUtility can be registered via ZCML like this:

configure.zcml:

<configure
   xmlns="http://namespaces.zope.org/zope"
   package="mypkg2"
   >

  <include package="zope.component" file="meta.zcml" />
  <utility component=".app.FooUtility" 
	   provides=".interfaces.IFoo" 
	   name="foo utility" />

</configure>

To check whether the FooUtility was registered and is available we first have to configure the Zope Component Architecture (ZCA). zope.pytest here helps with the zope.pytest.configure() function. It is normally used inside a funcarg function you have to write yourself.

We use this approach in a new test module where we want to test the FooUtility. The new test module is called test_foo.

tests/test_foo.py:

import mypkg2
from zope.component import queryUtility
from mypkg2.interfaces import IFoo
from zope.pytest import configure

def pytest_funcarg__config_mypkg2(request):
    return configure(request, mypkg2, 'ftesting.zcml')

def test_get_utility(config_mypkg2):
    util = queryUtility(IFoo, name='foo utility', default=None)
    assert util is not None

def test_dofoo_utility(config_mypkg2):
    util = queryUtility(IFoo, name='foo utility', default=None)
    assert util().do_foo() == 'Foo!'

Here the pytest_funcarg__config function provides a config argument for arbitrary test functions you want to write. It can be deployed by writing test functions that require an argument named config as shown in the test_foo_utility function.

If we had named the pytest_funcarg__ function "pytest_funcarg__manfred", we had to use an argument called manfred instead of config with our test functions.

The configuration used here is based on the local ftesting.zcml file (which includes configure.zcml). We could easily write several other funcarg functions based on other ZCML files and decide for each test function, which configuratio we would like to pick for the respective test, based on the funcarg name.

The main point about the shown pytest_funcarg__ function is that it calls zope.pytest.configure() which injects setup and teardown calls into the test that are called automatically before/after your test. This way the given ZCML files are already parsed when the test_foo_utility() test starts and any registrations are cleared up afterwards. This is the reason, why the foo utility looked up in our test can actually be found.

Please note, that in the actual tests we make no use of the passed config parameter. We only request it to inject the necessary setup and teardown functionality.

When run, all tests pass:

>>> import pytest
>>> pytest.main(mypkg_dir)
=============...=== test session starts ====...================
platform ... -- Python 2... -- pytest-...
collecting ...collected 5 items
<BLANKLINE>
.../mypkg2/tests/test_app.py ...
.../mypkg2/tests/test_foo.py ..
<BLANKLINE>
=============...=== 5 passed in ... seconds ===...=============
0

Both foo tests would fail without pytest_funcarg__config preparing the tests.

Functional Testing: Browsing Objects

The most interesting point about functional testing might be to check Zope-generated output, i.e. browser pages or similar. This is, what normally is referred to as ‘functional testing’.

This task normally needs much more setup where zope.pytest can come to help to minimize the efforts dramatically.

To show this we add a view for the SampleApp class we defined in app.py above. We add a new module browser.py in our mypkg package with the following contents:

New module browser.py:

from zope.publisher.browser import BrowserPage

class SampleAppView(BrowserPage):

    def __call__(self):
        self.request.response.setHeader('Content-Type', 'text/plain')
        return u'Hello from SampleAppView!'

This is a simple browser page that sets the content type of any HTTP response and returns a simple string as content.

However, to make content browsable we need more registrations. In configure.zcml we register the main components as above but this time including also the new browser page:

configure.zcml:

<configure
   xmlns="http://namespaces.zope.org/zope"
   xmlns:browser="http://namespaces.zope.org/browser"
   >

  <!-- Basic configuration. -->
   <include package="zope.component" file="meta.zcml" />
   <include package="zope.component" />

   <!-- Those next ZCML includes are required by zope.app.appsetup. -->
   <include package="zope.security" file="meta.zcml" />
   <include package="zope.security" />

   <include package="zope.location" />
   <include package="zope.traversing" />
   <include package="zope.container" />
   <include package="zope.site" />

   <include package="zope.app.appsetup" />

   <utility 
       component=".app.FooUtility"
       provides=".interfaces.IFoo"
       name="foo utility"
       />

   <browser:page
       for=".interfaces.ISampleApp"
       name="index.html"
       class=".browser.SampleAppView"
       permission="zope.View"
       />

</configure>

In ftesting.zcml we do all the registration stuff that is normally done in the site.zcml.

ftesting.zcml:

<configure
   xmlns="http://namespaces.zope.org/zope"
   xmlns:browser="http://namespaces.zope.org/browser"
   package="mypkg3"
   >

  <!-- This file is the equivalent of site.zcml and it is -->
  <!-- used for functional testing setup -->
  <include package="zope.component" file="meta.zcml" />
  <include package="zope.security" file="meta.zcml" />
  <include package="zope.publisher" file="meta.zcml" />
  <include package="zope.browserpage" file="meta.zcml" />
  <include package="zope.app.publication" file="meta.zcml" />
  <include package="zope.securitypolicy" file="meta.zcml" />

  <include package="zope.app.wsgi" />
  <include package="zope.app.zcmlfiles" />

  <include package="mypkg3" file="configure.zcml" />

  <securityPolicy
      component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy" />

  <role id="zope.Manager" title="Site Manager" />

  <grantAll role="zope.Manager" />

  <!-- Principals -->
  <unauthenticatedPrincipal
      id="zope.anybody"
      title="Unauthenticated User" />
  <grant
      permission="zope.View"
      principal="zope.anybody" />

  <!-- Principal that tests generally run as -->
  <principal
      id="zope.mgr"
      title="Manager"
      login="mgr"
      password="mgrpw" />

  <grant role="zope.Manager" principal="zope.mgr" />

</configure>

Now we are ready to add another test module that checks the new view defined in the browser module:

tests/test_browser.py:

import pytest
import mypkg3
from webob import Request
from zope.component import getMultiAdapter
from zope.publisher.browser import TestRequest
from zope.pytest import configure, create_app
from mypkg3.app import SampleApp


def pytest_funcarg__apps(request):
    app = SampleApp()
    return app, create_app(request, app)

def pytest_funcarg__config(request):
    return configure(request, mypkg3, 'ftesting.zcml')

def test_view_sampleapp(config, apps):
    zope_app, wsgi_app = apps
    view = getMultiAdapter(
        (zope_app, TestRequest()), name="index.html")
    rendered_view = view()
    assert view() == u'Hello from SampleAppView!'

def test_browser(config, apps):
    zope_app, wsgi_app = apps
    http_request = Request.blank('http://localhost/test/index.html')
    response = http_request.get_response(wsgi_app)
    assert response.body == 'Hello from SampleAppView!'
    assert response.status == "200 Ok"

@pytest.mark.xfail("sys.version_info < (2,6)")
def test_infrae_browser(config, apps):
    # Late import. This import will fail with Python < 2.6
    from infrae.testbrowser.browser import Browser
    zope_app, wsgi_app = apps
    browser = Browser(wsgi_app)
    browser.open('http://localhost/test/index.html')
    assert browser.contents == 'Hello from SampleAppView!'
    assert browser.status == '200 Ok'

Here we have three tests. While the first one checks only whether the component architecture can generate the new view in general, with the latter ones (test_browser and test_infrae_browser) we access the whole machinery via a real WSGI application. This gives us a sufficient level of abstraction for real functional testing.

Please note, that we make no strong assumptions about the existence of some ZODB working in background or similar. While in fact here a ZODB is working, the tests do not reflect this. We therefore can deploy non-Zope-specific packages like WebOb.

One of the main parts of this test module therefore is the funcarg function pytest_funcarg__apps that sets up a complete WSGI application and returns it together with a SampleApp object stored somewhere.

To do the complete setup pytest_funcarg__apps calls the zope.pytest function zope.pytest.create_app() with a SampleApp instance to be stored in the database. zope.pytest.create_app() stores this instance under the name test in the DB root and returns a ready-to-use WSGI application along with the SampleApp instance created.

In the first functional test (test_browser) we create and perform an HTTP request that is sent to the setup WSGI application and check the output returned by that request. Please note that we use http://localhost/test/index.html as URL. That’s because zope.pytest.create_app() stores our application under the name test in the DB and we registered the view on SampleApp objects under the name index.html.

The second functional test (test_infrae_browser) does nearly the same but this time deploying a faked browser provided by the infrae.testbrowser package. The latter is well prepared for simulations of browser sessions, virtual browser clicks, filling out HTML forms and much more you usually do with a browser. See the infrae.testbrowser documentation for details.

Usage of infrae.testbrowser, however, requires Python 2.6 at least. We therefore expect the respective test to fail if using older Python versions and mark this condition with a @pytest.mark.xfail decorator. Another great feature of py.test (see py.test skip and xfail mechanisms for details).

Finally, when run, all tests pass:

>>> import pytest
>>> pytest.main(mypkg_dir)
=============...=== test session starts ====...================
platform ... -- Python 2... -- pytest-...
collecting ...collected 8 items
<BLANKLINE>
.../mypkg3/tests/test_app.py ...
.../mypkg3/tests/test_browser.py ...
.../mypkg3/tests/test_foo.py ..
<BLANKLINE>
=============...=== ... passed... in ... seconds ===...=============
0

Writing and running doctests (unsupported)

zope.pytest currently has no specific support for doctesting. That means you can write and run regular doctests but there is currently no special hook or similar included for setting up Zope-geared environments/ZCML parsing and the like. We hope to provide doctest support in some future release.

Table Of Contents

Previous topic

Introduction

Next topic

py.test examples for Grok

This Page