14.2. roman.py, 第 2 阶段

现在你有了 roman 模块的大概框架,到了开始写代码以通过测试的时候了。

例 14.3. roman2.py

这个文件可以从 py/roman/stage2/ 目录中找到。

如果您还没有下载本书附带的样例程序, 可以 下载本程序和其他样例程序

"""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
romanNumeralMap 是一个用来定义三个内容的元组的元组:
  1. 代表大部分罗马数字的字符。注意不只是单字符的罗马数字,时时彩计划软件公式:你同样在这里定义诸如 CM (“比一千少一百,即 900”) 的双字符,这可以让稍后编写的 toRoman 简单一些。
  2. 罗马数字的顺序。它们是以降序排列的,从M 一路到 I
  3. 每个罗马数字所对应的数值。每个内部的元组都是一个 (numeralvalue) 数值对。
这里便显示出你丰富的数据结构带来的优势,你不需要什么特定的逻辑处理减法规则。你只需要通过搜寻 romanNumeralMap 寻找不大于输入数值的最大对应整数即可。只要找到,就在结果的结尾把这个整数对应的罗马字符添加到输出结果的末尾,从输入值中减去这个整数,一遍遍这样继续下去。

例 14.4. toRoman 如何工作

如果你不明了 toRoman 如何工作,在 while 循环的结尾添加一个 print 语句:

        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'

看来 toRoman 可以运转了,至少手工测试可以。但能通过单元测试吗?啊哈,不,不完全可以。

例 14.5. 以 romantest2.py 测试 roman2.py 的输出

要记得用 -v 命令行选项运行 romantest2.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 的返回值总是大写的,因为 romanNumeralMap 定义的罗马字符都是以大写字母表示的。因此这个测试已经通过了。
好消息来了:这个版本的 toRoman 函数能够通过已知值测试。记住,这并不能证明完全没问题,但至少通过测试多种有效输入考验了这个函数:包括每个单一字符的罗马数字,可能的最大输入 (3999),以及可能的最长的罗马数字 (对应于 3888)。从这点来看,你有理由相信这个函数对于任何有效输入都不会出问题。
但是,函数还没办法处理无效输入,每个无效输入测试都失败了。这很好理解,因为你还没有对无效输入进行检查,测试用例希望捕捉到特定的异常 (通过 assertRaises),而你根本没有让这些异常引发。这是你下一阶段的工作。

下面是单元测试结果的剩余部分,列出了所有失败的详细信息,你已经让它降到了 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 testNonInteger
    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)
时时彩稳赚专家大罗小罗 时时彩掌赢专家真假 皇冠时时彩平台注册 最强的时时彩软件 时时彩四星皇恩娱乐
时时彩最聪明的玩法 福少时时彩软件 时时彩功夫后一 重庆时时彩趋势图 易语言重庆时时彩源码
江西时时彩博客 澳门时时彩是不是真的 天天时时彩软件手机下载 拉菲2平台重庆时时彩 重庆时时彩骗局模式
时时彩后一投注技巧 重庆时时彩开奖结果双彩网 时时彩辅助 菲博娱乐平台一一: 如何购买江西时时彩
北京pk108码技巧 辽宁35选7字谜 快乐8开奖直播 牛牛私服 广东11选5开奖遗漏
江苏快3预测专家预测 元宝娱乐 博猫彩票 北京快乐8上中下稳赚 pk10开奖直播皇家彩世界
北京赛车pk10历史开奖 博彩通百乐坊 好运国际娱乐城 吉林快三儿走势图 快乐十分走势图陕西
憋尿的故事 河北快三开奖结果 黑龙江22选5中奖方式 如何做万花筒 快乐十分开奖号码