Dive Into Python Python for experienced programmers |

## 6.2. Introducing `romantest.py`

Now that we've completely defined the behavior we expect from our conversion functions, we're going to do something a little unexpected: we're going to write a test suite that puts these functions through their paces and makes sure that they behave the way we want them to. You read that right: we're going to write code that tests code that we haven't written yet.

This is called unit testing, since the set of two conversion functions can be written and tested as a unit, separate from any larger program they may become part of later. Python has a framework for unit testing, the appropriately-named `unittest` module.

`unittest` is available in Python 2.1 and later. Python 2.0 users can download it from `pyunit.sourceforge.net`.

Unit testing is an important part of an overall testing-centric development strategy. If you write unit tests, it is important to write them early (preferably before writing the code that they test), and to keep them updated as code and requirements change. Unit testing is not a replacement for higher-level functional or system testing, but it is important in all phases of development:

- Before writing code, it forces you to detail your requirements in a useful fashion.
- While writing code, it keeps you from over-coding. When all the test cases pass, the function is complete.
- When refactoring code, it assures you that the new version behaves the same way as the old version.
- When maintaining code, it helps you cover your ass when someone comes screaming that your latest change broke their old code. (“But sir, all the unit tests passed when I checked it in...”)

This is the complete test suite for our Roman numeral conversion functions, which are yet to be written but will eventually be in `roman.py`. It is not immediately obvious how it all fits together; none of these classes or methods reference any of the others. There are good reasons for this, as we'll see shortly.

Example 6.1. `romantest.py`

If you have not already done so, you can download this and other examples used in this book.

    """Unit test for roman.py"""

    import roman
    import unittest

    class KnownValues(unittest.TestCase):
        knownValues = ( (1, 'I'),
                        (2, 'II'),
                        (3, 'III'),
                        (4, 'IV'),
                        (5, 'V'),
                        (6, 'VI'),
                        (7, 'VII'),
                        (8, 'VIII'),
                        (9, 'IX'),
                        (10, 'X'),
                        (50, 'L'),
                        (100, 'C'),
                        (500, 'D'),
                        (1000, 'M'),
                        (31, 'XXXI'),
                        (148, 'CXLVIII'),
                        (294, 'CCXCIV'),
                        (312, 'CCCXII'),
                        (421, 'CDXXI'),
                        (528, 'DXXVIII'),
                        (621, 'DCXXI'),
                        (782, 'DCCLXXXII'),
                        (870, 'DCCCLXX'),
                        (941, 'CMXLI'),
                        (1043, 'MXLIII'),
                        (1110, 'MCX'),
                        (1226, 'MCCXXVI'),
                        (1301, 'MCCCI'),
                        (1485, 'MCDLXXXV'),
                        (1509, 'MDIX'),
                        (1607, 'MDCVII'),
                        (1754, 'MDCCLIV'),
                        (1832, 'MDCCCXXXII'),
                        (1993, 'MCMXCIII'),
                        (2074, 'MMLXXIV'),
                        (2152, 'MMCLII'),
                        (2212, 'MMCCXII'),
                        (2343, 'MMCCCXLIII'),
                        (2499, 'MMCDXCIX'),
                        (2574, 'MMDLXXIV'),
                        (2646, 'MMDCXLVI'),
                        (2723, 'MMDCCXXIII'),
                        (2892, 'MMDCCCXCII'),
                        (2975, 'MMCMLXXV'),
                        (3051, 'MMMLI'),
                        (3185, 'MMMCLXXXV'),
                        (3250, 'MMMCCL'),
                        (3313, 'MMMCCCXIII'),
                        (3408, 'MMMCDVIII'),
                        (3501, 'MMMDI'),
                        (3610, 'MMMDCX'),
                        (3743, 'MMMDCCXLIII'),
                        (3844, 'MMMDCCCXLIV'),
                        (3888, 'MMMDCCCLXXXVIII'),
                        (3940, 'MMMCMXL'),
                        (3999, 'MMMCMXCIX'))

        def testToRomanKnownValues(self):
            """toRoman should give known result with known input"""
            for integer, numeral in self.knownValues:
                result = roman.toRoman(integer)
                self.assertEqual(numeral, result)

        def testFromRomanKnownValues(self):
            """fromRoman should give known result with known input"""
            for integer, numeral in self.knownValues:
                result = roman.fromRoman(numeral)
                self.assertEqual(integer, result)

    class ToRomanBadInput(unittest.TestCase):
        def testTooLarge(self):
            """toRoman should fail with large input"""
            self.assertRaises(roman.OutOfRangeError, roman.toRoman, 4000)

        def testZero(self):
            """toRoman should fail with 0 input"""
            self.assertRaises(roman.OutOfRangeError, roman.toRoman, 0)

        def testNegative(self):
            """toRoman should fail with negative input"""
            self.assertRaises(roman.OutOfRangeError, roman.toRoman, -1)

        def testDecimal(self):
            """toRoman should fail with non-integer input"""
            self.assertRaises(roman.NotIntegerError, roman.toRoman, 0.5)

    class FromRomanBadInput(unittest.TestCase):
        def testTooManyRepeatedNumerals(self):
            """fromRoman should fail with too many repeated numerals"""
            for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
                self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s)

        def testRepeatedPairs(self):
            """fromRoman should fail with repeated pairs of numerals"""
            for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'):
                self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s)

        def testMalformedAntecedent(self):
            """fromRoman should fail with malformed antecedents"""
            for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
                      'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):
                self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, s)

    class SanityCheck(unittest.TestCase):
        def testSanity(self):
            """fromRoman(toRoman(n))==n for all n"""
            for integer in range(1, 4000):
                numeral = roman.toRoman(integer)
                result = roman.fromRoman(numeral)
                self.assertEqual(integer, result)

    class CaseCheck(unittest.TestCase):
        def testToRomanCase(self):
            """toRoman should always return uppercase"""
            for integer in range(1, 4000):
                numeral = roman.toRoman(integer)
                self.assertEqual(numeral, numeral.upper())

        def testFromRomanCase(self):
            """fromRoman should only accept uppercase input"""
            for integer in range(1, 4000):
                numeral = roman.toRoman(integer)
                roman.fromRoman(numeral.upper())
                self.assertRaises(roman.InvalidRomanNumeralError,
                                  roman.fromRoman, numeral.lower())

    if __name__ == "__main__":
        unittest.main()

Further reading

- The PyUnit home page has an in-depth discussion of using the `unittest` framework, including advanced features not covered in this chapter.
- The PyUnit FAQ explains why test cases are stored separately from the code they test.
- Python Library Reference summarizes the `unittest` module.
- ExtremeProgramming.org discusses why you should write unit tests.
- The Portland Pattern Repository has an ongoing discussion of unit tests, including a standard definition, why you should code unit tests first, and several in-depth case studies.