[Tutor] unittests

Steven D'Aprano steve at pearwood.info
Tue Apr 1 14:45:31 CEST 2014


On Tue, Apr 01, 2014 at 12:30:46PM +0100, Sydney Shall wrote:
> Another debutant!
> I am having trouble learning to use unittests.
> My question is;
>     In the example below, did you write the class 
> "SquareRootTests(unittest.TestCase):" ?


Yes, that unit test was written by Danny (I assume -- I suppose he might 
have copied it from somewhere else.)

>    Or do I find a set of them in the library?

There are a HUGE number of unit tests that come supplied with Python's 
source code, but they are used for testing Python itself. If your Python 
installation includes a "test" subdirectory, you can see the tests in 
there. They aren't reusable in your own code, except to read them and 
learn from them.

Instead, you write your own tests, to suit your own code, using the 
unittest module to do all the heavy-lifting.


>    And what is the significance of the name chosen 
> "self.assertAlmostEqual(square_root(3), 2.0) "?
>   I take it the first argument is calling my function with its argument 
> and the second argument is the correct answer?

Correct. This "assertAlmostEqual" test takes two numbers:

- first Python calls your square_root function with 3, and gets 
  some number which hopefully will be around 1.732;

- then Python takes the number 2.0;

- finally it calls the "assertAlmostEqual" test, which tests whether or 
  not 1.732 is "almost equal" to 2.0, according to whatever scheme it 
  uses to compare two numbers. (They don't.)

If they compared "almost equal", then that specific test would have 
counted as a passing test. But since they don't, then it counts as a 
test failure. The idea is that you collect all the test failures, and 
they represent bugs in your code that should be fixed.

Although they might be a bug in the test! In this case, Danny intends 
that as a deliberately buggy test -- 1.732... is *not* "approximately 
equal" to 2, at least not according to the rules of unittest.


> I am quite unclear how one proceeds to set up unittests.

Fortunately, most of the hard work is done for you by the unittest 
module. Suppose you have a simple module with one function and you want 
to test it:

# module pizza.py
def make_pizzas(number):
    return "made %d pizzas" % number


That's pretty simple, right? So simple that you can almost look at it 
and see that it works correctly.

make_pizza(5) 
=> returns "made 5 pizzas"


But let's do some nice simple unit tests for it. Start with a new file, 
which I would call "test_pizza.py", and import two modules: unittest, 
and your own module. (You need to import your module, so Python knows 
what you are testing.)

# test_pizza.py
import pizza
import unittest


The unittest module has a *lot* of complex code in it, because it has a 
lot of work that it needs to do. It needs to find the tests, initialise 
them, run the tests, collect the results, display a visual indication of 
whether tests are passing or failing, and so on. But to make this work, 
all we need do is write the tests in a way that the unittest module 
understands. We do that by using inheritance: we inherit from the 
TestCase class:


class MakePizzaTests(unittest.TestCase):
     """Collect all the tests for the make_pizza function."""


It's good practice to have a separate class for each group of related 
tests. In this case, because there's only one function, we can put all 
the tests for that function into one class. If there was a second 
function, we would use a second class:

class MakeSaladTests(unittest.TestCase):
    ...

but there isn't, so we won't.

At the moment, if we run the tests in this, there will be zero tests run 
and zero failed. Because we haven't actually written any tests, just the 
beginnings of it! So let's add a simple test. What's the simplest thing 
we can think of about the make_pizza function? It returns a string! So 
let's test it:


class MakePizzaTests(unittest.TestCase):
    """Collect all the tests for the make_pizza function."""

    def test_make_pizza_returns_string(self):
        # Test that make_pizza returns a string.
        for num in (1, 2, 3, 100, 1000, 78901):
            result = pizza.make_pizza(num)
            self.assertIsInstance(result, str)



Here, we don't actually case what result make_pizza gives us, we just 
want to check that it returns a string. Any old string will do. It's 
good practice to start with more general tests, then work your way down 
to writing specific tests.

Notice that this test tries the function with half a dozen different 
values. If I tried it with only one value, say 23, and the test passed, 
maybe the code only passes with 23 but fails with everything else. In a 
perfect world, I could try testing with *every* possible value, but that 
might take a while, a long, long while, so I compromise by testing with 
just a handful of representative values.

Now, let's test this out. At your shell prompt (not the Python 
interactive interpreter!), run this command from inside the same 
directory as your pizza.py and test_pizza.py files:


python3.3 -m unittest test_pizza


Notice that I *don't* include the .py after test_pizza. Unittest needs 
to import it as a module.

If you do this, unittest will run the one test it finds, print a single 
dot, and you're done. Success! Our first test passed. 

Let's add a few more: edit the test_pizza file and add these two methods 
to the MakePizzaTests class:

    def test_make_pizzas(self):
        # Test that make_pizza returns the expected result.
        self.assertEqual(pizza.make_pizza(7), "made 7 pizzas")
        self.assertEqual(pizza.make_pizza(23), "made 23 pizzas")

    def test_make_1_pizza(self):
        # Test that make_pizza gives us a special result with 1 pizza.
        result = pizza.make_pizza(1)
        self.assertEqual(result, "made 1 pizza")



This time, if you run the tests, you'll get two dots and an F for Fail. 
The test_make_1_pizza fails. Can you see why, and how to fix the bug in 
the make_pizza function?



-- 
Steven


More information about the Tutor mailing list