Chapter 9. Other Tools and Techniques

We've covered the core elements of testing in Python, but there are a number of peripheral methods and tools that will make your life easier. In this chapter, we're going to go through several of them in brief.

In this chapter, we're going to:

  • Discuss code coverage and how to get a code coverage report from Nose
  • Discuss continuous integration and Buildbot
  • Learn how to integrate automated testing with Git, Mercurial, Bazaar, and Subversion

Code coverage

Tests tell you when the code you're testing doesn't work the way you thought it would, but they don't tell you a thing about the code you're not testing. They don't even tell you that the code you're not testing isn't being tested.

Code coverage is a technique to address that shortcoming. A code coverage tool watches while your tests are running, and keeps track of which lines of code are (and aren't) executed. After the tests have run, the tool will give you a report describing how well your tests cover the whole body of code.

It's desirable to have the coverage approach 100 percent, as you probably figured out already. Be careful not to focus on the coverage number too intensely, though, because it can be a bit misleading. Even if your tests execute every line of code in the program, they can easily not test everything that needs to be tested. This means that you can't take 100 percent coverage as certain proof that your tests are complete. On the other hand, there are times when some code really, truly doesn't need to be covered by the tests—some debugging support code, for example, or code generated by a user interface builder—and so less than 100 percent coverage is often completely acceptable.

Code coverage is a tool to give you an insight into what your tests are doing, and what they might be overlooking. It's not the definition of a good test suite.

Installing coverage.py

We're going to be working with a module called coverage.py, which is—unsurprisingly—a code coverage tool for Python.

Since coverage.py isn't built in to Python, we're going to need to download and install it. You can download the latest version from the Python Package Index at http://pypi.python.org/pypi/coverage, but it will probably be easier simply to type the following from the command line:

python3 -m pip install --user coverage

We're going to walk through the steps of using coverage.py here, but if you want more information you can find it on the coverage.py home page at http://nedbatchelder.com/code/coverage/.

Using coverage.py with Nose

We're going to create a little toy code module with tests, and then apply coverage.py to find out how much of the code the tests actually use.

Put the following test code into test_toy.py. There are several problems with these tests, which we'll discuss later, but they ought to run:

from unittest import TestCase
import toy

class test_global_function(TestCase):
    def test_positive(self):
        self.assertEqual(toy.global_function(3), 4)

    def test_negative(self):
        self.assertEqual(toy.global_function(-3), -2)

    def test_large(self):
        self.assertEqual(toy.global_function(2**13), 2**13 + 1)

class test_example_class(TestCase):
    def test_timestwo(self):
        example = toy.Example(5)
        self.assertEqual(example.timestwo(), 10)

What we have here is a couple of TestCase classes with some very basic tests in them. These tests wouldn't be of much use in a real-world situation, but all we need them for is to illustrate how the code coverage tool works.

Put the following code into toy.py. Notice the if __name__ == '__main__' clause at the bottom; we haven't dealt with one of these in a while, so I'll remind you that the code inside that block runs doctest if we were to run the module with Python toy.py:

def global_function(x):
    r"""
    >>> global_function(5)
    6
    """
    return x + 1

class Example:
    def __init__(self, param):
        self.param = param

    def timestwo(self):
        return self.param * 2

    def __repr__(self):
        return 'Example({!r})'.format(self.param)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Here, we have the code that satisfies the tests we just wrote. Like the tests themselves, this code wouldn't be of much use, but it serves as an illustration.

Go ahead and run Nose. It should find the tests, run them, and report that all is well. The problem is that some of the code isn't ever tested. Let's run the tests again, only this time we'll tell Nose to use coverage.py to measure coverage while it's running the tests:

python -m nose --with-coverage --cover-erase

This should give us an error report that looks like this:

.....
Name    Stmts   Miss  Cover   Missing
-------------------------------------
toy        12      3    75%   16, 19-20
----------------------------------------------------------------------
Ran 5 tests in 0.053s

OK

The dots at the top indicate passing tests, and the OK at the bottom says that the testing procedure worked as expected, but the part in between is new. That's our coverage report. Apparently, our tests only cover three quarters of our code: out of the 12 statement lines in toy.py, three didn't get executed. These lines were 16 and 19 through 20.

Tip

The range 19-20 isn't any more useful than writing 19, 20 would have been, but larger contiguous groups of lines are reported in the same way. That's a lot easier to parse, visually, than a soup of separate line numbers would be, especially when it's a range like 361-947.

When we passed --with-coverage and --cover-erase as command-line parameters to Nose, what did they do? Well, --with-coverage is pretty straightforward: it told Nose to look for coverage.py and to use it while the tests get executed. That's just what we wanted. The second parameter, --cover-erase, tells Nose to forget about any coverage information that was acquired during previous runs. By default, coverage information is aggregated across all of the uses of coverage.py. This allows you to run a set of tests using different testing frameworks or mechanisms, and then check the cumulative coverage. You still want to erase the data from previous test runs at the beginning of this process, though, and the --cover-erase command line is how you tell Nose to tell coverage.py that you're starting a new.

Tip

Nose, being an integrated testing system, often renders the need to aggregate coverage information that is negligible. You'll almost always want --cover-erase when you invoke Nose with coverage enabled, so you should consider adding cover-erase=1 to your Nose configuration file, as discussed in previous chapters.

Another useful Nose command-line option is --cover-package=PACKAGE, which limits the coverage report to the specific package you're interested in. It didn't show up in our toy because we didn't import anything, but normally the coverage report includes every module or package that has code executed while your tests are running. The percentage of the standard library that is covered by your tests is usually not useful information. It can be convenient to limit the report to the things you actually want to know.

So, back to our coverage report. The missing lines were line 16 and lines 19 through 20. Looking back at our code, we see that line 16 is the __repr__ method. We really should have tested that, so the coverage check has revealed a hole in our tests that we should fix. Lines 19 and 20 are just code to run doctest, though. They're not something that we ought to be using under production conditions, so we can just ignore that coverage hole.

Code coverage can't detect problems with the tests themselves, in most cases. In the previous test code, the test for the timestwo method violates the isolation of units and invokes two different methods of example_class. Since one of the methods is the constructor, this might be acceptable, but the coverage checker isn't in a position to even see that there might be a problem. All it saw was more lines of code being covered. That's not a problem—it's how a coverage checker ought to work—but it's something to keep in mind. Coverage is useful, but high coverage doesn't equal good tests.

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

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