Plone 3 Products Development Cookbook
上QQ阅读APP看书,第一时间看更新

Working with paster-generated test suites

Products created with paster already come with four different types of test suites ready to be used:

  • Unit test in doctest syntax
  • Unit test inside the method's docstring
  • Integration test in doctest syntax
  • Functional test in doctest syntax

The automatically generated tests module provides the necessary code to run the test cases.

Getting ready

We are going to use the same egg-structured product we created with paster in the Installation of the product recipe. If you didn't read that one, you can use the example code available for download on the Packt Publishing website.

How to do it...

Open the tests module in Products.poxContentTypes/Products/poxContentTypes to see four commented test suites:

...
def test_suite():
    return unittest.TestSuite([

        # Unit tests
        #doctestunit.DocFileSuite(
        #    'README.txt', package='Products.poxContentTypes',
        #    setUp=testing.setUp, tearDown=testing.tearDown),

        #doctestunit.DocTestSuite(
        #    module='Products.poxContentTypes.mymodule',
        #    setUp=testing.setUp, tearDown=testing.tearDown),


        # Integration tests that use PloneTestCase
        #ztc.ZopeDocFileSuite(
        #    'README.txt', package='Products.poxContentTypes',
        #    test_class=TestCase),

        #ztc.FunctionalDocFileSuite(
        #    'browser.txt', package='Products.poxContentTypes',
        #    test_class=TestCase),

        ])
...

How it works...

By uncommenting the testing code above and writing some test code, we will learn each of the above testing variations.

Unit test DocFileSuite

The first testing technique available in test_suite method is a unit test that should be defined in a README.txt file inside the package we are developing.

Tip

Writing tests in separate files (separate from source modules) is preferable when tests are long or they have so many cases that it would damage the readability of the code itself.

Uncomment the lines:

        doctestunit.DocFileSuite(
            'README.txt', package='Products.poxContentTypes',
            setUp=testing.setUp, tearDown=testing.tearDown),

Create a README.txt file inside the package. If you are using the code from the previous chapter, the file should be created in ./src/Products.poxContentTypes/Products/poxContentTypes/ in your buildout directory.

Then add the following lines:

>>> from Products.poxContentTypes import config
>>> from Products.poxContentTypes.content.XNewsItem import XNewsItem

>>> xni=XNewsItem('dummy') 

>>> xni
<XNewsItem at dummy>

>>> xni.countryVocabulary()
(('AQ', 'Antartiqa'), ('AR', 'Argentina'), ('BS', 'Bahamas'), ('BR', 'Brazil'), ('CM', 'Cameroon'), ('CL', 'Chile'))

Note

Look at the way this doctest is written. It's exactly the same way the Python interpreter should have behaved if we were testing our code. Every >>> preceded line is an input command and the other lines are the corresponding output.

After the initial imports, we create an instance of our XNewsItem class. Bear in mind that this is not the way objects are created in a Plone site. But because we don't want to test our class interacting in a Plone site but the countryVocabulary method (this is a unit test, not an integration one), we just need a proper class' instance. On creation, we add the dummy argument because all Archetypes objects need an id as an initialization parameter.

After that, we just call the method in question to check if its returned values are the expected ones.

Running tests

Now we have to run all the tests (just one for the time being) in the package.

Inside your instance folder run this command:

./bin/instance test -s Products.poxContentTypes

We use the -s option to specify the package we want to test. By default Zope seeks a tests module or package inside the packages to be tested.

Note

You can get more available options to use when testing by running this command:

./bin/instance test --help

When done, we'll get an output similar to the following screenshot:

Running tests

We could have written this unit test even if we hadn't known anything about Plone but plain Python: we have instantiated the class and executed a method.

Unit test DocTestSuite

The second testing technique available in test_suite method is, again, a unit test. But we won't create any external file this time; tests will be written in docstrings.

Note

Tests written as docstrings (that is, inside source modules) are preferable when testing simple pieces of code with few cases.

Uncomment the following code and replace mymodule with the module we are testing:

        doctestunit.DocTestSuite(
 module='Products.poxContentTypes.content.XNewsItem',
            setUp=testing.setUp, tearDown=testing.tearDown),

