Outputting data from unit test in Python

Posted on

Question :

Outputting data from unit test in Python

If I’m writing unit tests in python (using the unittest module), is it possible to output data from a failed test, so I can examine it to help deduce what caused the error? I am aware of the ability to create a customized message, which can carry some information, but sometimes you might deal with more complex data, that can’t easily be represented as a string.

For example, suppose you had a class Foo, and were testing a method bar, using data from a list called testdata:

class TestBar(unittest.TestCase):
    def runTest(self):
        for t1, t2 in testdata:
            f = Foo(t1)
            self.assertEqual(f.bar(t2), 2)

If the test failed, I might want to output t1, t2 and/or f, to see why this particular data resulted in a failure. By output, I mean that the variables can be accessed like any other variables, after the test has been run.

Answer #1:

Very late answer for someone that, like me, comes here looking for a simple and quick answer.

In Python 2.7 you could use an additional parameter msg to add information to the error message like this:

self.assertEqual(f.bar(t2), 2, msg='{0}, {1}'.format(t1, t2))

Offical docs here

Answered By: Facundo Casco

Answer #2:

We use the logging module for this.

For example:

import logging
class SomeTest( unittest.TestCase ):
    def testSomething( self ):
        log= logging.getLogger( "SomeTest.testSomething" )
        log.debug( "this= %r", self.this )
        log.debug( "that= %r", self.that )
        # etc.
        self.assertEquals( 3.14, pi )

if __name__ == "__main__":
    logging.basicConfig( stream=sys.stderr )
    logging.getLogger( "SomeTest.testSomething" ).setLevel( logging.DEBUG )

That allows us to turn on debugging for specific tests which we know are failing and for which we want additional debugging information.

My preferred method, however, isn’t to spend a lot of time on debugging, but spend it writing more fine-grained tests to expose the problem.

Answered By: S.Lott

Answer #3:

You can use simple print statements, or any other way of writing to stdout. You can also invoke the Python debugger anywhere in your tests.

If you use nose to run your tests (which I recommend), it will collect the stdout for each test and only show it to you if the test failed, so you don’t have to live with the cluttered output when the tests pass.

nose also has switches to automatically show variables mentioned in asserts, or to invoke the debugger on failed tests. For example -s (--nocapture) prevents the capture of stdout.

Answered By: Ned Batchelder

Answer #4:

I don’t think this is quite what your looking for, there’s no way to display variable values that don’t fail, but this may help you get closer to outputting the results the way you want.

You can use the TestResult object returned by the TestRunner.run() for results analysis and processing. Particularly, TestResult.errors and TestResult.failures

About the TestResults Object:


And some code to point you in the right direction:

>>> import random
>>> import unittest
>>> class TestSequenceFunctions(unittest.TestCase):
...     def setUp(self):
...         self.seq = range(5)
...     def testshuffle(self):
...         # make sure the shuffled sequence does not lose any elements
...         random.shuffle(self.seq)
...         self.seq.sort()
...         self.assertEqual(self.seq, range(10))
...     def testchoice(self):
...         element = random.choice(self.seq)
...         error_test = 1/0
...         self.assert_(element in self.seq)
...     def testsample(self):
...         self.assertRaises(ValueError, random.sample, self.seq, 20)
...         for element in random.sample(self.seq, 5):
...             self.assert_(element in self.seq)
>>> suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
>>> testResult = unittest.TextTestRunner(verbosity=2).run(suite)
testchoice (__main__.TestSequenceFunctions) ... ERROR
testsample (__main__.TestSequenceFunctions) ... ok
testshuffle (__main__.TestSequenceFunctions) ... FAIL

ERROR: testchoice (__main__.TestSequenceFunctions)
Traceback (most recent call last):
  File "<stdin>", line 11, in testchoice
ZeroDivisionError: integer division or modulo by zero

FAIL: testshuffle (__main__.TestSequenceFunctions)
Traceback (most recent call last):
  File "<stdin>", line 8, in testshuffle
AssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ran 3 tests in 0.031s

FAILED (failures=1, errors=1)
>>> testResult.errors
[(<__main__.TestSequenceFunctions testMethod=testchoice>, 'Traceback (most recent call last):n  File "<stdin>"
, line 11, in testchoicenZeroDivisionError: integer division or modulo by zeron')]
>>> testResult.failures
[(<__main__.TestSequenceFunctions testMethod=testshuffle>, 'Traceback (most recent call last):n  File "<stdin>
", line 8, in testshufflenAssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]n')]
Answered By: monkut

Answer #5:

The method I use is really simple. I just log it as a warning so it will actually show up.

import logging

class TestBar(unittest.TestCase):
    def runTest(self):

       #this line is important
       log = logging.getLogger("LOG")

       for t1, t2 in testdata:
         f = Foo(t1)
         self.assertEqual(f.bar(t2), 2)
Answered By: Orane

Answer #6:

Another option – start a debugger where the test fails.

Try running your tests with Testoob (it will run your unittest suite without changes), and you can use the ‘–debug’ command line switch to open a debugger when a test fails.

Here’s a terminal session on windows:

C:work> testoob tests.py --debug
Debugging for failure in test: test_foo (tests.MyTests.test_foo)
> c:python25libunittest.py(334)failUnlessEqual()
-> (msg or '%r != %r' % (first, second))
(Pdb) up
> c:worktests.py(6)test_foo()
-> self.assertEqual(x, y)
(Pdb) l
  1     from unittest import TestCase
  2     class MyTests(TestCase):
  3       def test_foo(self):
  4         x = 1
  5         y = 2
  6  ->     self.assertEqual(x, y)
Answered By: orip

Answer #7:

I think I might have been overthinking this. One way I’ve come up with that does the job, is simply to have a global variable, that accumulates the diagnostic data.

Somthing like this:

log1 = dict()
class TestBar(unittest.TestCase):
    def runTest(self):
        for t1, t2 in testdata:
            f = Foo(t1) 
            if f.bar(t2) != 2: 
                log1("TestBar.runTest") = (f, t1, t2)
                self.fail("f.bar(t2) != 2")

Thanks for the replies. They have given me some alternative ideas for how to record information from unit tests in python.

Answered By: Silverfish

Answer #8:

What I do in these cases is to have a log.debug() with some messages in my application. Since the default logging level is WARNING, such messages don’t show in the normal execution.

Then, in the unittest I change the logging level to DEBUG, so that such messages are shown while running them.

import logging

log.debug("Some messages to be shown just when debugging or unittesting")

In the unittests:

# Set log level
loglevel = logging.DEBUG

See a full example:

This is daikiri.py, a basic class that implements a Daikiri with its name and price. There is method make_discount() that returns the price of that specific daikiri after applying a given discount:

import logging

log = logging.getLogger(__name__)

class Daikiri(object):
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def make_discount(self, percentage):
        log.debug("Deducting discount...")  # I want to see this message
        return self.price * percentage

Then, I create a unittest test_daikiri.py that checks its usage:

import unittest
import logging
from .daikiri import Daikiri

class TestDaikiri(unittest.TestCase):
    def setUp(self):
        # Changing log level to DEBUG
        loglevel = logging.DEBUG

        self.mydaikiri = Daikiri("cuban", 25)

    def test_drop_price(self):
        new_price = self.mydaikiri.make_discount(0)
        self.assertEqual(new_price, 0)

if __name__ == "__main__":

So when I execute it I get the log.debug messages:

$ python -m test_daikiri
DEBUG:daikiri:Deducting discount...
Ran 1 test in 0.000s


Leave a Reply

Your email address will not be published. Required fields are marked *