cement/doc/source/dev/testing.rst
2012-10-08 18:18:46 -05:00

133 lines
4.8 KiB
ReStructuredText

Unit Testing Your Application
=============================
Testing is an incredibly important part of the application development
process. The Cement framework provides some simple helpers and shortcuts for
executing basic tests of your application. Please note that
:ref:`cement.utils.test <cement.utils.test>` *does* require the 'nose' package. That said,
using :ref:`cement.utils.test <cement.utils.test>` is not required to test a Cement based
application. It is merely here for convenience, and is used by Cement when
performing its own Nose tests.
For more information on testing, please see the following:
* `UnitTest <http://docs.python.org/library/unittest.html>`_
* `Nose <http://nose.readthedocs.org/en/latest/>`_
* `Coverage <http://nedbatchelder.com/code/coverage/>`_
API Reference:
* :ref:`Cement Testing Utility <cement.utils.test>`
An Example Test Case
--------------------
The following outlines a basic test case using the cement.utils.test module.
.. code-block:: python
from cement.utils import test
from myapp.cli.main import MyApp
class MyTestCase(test.CementTestCase):
app_class = MyApp
def setUp(self):
super(MyTestCase, self).setUp()
# Clear existing hooks/handlers/etc
self.reset_backend()
# Create a default application for the test functions to use.
# Note that some tests make require you to perform this in the
# test function in order to alter functionality. That perfectly
# fine, this is only hear for convenience
self.app = MyApp(argv=[], config_files=[])
def test_myapp(self):
# Setup the application
self.app.setup()
# Perform basic assertion checks. You can do this anywhere in the
# test function, depending on what the assertion is checking.
self.ok(self.config.has_key('myapp', 'debug))
self.eq(self.config.get('myapp', 'debug'), False)
# Run the applicaion, if necessary
self.app.run()
# Test the last rendered output (if app.render was used)
data, output = app.get_last_rendered()
self.eq(data, {'foo':'bar'})
self.eq(output, 'some rendered output text)
# Close the application, again if necessary
self.app.close()
@test.raises(Exception)
def test_exception(self):
try:
# Perform tests that intentionally cause an exception. The
# test passes only if the exception is raised.
raise Exception('test')
except Exception as e:
# Do further checks to ensure the proper exception was raised
self.eq(e.args[0], 'Some Exception Message')
# Finally, call raise again which re-raises the exception that
# we just caught. This completes out test (to actually
# verify that the exception was raised)
raise
Cement Testing Caveats
----------------------
In general, testing Cement applications should be no different than testing
anything else in Python. That said, the following are some things to
keep in mind.
**Command Line Arguments**
Never rely on 'sys.argv' for command line arguments. The CementApp() class
accepts the 'argv' keyword argument allowing you to pass the arguments that
you would like to test for. Using sys.argv will cause issues with the
calling script (i.e. nosetests, etc) and other issues. Always pass 'argv' to
CementApp().
**Config Files**
It is recommended to always set your apps 'config_files' setting to an empty
list, or to something relative to your current working directory. Using
default config files settings while testing will introduce unexpected results.
For example, if a '~/myapp.conf' user configuration exists it can alter the
runtime of your application in a way that might cause tests to fail.
**Making Things Easy**
The easiest way to accomplish the above is by sub-classing your CementApp into
a special 'testing' version. For example:
.. code-block:: python
from cement.utils import test
from myapp.cli.main import MyApp
class MyTestApp(MyApp):
class Meta:
argv = []
config_files = []
class MyTestCase(test.CementTestCase):
app_class = MyTestApp
def test_myapp_default(self):
self.app.setup()
self.app.run()
self.app.close()
def test_myapp_foo(self):
self.app = MyTestApp(argv=['--foo', 'bar])
self.app.setup()
self.app.run()
self.app.close()