[Tutor] TDD in Python

Steven D'Aprano steve at pearwood.info
Mon Sep 26 22:50:14 EDT 2016


Hi Angela, and welcome!

My comments below, interleaved with yours.


On Mon, Sep 26, 2016 at 09:54:18PM +0100, angela ebirim wrote:

[...]
> I'm keen to learn about testing my Python code and hoping someone could
> help me with this query...
> 
> #test.py
> 
> class Test:
>    def __init__(self, members):
>           self.members = members
> 
>    def printMembers(self):
>        print('Printing members of Test class...')
>        for member in self.members: print('\t%s ' % member)
> 
> 
> test = Test(['Gazelle', 'Elephant', 'Lion', 'Fish'])
> test.printMembers()


It is a little confusing to have your code *being tested* called "test". 
As you can see below, that leads to your test code being called 
"test_test".

You might like to consider renaming the code being tested "demo", 
"example" or something similar.



> #test_test.py
> 
> import unittest
> from test import Test
> 
> class TestTestCase(unittest.TestCase):
>    """Tests for `test.py`."""
> 
>   #preparing to test
>    def setUp(self):
>        """Sample test case"""
>        print "TestTest::setUp_:begin"
>        # self.members = [1, 2, 3, 4, 5]
>        self.members = Test([1, 2, 3, 4])
> 
>    def test_is_printMembers(self):
>        """Will print a list of contents?"""
>        self.assertTrue(self.members.printMembers)
[...]
> Does my test code in test_test.py look correct?

Unfortunately not. You are on the right track, but not quite there yet.

Firstly, unfortunately you have picked a complicated example to test! 
Your printMembers method doesn't return a result, it operates by 
side-effect: it prints the existing members. That makes it hard to test.

Your test_is_printMembers test method will always succeed (unless it 
crashes and raises an exception). Let's run through it by hand:

    self.assertTrue(self.members.printMembers)


First we grab self.members, which is created by the setUp method. So far 
so good, this should always work.

Then we fetch the printMembers attribute from self.members, but *don't* 
call it. That should always succeed, since the class does have a 
printMembers method. If it doesn't succeed, we get an exception, and 
this test case (test_is_printMembers) will be flagged as "E" (error).

Finally we do our test assertion: assertTrue tests whether the method 
object is truthy, which it always is. Again, this cannot fail (unless 
you change printMembers to a falsey object, like None or 0). So long as 
printMembers is a method, this test will always succeed, no matter what 
the method does.

So let's fix this. First, let's write a test that simply checks whether 
or not printMembers exists and is a method, we don't care what it does, 
only whether it is there:


import inspect

class TestTestCase(unittest.TestCase):
    """Tests for `test.py`."""

    def setUp(self):
        self.members = Test([1, 2, 3, 4])

    def test_printMembers_is_method(self):
        printMembers = self.members.printMembers  # don't call it
        self.assertTrue(inspect.ismethod(printMembers))


That's not a very exiting test, but it is simple: it will raise an 
exception if printMembers doesn't exist, and the test will fail if it 
does exist but isn't a method.

Now let's add a test that checks the result of calling the printMembers 
method. This is tricky because we have to capture the output of print. 
Let's start with a test that only checks that the method returns None. 
Add a couple more imports:

import sys
from io import StringIO


and a new test method. We need to save the existing place where print 
output goes to ("standard out", or stdout), replace it with a fake, run 
call the method, replace the fake with the real one, and then check the 
result:


    def test_printMembers_returns_none(self):
        stdout = sys.stdout  # Save the current stdout
        try:
            sys.stdout = StringIO()  # replace it with a fake
            result = self.members.printMembers()  # call the method
        finally:
            sys.stdout = stdout  # restore the original
        # now we can test that the return result is what we expect
        self.assertTrue(result is None)



Finally, let's check that the output of calling printMembers is correct. 
That's just a slight variation on the above:


    def test_printMembers_output(self):
        stdout = sys.stdout  # Save the current stdout
        out = StringIO()
        try:
            sys.stdout = out
            self.members.printMembers()
        finally:
            sys.stdout = stdout  # restore the original
        text = out.getvalue()
        expected = 'Printing members of Test class...\n'
        expected += '\t1 \n\t2 \n\t3 \n\t4 \n'
        self.assertEqual(text, expected)


When you run the unit test suite, each test will be run, and it will 
print a nice progress display. Each test will print one of:

   . (dot) for passed tests
   E for tests which raise an exception
   F for tests which fail
   S for tests which are skipped
        

P.S. I have not run the above code, so while I expect it to work, there 
may be a few bugs in it. I am also assuming you are using Python 3.



-- 
Steve


More information about the Tutor mailing list