1.4. Writing unit tests¶
HORTON uses the Nosetests program to run all the unit tests. The goal of a unit test is to check whether as small piece of code works as expected.
1.4.1. Running the tests¶
The tests are run as follows (including preparation steps):
toony@poony ~/.../horton:master> ./cleanfiles.sh toony@poony ~/.../horton:master> ./setup.py build_ext -i toony@poony ~/.../horton:master> nosetests -v
This will run the tests with the version of HORTON in the source tree, i.e. not
the one that is installed with
python setup.py install. If you have not
.py files, the script
./cleanfiles.sh can be skipped. If you
did not change any low-level code,
./setup.py build_ext -i can be skipped.
When working on a specific part of the code, it is often convenient to limit the
number of tests that are checked. The following runs only the tests in
toony@poony ~/.../horton:master> nosetests -v horton/test/test_cell.py
Within one file, you can also select one test function:
toony@poony ~/.../horton:master> nosetests -v horton/test/test_cell.py:test_from_parameters3
1.4.2. Writing new tests¶
All tests in HORTON are located in the directories
horton/*/test. All module files containing tests have a filename that starts
test_. In these modules, all functions with a name that starts with
test_ are picked up by Nosetests. Tests that do not follow this convention
are simply ignored.
The basic structure of a test is as follows:
def test_sum(): a = 1 b = 2 assert a+b == 3
HORTON currently contains many examples that can be used as a starting point for new tests. The easiest way to write new tests is to just copy an existing test (similar to what you have in mind) and start modifying it.
Most test packages in
horton contain a
common module that contains useful
functions that facilitate the development of tests. An important example is the
check_delta function to test if analytical derivatives are properly
implemented. This is a simple example:
import numpy as np from horton.common import check_delta def test_quadratic(): # a vector function that computes the squared norm divided by two def fun(x): return np.dot(x, x)/2 # the gradient of that vector function def deriv(x): return x # the dimension of the vector x ndim = 5 # the number of small displacements used in the test ndisp = 100 # a reference point used for the test of the analytical derivatives x0 = np.random.uniform(-1, 1, ndim) # the small displacements, i.e. each row is one (small) relative vector dxs = np.random.uniform(1e-5, 1e5, (ndisp, ndim)) check_delta(fun, deriv, x0, dxs)
1.4.3. Writing tests that need a temporary directory¶
A context manager is implemented in
horton.test.common to simplify tests
that need a temporary working directory. It can be used as follows:
from horton.test.common import tmpdir def test_something(): with tmpdir('horton.somemodule.test.test_something') as dn: # dn is a string with the temporary directory name. # put here the part of the test that operates in the temporary directory.
On most systems, this temporary directory is a subdirectory of
'horton.somemodule.test.test_something' will occur in the directory
name, such that it can be easily recognized if needed.
1.4.4. Writing tests that use random numbers¶
Tests that make use of random numbers can be problematic when they only fail sometimes for very specific and rare values of the random numbers. To avoid issues with such corner cases, one must fix the random seed in the tests as follows:
from horton.test.common import numpy_seed def test_foo(): # Some deterministic test code here. # ... # The part of test that uses random numbers should be repeated several times # with different random numbers to make use of the randomness. for irep in xrange(100): # Fix the seed differently but determinstically at each repetition. with numpy_seed(irep): # Test code that uses numpy.random should go here.
horton/grid/test/test_cubic_spline.py is a
realistic example that properly uses random numbers.