An Introduction to Python Testing with Nose
This article delves into the rudimentary aspects of executing a Python test suite using Nose. It focuses on practical aspects for quickly developing a test suite and reviewing its results.
Back when I first started writing Python code, the common method for writing and running a series of unit tests was to utilize the standard Unittest
module for making assertions and developing your custom runner. The runner was tasked with tracking assertions and generating a human-readable report, albeit without standardization. Due to this, many older open-source Python projects still use these types of runners, which are harder to maintain. These runners have evolved over time to generate better reports, add coverage, inspect various file types, and more.
However, the Python testing landscape has significantly transformed in recent years. The standard Python module has grown robust, with numerous assertion cases being added and a vast collection of utilities continuously expanding.
During this time, Nose has emerged as the de-facto standard for running unit tests. It includes an extensive base library for assertions/utils to extend your unit test classes, and report generation has now become standardized, with Xunit reigning supreme in output formats.
I typically run my tests on a pre-commit basis. This way, before pushing a change into the repo, my test base runs to check if any modifications might break existing code. Now, I generally use a development branch, and before migrating changes to the master branch, I utilize a Jenkins or Travis server to continually run my test suites and verify their success.
Utilizing Nose
As I mentioned earlier, nosetests
is the default standard in every project I work on. Here, I’ll share my standard configuration and the methods of integrating these components.
Here’s a simple example of a testable class:
|
|
In every test suite I’ve written, I’ve used the nose.tools
collection because it provides a set of convenience functions that make writing tests easier and does not depend on the unittest
module.
Here’s a basic test suite for the above class:
|
|
To run this test suite using Nose, execute the nosetests
command from the command line. My usual command is:
|
|
This command provides verbose output and will halt the test runner if any test fails. You can review other command-line options here.
In the example case, Nose’s output will be something like:
|
|
Two crucial aspects of running Nose over any other runner are the output format and coverage options. By default, you can specify the `–with-coverage
` option, and Nose will output the missing and covered statements in your code.
To produce the standard XUnit output file format (for compatibility with CI environments like Jenkins or Travis), pass the following command:
|
|
This command generates a nosetests.xml
file in the current working directory.
To produce a human-readable report from this XUnit output, I’ve developed an XSLT template with filters and other useful features, available on Github Nosetests-XUnit-XSLT.
To format the report, run the following commands:
|
|
By default, nosetests
tries to load a configuration file (nose.cfg
) from the current working directory. Here’s my default configuration file:
|
|
To integrate nosetests
into a Jenkins setup, you will need to add support for XUnit, which can be achieved using the Jenkins XUnit plugin.
Auto-generating tests
Another great feature of Nose is the test generator support. Test generators let you enhance your test base by yielding test cases with different parameters, thus increasing both the number and quality of tests.
Here’s a good example for our basic class using generators:
|
|
This generates ten new test cases, contributing to a more robust code base.
In this article, we’ve covered some basic aspects of the standard Python test suite. My hope is that you’ll dive deeper into Nose and start writing your own enhancements.
In the next article in this series, I will explain how to extend Nose to create your own test runner, how to run tests in parallel to accelerate the process, and also how to write useful Nose plugins to take full advantage of this fantastic Python testing tool. Stay tuned!