Nose enhances
unittest
by providing test fixtures at the package and module levels. The package setup
function is run before any of the tests in any of the modules in a package, while the teardown
function is run after all of the tests in all of the modules in the package have completed. Similarly, the module setup
function is run before any of the tests in a given module have been executed, and the module teardown
function is executed after all of the tests in the module have been executed.
We're going to build a test module with a module-level fixture. In the fixture, we'll replace the datetime.date.today
function, which normally returns an object representing the current date. We want it to return a specific value, so that our tests can know what to expect. Perform the following steps:
tests
.tests
directory, create a file called module_fixture_tests.py
containing the following code:from unittest import TestCase from unittest.mock import patch, Mock from datetime import date fake_date = Mock() fake_date.today = Mock(return_value = date(year = 2014, month = 6, day = 12)) patch_date = patch('module_fixture_tests.date', fake_date) def setup(): patch_date.start() def teardown(): patch_date.stop() class first_tests(TestCase): def test_year(self): self.assertEqual(date.today().year, 2014) def test_month(self): self.assertEqual(date.today().month, 6) def test_day(self): self.assertEqual(date.today().day, 12) class second_tests(TestCase): def test_isoformat(self): self.assertEqual(date.today().isoformat(), '2014-06-12')
TestCase
classes in this module. Using pure unittest
, we'd have to duplicate the fixture code in each of these classes. Nose lets us write it once and use it in both the places.python -m nose
.tests
as a directory that might contain tests (because of the directory name), find the module_fixtures_tests.py
file, run the setup
function, run all of the tests, and then run the teardown
function. There won't be much to see, though, aside from a simple report of how many tests passed.You might have noticed yet another way of using unittest.mock.patch
in the previous example. In addition to being usable as a decorator or a context manager, you can also use the patch
function as a constructor, and call start
and stop
on the object it returns. Of all the ways you can use the patch
function, this is the one to avoid in most cases, because this requires you to be careful to remember to call the stop
function. The preceding code would have been better using patch_date
as a class decorator on each of the TestCase
classes, except that the point here was to demonstrate what module-level fixtures look like.
Normally, rather than creating mock objects, setup
and teardown
will do things such as handle, create, and destroy temporary files, or so on.
We can save ourselves some time and effort by using a second layer of test fixtures that wrap around the entire test modules instead of single test methods. By doing this, we save ourselves from duplicating the fixture code inside every test class in the module; but this comes with a cost. The setup
and teardown
functions aren't run before and after each test, as normal test fixtures are. Instead, all of the tests in the module happen between a single module-level setup/teardown pair, which means that, if a test does something that affects the environment created by the setup
function, it won't be undone before the next test runs. In other words, the isolation of tests is not guaranteed with respect to the environment created by a module-level fixture.
Now, we're going to create a fixture that wraps around all the test modules in an entire package. Perform the following steps:
__init__.py
to the tests
directory that we created in the last practice section. (That's two underscores, the word init
and two more underscores). The presence of this file tells Python that the directory is a package.module_fixture_tests.py
, change:patch_date = patch('module_fixture_tests.date', fake_date)
with the following:
patch_date = patch('tests.module_fixture_tests.date', fake_date)
__init__.py
in the tests
directory:from os import unlink def setup(): with open('test.tmp', 'w') as f: f.write('This is a test file.') def teardown(): unlink('test.tmp')
package_fixtures_tests.py
to the tests
directory, with the following contents:from unittest import TestCase from glob import glob class check_file_exists(TestCase): def test_glob(self): self.assertIn('test.tmp', glob('*.tmp'))
test_glob
function can't succeed unless test.tmp
exists. Since this file is created in the package setup and destroyed in the package teardown (and it no longer exists), we know that the setup was run before the test, and teardown was run after the test. If we added a test to module_fixture_tests.py
that depended on test.tmp
, they too would pass, because the setup
function is called before any test in the package, and teardown
is called after every test in the package has run.We worked with yet another layer of test fixture, this time wrapping around all of the test modules in the tests
directory. As you can see from looking at the code we just wrote, the environment created by the package-level test fixture is available in every test in every module in the package.
Like module-level test fixtures, package-level test fixtures can be a big labor-saving shortcut, but they don't provide you with the protection against communication between tests that real test-level fixtures do.
Why did we change 'module_fixture_tests.date'
into 'tests.module_fixture_tests.date'
when we added the package-level fixture? Well, when we added __init__.py
to the tests
directory, in Python's view, we changed that directory into a Python package. As a Python package, its name is part of the absolute name of any variable inside it, which indirectly includes our imported date
class. We have to pass an absolute variable name to patch, so we have to start with the containing package name.
18.188.218.157