Home > Dive Into Python > Unit Testing > roman.py, stage 2 | << >> | ||||
diveintopython.org Python for experienced programmers |
Now that we have the framework of our roman module laid out, it's time to start writing code and passing test cases.
If you have not already done so, you can download this and other examples used in this book.
"""Convert to and from Roman numerals""" #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1)) def toRoman(n): """convert integer to Roman numeral""" result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result def fromRoman(s): """convert Roman numeral to integer""" pass
Example 6.10. How toRoman works
If you're not clear how toRoman works, add a print statement to the end of the while loop:
while n >= integer: result += numeral n -= integer print 'subtracting', integer, 'from input, adding', numeral, 'to output'
>>> import roman2 >>> roman2.toRoman(1424) subtracting 1000 from input, adding M to output subtracting 400 from input, adding CD to output subtracting 10 from input, adding X to output subtracting 10 from input, adding X to output subtracting 4 from input, adding IV to output 'MCDXXIV'
So toRoman appears to work, at least in our manual spot check. But will it pass the unit testing? Well no, not entirely.
Example 6.11. Output of romantest2.py against roman2.py
fromRoman should only accept uppercase input ... FAIL toRoman should always return uppercase ... ok fromRoman should fail with malformed antecedents ... FAIL fromRoman should fail with repeated pairs of numerals ... FAIL fromRoman should fail with too many repeated numerals ... FAIL fromRoman should give known result with known input ... FAIL toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... FAIL toRoman should fail with non-integer input ... FAIL toRoman should fail with negative input ... FAIL toRoman should fail with large input ... FAIL toRoman should fail with 0 input ... FAIL
toRoman does, in fact, always return uppercase, because our romanNumeralMap defines the Roman numeral representations as uppercase. So this test passes already. | |
Here's the big news: this version of the toRoman function passes the known values test. Remember, it's not comprehensive, but it does put the function through its paces with a variety of good inputs, including inputs that produce every single-character Roman numeral, the largest possible input (3999), and the input that produces the longest possible Roman numeral (3888). At this point, we can be reasonably confident that the function works for any good input value you could throw at it. | |
However, the function does not “work” for bad values; it fails every single bad input test. That makes sense, because we didn't include any checks for bad input. Those test cases look for specific exceptions to be raised (via assertRaises), and we're never raising them. We'll do that in the next stage. |
Here's the rest of the output of the unit test, listing the details of all the failures. We're down to 10.
====================================================================== FAIL: fromRoman should only accept uppercase input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 156, in testFromRomanCase roman2.fromRoman, numeral.lower()) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with malformed antecedents ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 133, in testMalformedAntecedent self.assertRaises(roman2.InvalidRomanNumeralError, roman2.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with repeated pairs of numerals ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 127, in testRepeatedPairs self.assertRaises(roman2.InvalidRomanNumeralError, roman2.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should fail with too many repeated numerals ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 122, in testTooManyRepeatedNumerals self.assertRaises(roman2.InvalidRomanNumeralError, roman2.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: InvalidRomanNumeralError ====================================================================== FAIL: fromRoman should give known result with known input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 99, in testFromRomanKnownValues self.assertEqual(integer, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or '%s != %s' % (first, second)) AssertionError: 1 != None ====================================================================== FAIL: fromRoman(toRoman(n))==n for all n ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 141, in testSanity self.assertEqual(integer, result) File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or '%s != %s' % (first, second)) AssertionError: 1 != None ====================================================================== FAIL: toRoman should fail with non-integer input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 116, in testDecimal self.assertRaises(roman2.NotIntegerError, roman2.toRoman, 0.5) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: NotIntegerError ====================================================================== FAIL: toRoman should fail with negative input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 112, in testNegative self.assertRaises(roman2.OutOfRangeError, roman2.toRoman, -1) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError ====================================================================== FAIL: toRoman should fail with large input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 104, in testTooLarge self.assertRaises(roman2.OutOfRangeError, roman2.toRoman, 4000) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError ====================================================================== FAIL: toRoman should fail with 0 input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 108, in testZero self.assertRaises(roman2.OutOfRangeError, roman2.toRoman, 0) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises raise self.failureException, excName AssertionError: OutOfRangeError ---------------------------------------------------------------------- Ran 12 tests in 0.320s FAILED (failures=10)
roman.py, stage 1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | roman.py, stage 3 |