The basic concept of an automated unittest test case is to instantiate part of our code, subject it to operations, and verify certain results using assertions.
Unittest was added to Python's standard batteries included library suite and doesn't require any extra installation.
With these steps, we will code a simple program and then write some automated tests using unittest:
recipe1.py
in which to put all of this recipe's code. Pick a class to test. This is known as the class under test. For this recipe, we'll pick a class that uses a simplistic Roman numeral converter:class RomanNumeralConverter(object): def __init__(self, roman_numeral): self.roman_numeral = roman_numeral self.digit_map = {"M":1000, "D":500, "C":100, "L":50, "X":10, "V":5, "I":1} def convert_to_decimal(self): val = 0 for char in self.roman_numeral: val += self.digit_map[char] return val
Test
appended to the end, subclassing unittest.TestCase
. Appending a test class with Test
is a common convention, but not a requirement. Extending unittest.TestCase
is a requirement needed to hook into unittest's standard test runner.import unittest class RomanNumeralConverterTest(unittest.TestCase):
test
, so they are automatically picked up by the test number of unittest.def test_parsing_millenia(self): value = RomanNumeralConverter("M") self.assertEquals(1000, value.convert_to_decimal()) def test_parsing_century(self): value = RomanNumeralConverter("C") self.assertEquals(100, value.convert_to_decimal()) def test_parsing_half_century(self): value = RomanNumeralConverter("L") self.assertEquals(50, value.convert_to_decimal()) def test_parsing_decade(self): value = RomanNumeralConverter("X") self.assertEquals(10, value.convert_to_decimal()) def test_parsing_half_decade(self): value = RomanNumeralConverter("V") self.assertEquals(5, value.convert_to_decimal()) def test_parsing_one(self): value = RomanNumeralConverter("I") self.assertEquals(1, value.convert_to_decimal()) def test_empty_roman_numeral(self): value = RomanNumeralConverter("") self.assertTrue(value.convert_to_decimal() == 0) self.assertFalse(value.convert_to_decimal() > 0) def test_no_roman_numeral(self): value = RomanNumeralConverter(None) self.assertRaises(TypeError, value.convert_to_decimal)
if __name__ == "__main__": unittest.main()
In the first step, we picked a class to test. Next, we created a separate test class. By naming the test class [class under test]Test
, it is easy to tell which class is under test. Each test method name must start with test
, so that unittest will automatically pick it up and run it. To add more tests, just define more test
methods. Each of these tests utilizes various assertions.
assertEquals(first, second[, msg])
: Compares first and second expressions; and fails, if they don't have the same value. We can optionally print a special message if there is a failure.assertTrue(expression[, msg])
: Tests the expression and fails if it is false. We can optionally print a special message if there is a failure.assertFalse(expression[, msg])
: Tests the expression and fails if it is true. We can optionally print a special message if there is a failure.assertRaises(exception, callable, …)
: Runs the callable, with any arguments, for the callable listed afterwards, and fails if it doesn't raise the exception.Unittest provides many options for asserting, failing, and other convenient options. The following sections show some recommendations on how to pick and choose from these options.
When an assertEquals
fails, the first and second values are printed in the error report, giving better feedback of what went wrong. assertTrue
and assertFalse
simply report failure. Not all testable results fit this but, if possible, use assertEquals
.
It's important to understand the concept of equality. When comparing integers, strings, and other scalars, it's very simple. It doesn't work as well with collections like dictionaries, lists, and sets. Complex, custom-defined objects may carry custom definitions of equality. These complex objects may require more fine-grained assertions. That is why it's probably a good idea to also include some test methods that directly target equality and inequality when working with custom objects.
Unittest has a self.fail([msg])
operation that unconditionally causes the test to fail, along with an optional message. This was not shown earlier because it is not recommended for use.
The fail
method is often used to detect certain situations like exceptions. A common idiom is as follows:
import unittest class BadTest(unittest.TestCase): def test_no_roman_numeral(self): value = RomanNumeralConverter(None) try: value.convert_to_decimal() self.fail("Expected a TypeError") except TypeError, e: pass
This tests the same behavior as the earlier test_no_roman_numeral
. The problem with this approach is that, when the code is working properly, the fail
method is never executed. Code which is not executed regularly is at risk of becoming out of date and invalid. This will also interfere with coverage reports. Instead, it is better to use assertRaises
as we used in the earlier examples. For other situations, look at rewriting the test using the other assertions.
Python's official documentation on unittest shows many other assertions, however, they depend on the version of Python we are using. Some have been deprecated; others are only available in later versions like Python 2.7.
If our code must support multiple versions of Python, then we must use the lowest common denominator. This recipe shows core assertions available in all versions since Python 2.1.
A newer unittest2 (http://pypi.python.org/pypi/unittest2/) is under development that backports several of these newer unittest features into Python 2.4+. However, due to unittest2 being in the beta stage at the time of writing and limitations to the size of this book, I decided to focus on unittest.
18.119.159.178