--- Traceback most recent call last: File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 133, in testMalformedAntecedent self.assertRaisesroman1.InvalidRomanNumeralError, roman1.
Trang 1Chapter 14 Test-First Programming
14.1 roman.py, stage 1
Now that the unit tests are complete, it's time to start writing the code that the test cases are attempting to test You're going to do this in stages, so you can see all the unit tests fail, then watch them pass one by one as you fill in the gaps in roman.py
Example 14.1 roman1.py
This file is available in py/roman/stage1/ in the examples directory
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
Trang 2class RomanError(Exception): pass 1
class OutOfRangeError(RomanError): pass 2
class NotIntegerError(RomanError): pass
class InvalidRomanNumeralError(RomanError): pass 3
1 This is how you define your own custom exceptions in Python
Exceptions are classes, and you create your own by subclassing existing exceptions It is strongly recommended (but not required) that you subclass Exception, which is the base class that all built-in exceptions inherit from Here I am defining RomanError (inherited from Exception) to act as the base class for all my other custom exceptions to follow This is a matter of style; I
Trang 3could just as easily have inherited each individual exception from the
Exception class directly
2 The OutOfRangeError and NotIntegerError exceptions will eventually
be used by toRoman to flag various forms of invalid input, as specified in ToRomanBadInput
3 The InvalidRomanNumeralError exception will eventually be used by fromRoman to flag invalid input, as specified in FromRomanBadInput
4 At this stage, you want to define the API of each of your functions, but you don't want to code them yet, so you stub them out using the Python reserved word pass
Now for the big moment (drum roll please): you're finally going to run the unit test against this stubby little module At this point, every test case
should fail In fact, if any test case passes in stage 1, you should go back to romantest.py and re-evaluate why you coded a test so useless that it passes with do-nothing functions
Run romantest1.py with the -v command-line option, which will give more verbose output so you can see exactly what's going on as each test case runs With any luck, your output should look like this:
Example 14.2 Output of romantest1.py against roman1.py
Trang 4fromRoman should only accept uppercase input ERROR
toRoman should always return uppercase ERROR
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 FAIL
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
======================================================
================
ERROR: fromRoman should only accept uppercase input
Trang 5-
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 154, in
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 148, in
Trang 6-
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 133, in
testMalformedAntecedent
self.assertRaises(roman1.InvalidRomanNumeralError,
roman1.fromRoman, s)
File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 127, in
Trang 7raise self.failureException, excName
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 122, in
testTooManyRepeatedNumerals
self.assertRaises(roman1.InvalidRomanNumeralError,
roman1.fromRoman, s)
File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
Trang 8Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.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))
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 93, in
testToRomanKnownValues
self.assertEqual(numeral, result)
File "c:\python21\lib\unittest.py", line 273, in failUnlessEqual
raise self.failureException, (msg or '%s != %s' % (first, second))
AssertionError: I != None
Trang 9================
FAIL: fromRoman(toRoman(n))==n for all n
-
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.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))
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 116, in
testNonInteger
Trang 10self.assertRaises(roman1.NotIntegerError, roman1.toRoman, 0.5)
File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 112, in
testNegative
self.assertRaises(roman1.OutOfRangeError, roman1.toRoman, -1)
File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
Trang 11-
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 104, in
testTooLarge
self.assertRaises(roman1.OutOfRangeError, roman1.toRoman, 4000)
File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 108, in testZero
self.assertRaises(roman1.OutOfRangeError, roman1.toRoman, 0)
File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
AssertionError: OutOfRangeError 2
Trang 12-
Ran 12 tests in 0.040s 3
FAILED (failures=10, errors=2) 4
1 Running the script runs unittest.main(), which runs each test case, which is to say each method defined in each class within romantest.py For each test case, it prints out the doc string of the method and whether that test passed or failed As expected, none of the test cases passed
2 For each failed test case, unittest displays the trace information
showing exactly what happened In this case, the call to assertRaises (also called failUnlessRaises) raised an AssertionError because it was expecting toRoman to raise an OutOfRangeError and it didn't
3 After the detail, unittest displays a summary of how many tests were performed and how long it took
4 Overall, the unit test failed because at least one test case did not pass When a test case doesn't pass, unittest distinguishes between failures and errors A failure is a call to an assertXYZ method, like assertEqual or
assertRaises, that fails because the asserted condition is not true or the
expected exception was not raised An error is any other sort of exception raised in the code you're testing or the unit test case itself For instance, the testFromRomanCase method (“fromRoman should only accept uppercase
Trang 13input”) was an error, because the call to numeral.upper() raised an
AttributeError exception, because toRoman was supposed to return a string but didn't But testZero (“toRoman should fail with 0 input”) was a failure, because the call to fromRoman did not raise the InvalidRomanNumeral exception that assertRaises was looking for
14.2 roman.py, stage 2
Now that you have the framework of the roman module laid out, it's time to start writing code and passing test cases
Example 14.3 roman2.py
This file is available in py/roman/stage2/ in the examples directory
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
Trang 14class RomanError(Exception): pass
class OutOfRangeError(RomanError): pass
class NotIntegerError(RomanError): pass
class InvalidRomanNumeralError(RomanError): pass
#Define digit mapping
Trang 161 The character representations of the most basic Roman numerals Note that this is not just the single-character Roman numerals; you're also
defining two-character pairs like CM (“one hundred less than one
thousand”); this will make the toRoman code simpler later
2 The order of the Roman numerals They are listed in descending value order, from M all the way down to I
3 The value of each Roman numeral Each inner tuple is a pair of
corresponding integer value from the input, lather, rinse, repeat
Example 14.4 How toRoman works
If you're not clear how toRoman works, add a print statement to the end of the while loop:
Trang 17subtracting 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
Trang 18Remember to run romantest2.py with the -v command-line flag to enable verbose mode
fromRoman should only accept uppercase input FAIL
toRoman should always return uppercase ok 1
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 2
fromRoman(toRoman(n))==n for all n FAIL
toRoman should fail with non-integer input FAIL 3
toRoman should fail with negative input FAIL
toRoman should fail with large input FAIL
toRoman should fail with 0 input FAIL
Trang 191 toRoman does, in fact, always return uppercase, because
romanNumeralMap defines the Roman numeral representations as
uppercase So this test passes already
2 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, you can be reasonably confident that the function
works for any good input value you could throw at it
3 However, the function does not “work” for bad values; it fails every single bad input test That makes sense, because you didn't include any
checks for bad input Those test cases look for specific exceptions to be raised (via assertRaises), and you're never raising them You'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 You're down to 10
======================================================
================
Trang 20FAIL: 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
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)
Trang 21File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
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
Trang 22-
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
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
Trang 23raise self.failureException, (msg or '%s != %s' % (first, second))
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))
Trang 24File "C:\docbook\dip\py\roman\stage2\romantest2.py", line 116, in
testNonInteger
self.assertRaises(roman2.NotIntegerError, roman2.toRoman, 0.5)
File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
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
Trang 25================
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
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)
Trang 26File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
raise self.failureException, excName
This file is available in py/roman/stage3/ in the examples directory
If you have not already done so, you can download this and other examples used in this book
Trang 27"""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