(ユニットテスト)

はじめに

Unit tests are automated tests created by developer to ensure that the add-on product is intact in the current product configuration. Unit tests are regression tests and are designed to catch broken functionality over the code evolution.

Plone unit testing tutorial.

Running unit tests

For Plone and all registered add-ons - useful to check whether you have a good component set:

bin/instance test

For one package:

bin/instance test -s package.subpackage

For one test case:

bin/instance test -s package.subpackage -t TestCaseClassName

For two test cases:

bin/instance test -s package.subpackage -t TestClass1|TestClass2

To drop in pdb debugger for each failure:

bin/instance test -s package.subpackage -D

To exclude tests:

bin/instance test -s package.subpackage -t !test_name

To list tests that will be run:

bin/instance test -s package.subpackage --list-tests

警告

Test runner does not give an error if you supply invalid package and test case name. Instead it just simply doesn’t execute tests.

AttributeError: ‘module’ object has no attribute ‘test_suite’

If you get the above error message there are potential reasons

  • Having both tests.py and tests folder
  • Old version: Zope version X unit test framework was updated not to need anymore explicit test_suite declaration in test module. Instead, all classes subclasses TestCase are automatically picked. However, this change is backwards incompatible. http://wiki.zope.org/zope2/HowToRunZopeUnitTests

Test coverage

Zope test running can show how much your code is covered by automatic tests.

Unit tests and skin data

If your test code modifies skin registries you need to force skin data reload.

Example (self = unit test):

self._refreshSkinData()

Running add-on installers and extensions profiles for unit tests

By default, no add-on installers or extension profiles are installed.

You need to modify PloneTestCase.setupPloneSite() call in your base unit tests.

Simple example:

ptc.setupPloneSite(products=['namespace.yourproduct'])

Complex example:

ptc.setupPloneSite(products=['harvinaiset.app', 'TickingMachine'], extension_profiles=["harvinaiset.app:tests","harvinaiset.app:default"])

Tested package not found warning

Installers may fail without interrupting the test run. Monitor Zope start up messages. If you get error like:

Installing gomobiletheme.basic ... NOT FOUND

You might be missing this from your configure.zcml:

<five:registerPackage package="." initialize=".initialize" />

... or you have spelling error in your test setup code.

Setting log level in unit tests

Many components use debug output level and unit testing default output level is info. Import messages may go unnoticed during the unit test development.

Add this to your unit test code:

def enableDebugLog(self):
    """ Enable context.plone_log() output from Python scripts """
    import sys, logging
    from Products.CMFPlone.log import logger
    logger.root.setLevel(logging.DEBUG)
    logger.root.addHandler(logging.StreamHandler(sys.stdout))

HTTP request

Zope unit tests have faux HTTPRequest object set up.

You can access it:

self.portal.REQUEST # Faux HTTPRequest object

Grabbing emails

To debug outgoing email traffic you can create a dummy mailhost.

ノート

MailHost code has been changed in Plone 4. This instructions apply for Plone 3.

Example:

from zope.component import getUtility, getMultiAdapter, getSiteManager
from zope.component import getUtility, getMultiAdapter, getSiteManager

from Products.MailHost.interfaces import IMailHost
from Products.SecureMailHost.SecureMailHost import SecureMailHost
from Products.CMFCore.utils import getToolByName

class DummySecureMailHost(SecureMailHost):
    """ Grab outgoing emails """

    meta_type = 'Dummy secure Mail Host'
    def __init__(self, id):
        self.id = id

        # Use these two instance attributes to check what email has been sent
        self.sent = []
        self.mto = None

    def _send(self, mfrom, mto, messageText, debug=False):
        self.sent.append(messageText)
        self.mto = mto



 ...

 def afterSetUp(self):

        self.loginAsPortalOwner()
        sm = getSiteManager(self.portal)
        sm.unregisterUtility(provided=IMailHost)
        self.dummyMailHost = DummySecureMailHost('dMailhost')
        sm.manage_changeProperties({'email_from_address': 'moo@isthemasteofuniverse.com'})
        sm.registerUtility(self.dummyMailHost, IMailHost)

        # Set mail host for tools which use getToolByName() look up
        self.MailHost = self.dummyMailHost

        # Make sure that registration tool uses mail host mock
        rtool = getToolByName(self.portal, 'portal_registration')
        rtool.MailHost = self.dummyMailHost

 ....

 def test_xxx(self):

        # Reset outgoing emails
        self.dummyMailHost.sent = []

        # Do a workflow state change which should trigger content rule
        # sending out email
        self.workflow.doActionFor(member, "approve_by_sits")
        review_state = self.workflow.getInfoFor(member, 'review_state')
        self.assertEqual(review_state, "approved_by_sits")

        # Check that email has been sent
        self.assertEqual(len(self.dummyMailHost.sent), 1)

Unit testing and Zope component architecture

If you are dealing with low level Zope component architecture in your unit tests, there are some things to note. Global site manager doesn’t behave properly in the unit tests.

See discussion: http://n2.nabble.com/PTC-global-components-bug–tp3413057p3413057.html

ZCML

Below are examples how to run special ZCML snippets for your unit tests.

    import unittest

from base import PaymentProcessorTestCase


from Products.Five import zcml
from zope.configuration.exceptions import ConfigurationError


from getpaid.paymentprocessors.registry import paymentProcessorRegistry
configure_zcml = '''
<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:five="http://namespaces.zope.org/five"
    xmlns:paymentprocessors="http://namespaces.plonegetpaid.com/paymentprocessors"
    i18n_domain="foo">


    <paymentprocessors:registerProcessor
       name="dummy"
       processor="getpaid.paymentprocessors.tests.dummies.DummyProcessor"
       selection_view="getpaid.paymentprocessors.tests.dummies.DummyButton"
       thank_you_view="getpaid.paymentprocessors.tests.dummies.DummyThankYou"
       />

</configure>'''


bad_processor_zcml = '''
<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:five="http://namespaces.zope.org/five"
    xmlns:paymentprocessors="http://namespaces.plonegetpaid.com/paymentprocessors"
    i18n_domain="foo">


    <paymentprocessors:registerProcessor
       name="dummy"
       selection_view="getpaid.paymentprocessors.tests.dummies.DummyButton"
       thank_you_view="getpaid.paymentprocessors.tests.dummies.DummyThankYou"
       />


</configure>'''




class TestZCML(PaymentProcessorTestCase):
    """ Test ZCML directives """


    def test_register(self):
        """ Check that ZCML entry gets added to our processor registry """
        zcml.load_string(configure_zcml)


        # See that our processor got registered
        self.assertEqual(len(papaymentProcessorRegistryistry.items()), 1)


    def test_bad_processor(self):
        """ Check that ZCML entry which has bad processor declaration is caught """


        try:
            zcml.load_string(bad_processor_zcml)
            raise AssertionError("Should not be never reached")
        except ConfigurationError, e:
            pass