# Dive Into Python

Python for experienced programmers

Find:

## 6.4. Testing for failure

It is not enough to test that our functions succeed when given good input; we must also test that they fail when given bad input. And not just any sort of failure; they must fail in the way we expect.

Remember our other requirements for `toRoman`:

2. `toRoman` should fail when given an integer outside the range `1` to `3999`.
3. `toRoman` should fail when given a non-integer decimal.

In Python, functions indicate failure by raising exceptions, and the `unittest` module provides methods for testing whether a function raises a particular exception when given bad input.

### Example 6.3. Testing bad input to `toRoman`

    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)

The `TestCase` class of the `unittest` provides the `assertRaises` method, which takes the following arguments: the exception we’re expecting, the function we’re testing, and the arguments we’re passing that function. (If the function we’re testing takes more than one argument, pass them all to `assertRaises`, in order, and it will pass them right along to the function we’re testing.) Pay close attention to what we’re doing here: instead of calling `toRoman` directly and manually checking that it raises a particular exception (by wrapping it in a `try...except` block), `assertRaises` has encapsulated all of that for us. All we do is give it the exception (`roman.OutOfRangeError`), the function (`toRoman`), and `toRoman`’s arguments (`4000`), and `assertRaises` takes care of calling `toRoman` and checking to make sure that it raises `roman.OutOfRangeError`. (Have I mentioned recently how handy it is that everything in Python is an object, including functions and exceptions?)

Along with testing numbers that are too large, we need to test numbers that are too small. Remember, Roman numerals cannot express `0` or negative numbers, so we have a test case for each of those (`testZero` and `testNegative`). In `testZero`, we are testing that `toRoman` raises a `roman.OutOfRangeError` exception when called with `0`; if it does not raise a `roman.OutOfRangeError` (either because it returns an actual value, or because it raises some other exception), this test is considered failed.

| Requirement #3 specifies that `toRoman` cannot accept a non-integer decimal, so here we test to make sure that `toRoman` raises a `roman.NotIntegerError` exception when called with a decimal (`0.5`). If `toRoman` does not raise a `roman.NotIntegerError`, this test is considered failed.

The next two requirements are similar to the first three, except they apply to `fromRoman` instead of `toRoman`:

4. `fromRoman` should take a valid Roman numeral and return the number that it represents.
5. `fromRoman` should fail when given an invalid Roman numeral.

Requirement #4 is handled in the same way as requirement #1, iterating through a sampling of known values and testing each in turn. Requirement #5 is handled in the same way as requirements #2 and #3, by testing a series of bad inputs and making sure `fromRoman` raises the appropriate exception.

### Example 6.4. Testing bad input to `fromRoman`

    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)

Not much new to say about these; the pattern is exactly the same as the one we used to test bad input to `toRoman`. I will briefly note that we have another exception: `roman.InvalidRomanNumeralError`. That makes a total of three custom exceptions that will need to be defined in `roman.py` (along with `roman.OutOfRangeError` and `roman.NotIntegerError`). We’ll see how to define these custom exceptions when we actually start writing `roman.py`, later in this chapter.