Dive Into Python-Chapter 14. Test-First Programming

Chia sẻ: Thanh Cong | Ngày: | Loại File: PDF | Số trang:53

0
46
lượt xem
7
download

Dive Into Python-Chapter 14. Test-First Programming

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Tham khảo tài liệu 'dive into python-chapter 14. test-first programming', công nghệ thông tin, kỹ thuật lập trình phục vụ nhu cầu học tập, nghiên cứu và làm việc hiệu quả

Chủ đề:
Lưu

Nội dung Text: Dive Into Python-Chapter 14. Test-First Programming

  1. Chapter 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
  2. class RomanError(Exception): pass 1 class OutOfRangeError(RomanError): pass 2 class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass 3 def toRoman(n): """convert integer to Roman numeral""" pass 4 def fromRoman(s): """convert Roman numeral to integer""" pass 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
  3. could 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
  4. fromRoman 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
  5. ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 154, in testFromRomanCase roman1.fromRoman(numeral.upper()) AttributeError: 'None' object has no attribute 'upper' ====================================================== ================ ERROR: toRoman should always return uppercase ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 148, in testToRomanCase self.assertEqual(numeral, numeral.upper()) AttributeError: 'None' object has no attribute 'upper' ====================================================== ================ FAIL: fromRoman should fail with malformed antecedents
  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 AssertionError: InvalidRomanNumeralError ====================================================== ================ FAIL: fromRoman should fail with repeated pairs of numerals ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 127, in testRepeatedPairs self.assertRaises(roman1.InvalidRomanNumeralError, roman1.fromRoman, s) File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
  7. 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\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 AssertionError: InvalidRomanNumeralError ====================================================== ================ FAIL: fromRoman should give known result with known input ----------------------------------------------------------------------
  8. Traceback (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)) AssertionError: 1 != None ====================================================== ================ FAIL: toRoman should give known result with known input ---------------------------------------------------------------------- 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
  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)) AssertionError: 1 != None ====================================================== ================ FAIL: toRoman should fail with non-integer input ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\docbook\dip\py\roman\stage1\romantest1.py", line 116, in testNonInteger
  10. self.assertRaises(roman1.NotIntegerError, roman1.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\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 AssertionError: OutOfRangeError ====================================================== ================ FAIL: toRoman should fail with large input
  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 AssertionError: OutOfRangeError ====================================================== ================ FAIL: toRoman should fail with 0 input 1 ---------------------------------------------------------------------- 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
  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
  13. input”) 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
  14. class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = (('M', 1000), 1 ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5),
  15. ('IV', 4), ('I', 1)) def toRoman(n): """convert integer to Roman numeral""" result = "" for numeral, integer in romanNumeralMap: while n >= integer: 2 result += numeral n -= integer return result def fromRoman(s): """convert Roman numeral to integer""" pass 1 romanNumeralMap is a tuple of tuples which defines three things:
  16. 1. 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 (numeral, value). 2 Here's where your rich data structure pays off, because you don't need any special logic to handle the subtraction rule. To convert to Roman numerals, you simply iterate through romanNumeralMap looking for the largest integer value less than or equal to the input. Once found, you add the Roman numeral representation to the end of the output, subtract the 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:
  17. 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 this manual spot check. But will it pass the unit testing? Well no, not entirely. Example 14.5. Output of romantest2.py against roman2.py
  18. Remember 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
  19. 1 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. ====================================================== ================
  20. 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)
Đồng bộ tài khoản