[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