# 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.