Running Initialization Code Before the Test RunΒΆ

Many applications, especially those using web frameworks like Pylons or Django, can’t be tested without first being configured or otherwise initialized. Plugins can fulfill this requirement by implementing begin().

In this example, we’ll use a very simple example: a widget class that can’t be tested without a configuration.

Here’s the widget class. It’s configured at the class or instance level by setting the cfg attribute to a dictionary.

>>> class ConfigurableWidget(object):
...     cfg = None
...     def can_frobnicate(self):
...         return self.cfg.get('can_frobnicate', True)
...     def likes_cheese(self):
...         return self.cfg.get('likes_cheese', True)

The tests verify that the widget’s methods can be called without raising any exceptions.

>>> import unittest
>>> class TestConfigurableWidget(unittest.TestCase):
...     longMessage = False
...     def setUp(self):
...         self.widget = ConfigurableWidget()
...     def test_can_frobnicate(self):
...         """Widgets can frobnicate (or not)"""
...         self.widget.can_frobnicate()
...     def test_likes_cheese(self):
...         """Widgets might like cheese"""
...         self.widget.likes_cheese()
...     def shortDescription(self): # 2.7 compat
...         try:
...             doc = self._testMethodDoc
...         except AttributeError:
...             # 2.4 compat
...             doc = self._TestCase__testMethodDoc
...         return doc and doc.split("\n")[0].strip() or None

The tests are bundled into a suite that we can pass to the test runner.

>>> def suite():
...     return unittest.TestSuite([
...         TestConfigurableWidget('test_can_frobnicate'),
...         TestConfigurableWidget('test_likes_cheese')])

When we run tests without first configuring the ConfigurableWidget, the tests fail.

Note

The function nose.plugins.plugintest.run() reformats test result output to remove timings, which will vary from run to run, and redirects the output to stdout.

>>> from nose.plugins.plugintest import run_buffered as run
>>> argv = [__file__, '-v']
>>> run(argv=argv, suite=suite())  
Widgets can frobnicate (or not) ... ERROR
Widgets might like cheese ... ERROR

======================================================================
ERROR: Widgets can frobnicate (or not)
----------------------------------------------------------------------
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'get'

======================================================================
ERROR: Widgets might like cheese
----------------------------------------------------------------------
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'get'

----------------------------------------------------------------------
Ran 2 tests in ...s

FAILED (errors=2)

To configure the widget system before running tests, write a plugin that implements begin() and initializes the system with a hard-coded configuration. (Later, we’ll write a better plugin that accepts a command-line argument specifying the configuration file.)

>>> from nose.plugins import Plugin
>>> class ConfiguringPlugin(Plugin):
...     enabled = True
...     def configure(self, options, conf):
...         pass # always on
...     def begin(self):
...         ConfigurableWidget.cfg = {}

Now configure and execute a new test run using the plugin, which will inject the hard-coded configuration.

>>> run(argv=argv, suite=suite(),
...     plugins=[ConfiguringPlugin()])  
Widgets can frobnicate (or not) ... ok
Widgets might like cheese ... ok

----------------------------------------------------------------------
Ran 2 tests in ...s

OK

This time the tests pass, because the widget class is configured.

But the ConfiguringPlugin is pretty lame – the configuration it installs is hard coded. A better plugin would allow the user to specify a configuration file on the command line:

>>> class BetterConfiguringPlugin(Plugin):
...     def options(self, parser, env={}):
...         parser.add_option('--widget-config', action='store',
...                           dest='widget_config', default=None,
...                           help='Specify path to widget config file')
...     def configure(self, options, conf):
...         if options.widget_config:
...             self.load_config(options.widget_config)
...             self.enabled = True
...     def begin(self):
...         ConfigurableWidget.cfg = self.cfg
...     def load_config(self, path):
...         from ConfigParser import ConfigParser
...         p = ConfigParser()
...         p.read([path])
...         self.cfg = dict(p.items('DEFAULT'))

To use the plugin, we need a config file.

>>> import os
>>> cfg_path = os.path.join(os.path.dirname(__file__), 'example.cfg')
>>> cfg_file = open(cfg_path, 'w')
>>> bytes = cfg_file.write("""\
... [DEFAULT]
... can_frobnicate = 1
... likes_cheese = 0
... """)
>>> cfg_file.close()

Now we can execute a test run using that configuration, after first resetting the widget system to an unconfigured state.

>>> ConfigurableWidget.cfg = None
>>> argv = [__file__, '-v', '--widget-config', cfg_path]
>>> run(argv=argv, suite=suite(),
...     plugins=[BetterConfiguringPlugin()]) 
Widgets can frobnicate (or not) ... ok
Widgets might like cheese ... ok

----------------------------------------------------------------------
Ran 2 tests in ...s

OK