Now modify the countryVocabulary method's docstring by adding the same test as in the previous section. The file to be modified is XNewsItem.py in src/Products.poxContentTypes/Products/poxContentTypes/content.

    security.declarePublic('countryVocabulary')
    def countryVocabulary(self):
        """
        Returns a list of country codes and their names for the"country" field

        >>> from Products.poxContentTypes import config
        >>> from Products.poxContentTypes.content.XNewsItem importXNewsItem

        >>> xni=XNewsItem('dummy')    

        >>> xni
        <XNewsItem at dummy>

        >>> xni.countryVocabulary()
        (('AQ', 'Antartiqa'), ('AR', 'Argentina'), ('BS', 'Bahamas'),('BR', 'Brazil'), ('CM', 'Cameroon'), ('CL', 'Chile'))
        """        
        return (
                 ('AQ', 'Antartiqa'),
                 ('AR', 'Argentina'),
                 ('BS', 'Bahamas'), 
                 ('BR', 'Brazil'),
                 ('CM', 'Cameroon'),
                 ('CL', 'Chile')
                )

As you can see, comments can be inserted together with testing code. This is the way doctests meet the "doc" part of their name.

Now, again test the package by running:

./bin/instance test -s Products.poxContentTypes

You should get an output as shown in the following screenshot:

Unit test DocTestSuite

Note

You might have noticed that the only difference between this output and the one when we had one unit test (except for the obvious summary information at the end) is the number of dots between Running and Ran 2 tests...

There's one dot for every passing test.

Integration test ZopeDocFileSuite using PloneTestCase

The third testing technique available in test_suite method is an integration test. This means that we are going to test our class as a component of a bigger environment: a Plone site.

Uncomment the next group of lines and replace README.txt with INTEGRATION.txt: we'll create a new file instead of overwriting our first unit test.

        ztc.ZopeDocFileSuite(
            'INTEGRATION.txt', package='Products.poxContentTypes',
            test_class=TestCase),

Integration tests based on the PloneTestCase class set a whole Plone site on the fly. For this particular one, we'll need our product to be installed, so open the tests.py file and change the s etupPloneSite call after initial imports and replace it with the following lines of code:

# ptc.setupPloneSite()
ztc.installProduct('poxContentTypes')
ptc.setupPloneSite(products=['poxContentTypes',])

Then create a new INTEGRATION.txt file inside your package folder, which is ./src/Products.poxContentTypes/Products/poxContentTypes/ in your buildout folder.

>>> self.loginAsPortalOwner()
>>> portal.invokeFactory('XNewsItem', 'dummy')
'dummy'
>>> portal.dummy
<XNewsItem at /plone/dummy>

>>> portal.dummy.countryVocabulary()
(('AQ', 'Antartiqa'), ('AR', 'Argentina'), ('BS', 'Bahamas'), ('BR', 'Brazil'), ('CM', 'Cameroon'), ('CL', 'Chile'))

Spot the big difference with the previous unit tests regarding initialization of our objects. We are now using the in vokeFactory method in the portal object to create an instance of our XNewsItem class. Furthermore, we need special permissions to create content in a Plone portal, that's why we first loginAsPortalOwner().

We could have added a line like:

xni=XNewsItem(‘dummy’) 

This would use xni as in the previous examples. However, we wanted to emphasize the integration nature of this test by using the portal object repeatedly.

Note

If you have any iPython-related egg (ipython, ipdb, or iw.debug) installed in your instance, you would probably want to uninstall them by commenting their lines in the buildout.cfg file. iPython changes the way of printing into the standard output, so the expected output won't match the actual one.

Test the package again by running:

./bin/instance test -s Products.poxContentTypes

You should get an output like the following (we have removed several Zope warnings during instance start up):

Integration test ZopeDocFileSuite using PloneTestCase

Execution time now is much longer than in previous test runs. This is due to the recently added integration test because a new full Plone site is being automatically created.

Functional test

We'll cover how to create functional tests later on in this chapter. Yet, we can also run them with the paster-created test suite. Uncomment the following lines if you want to run a functional test defined in the browser.txt file:

        ztc.FunctionalDocFileSuite(
            'browser.txt', package='Products.poxContentTypes',
            test_class=TestCase),

See also

  • Installation of the product