Nose and unittest tests

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.

Module fixture practice

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:

  1. Create a directory called tests.
  2. Within the 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')
  3. Notice that there are two 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.
  4. Go ahead and run the tests by moving to the directory that contains the tests directory and type python -m nose.
  5. Nose will recognize 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.

Package fixture practice

Now, we're going to create a fixture that wraps around all the test modules in an entire package. Perform the following steps:

  1. Add a new file called __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.
  2. In 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)
  3. Place the following code inside __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')

    Note

    It's fairly common that the __init__.py files are completely empty, but they're the canonical source for the package object; so that's where Nose looks for a package-level fixture.

  4. Add a new file called 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'))
  5. Go ahead and run the tests again. You won't see much output, but that just means the tests passed. Notice that the 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.

    Tip

    The glob module provides the ability to expand command-line - style wildcards into a list of filenames. The glob.glob function is one of several globbing functions available.

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.

Note

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.200.71