楼主: lastwinner

[参考文档] Python 研究(Dive Into Python)

[复制链接]
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
271#
 楼主| 发表于 2006-7-19 00:21 | 只看该作者
14.5. roman.py, 第 5 阶段
现在 fromRoman 对于有效输入能够正常工作了,是揭开最后一个谜底的时候了:使它正常工作于无效输入的情况下。这意味着要找出一个方法检查一个字符串是不是有效的罗马数字。这比 toRoman 中验证有效的数字输入 困难, 但是你可以使用一个强大的工具:正则表达式。

如果你不熟悉正则表达式,并且没有读过 第 7 章 正则表达式,现在是该好好读读的时候了。

如你在 第 7.3 节 “个案研究:罗马字母”中所见到的,构建罗马数字有几个简单的规则:使用字母 M, D, C, L, X, V,和 I。让我们回顾一下:

字符是被“加”在一起的: I 是 1, II 是 2, III 是 3。 VI 是 6 (看上去就是 “5 加 1”), VII 是 7, VIII 是 8。
这些字符( I, X, C, 和 M)最多可以重复三次。 对于 4,你则需要利用下一个能够被5整除的字符进行减操作得到。你不能把 4 表示为 IIII 而应该表示为 IV (“比 5 小 1 ”)。 40 则被写作 XL (“比 50 小 10”), 41 表示为 XLI, 42 表示为 XLII, 43 表示为 XLIII, 44 表示为 XLIV (“比50小10,又比 5 小 1”)。
类似的,对于数字 9, 你必须利用下一个能够被10整除的字符进行减操作得到:8 是 VIII,而 9 是 IX (“比 10 小 1”),而不是 VIIII (由于 I 不能重复四次)。 90 表示为 XC, 900 表示为 CM。
有五个字符不能被重复: 10 应该表示为 X, 而不会是 VV。 100 应该表示为 C,而不是 LL。
罗马数字经常从高位到低位书写,从左到右阅读,因此不同顺序的字符意义大不相同。 DC 是 600, CD 是完全另外一个数 (400,“比 500 少 100”)。 CI 是 101,而 IC 根本就不是一个有效的罗马数字(因为你无法从100直接减1,应该写成 XCIX,意思是 “比 100 少 10,然后加上数字 9,也就是比 10 少 1”)。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
272#
 楼主| 发表于 2006-7-19 00:21 | 只看该作者
例 14.12. roman5.py
这个程序可以在例子目录下的py/roman/stage5/ 目录中找到。

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

"""Convert to and from Roman numerals"""
import re

#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"""
    if not (0 < n < 4000):
        raise OutOfRangeError, "number out of range (must be 1..3999)"
    if int(n) <> n:
        raise NotIntegerError, "non-integers can not be converted"

    result = ""
    for numeral, integer in romanNumeralMap:
        while n >= integer:
            result += numeral
            n -= integer
    return result

#Define pattern to detect valid Roman numerals
romanNumeralPattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'

def fromRoman(s):
    """convert Roman numeral to integer"""
    if not re.search(romanNumeralPattern, s):                                    
        raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s

    result = 0
    index = 0
    for numeral, integer in romanNumeralMap:
        while s[index:index+len(numeral)] == numeral:
            result += integer
            index += len(numeral)
    return result
  这只是 第 7.3 节 “个案研究:罗马字母” 中讨论的匹配模版的继续。 十位上可能是XC (90), XL (40),或者可能是 L 后面跟着 0 到 3 个 X 字符。 个位则可能是 IX (9), IV (4),或者是一个可能是 V 后面跟着 0 到 3 个 I 字符。
  把所有的逻辑编码成正则表达式,检查无效罗马字符的代码就很简单了。 如果 re.search 返回一个对象则表示匹配了正则表达式,输入是有效的,否则输入无效。

这里你可能会怀疑这个面目可憎的正则表达式是否真能查出错误的罗马字符表示。没关系,不必完全听我的,不妨看看下面的结果:

使用道具 举报

回复
论坛徽章:
7
六级虎吧徽章
日期:2009-03-30 21:56:32
273#
发表于 2006-7-19 00:25 | 只看该作者
楼主可真能贴……

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
274#
 楼主| 发表于 2006-7-19 00:27 | 只看该作者
最初由 tang2049 发布
[B]楼主可真能贴…… [/B]


比你差远了

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
275#
 楼主| 发表于 2006-7-19 00:38 | 只看该作者
例 14.13. 用 romantest5.py 测试 roman5.py 的结果

fromRoman should only accept uppercase input ... ok         
toRoman should always return uppercase ... ok
fromRoman should fail with malformed antecedents ... ok      
fromRoman should fail with repeated pairs of numerals ... ok
fromRoman should fail with too many repeated numerals ... ok
fromRoman should give known result with known input ... ok
toRoman should give known result with known input ... ok
fromRoman(toRoman(n))==n for all n ... ok
toRoman should fail with non-integer input ... ok
toRoman should fail with negative input ... ok
toRoman should fail with large input ... ok
toRoman should fail with 0 input ... ok

----------------------------------------------------------------------
Ran 12 tests in 2.864s

OK                                                             有件事我未曾讲过,那就是默认情况下正则表达式大小写敏感。 由于正则表达式 romanNumeralPattern 是以大写字母构造的,re.search 将拒绝不全部是大写字母构成的输入。 因此大写输入的检查就通过了。
  更重要的是,无效输入测试也通过了。 例如,前面那个测试用例需要检查 MCMC 之类的情形。 正如你所见,这不匹配正则表达式, 因此 fromRoman 引发一个测试用例正在等待的 InvalidRomanNumeralError 异常,所以测试通过了。
  事实上,所有的无效输入测试都通过了。 正则表达式捕捉了所有你在编写测试用例时所能预见的所有情况。
  最终迎来了 “OK”这个平淡的“年度大奖”,所有测试都通过后 unittest 模块就会输出它。


当所有测试都通过了,停止编程。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
276#
 楼主| 发表于 2006-7-19 00:38 | 只看该作者
第 15 章 重构
15.1. 处理 bugs
15.2. 应对需求变化
15.3. 重构
15.4. 后记
15.5. 小结
15.1. 处理 bugs
尽管你很努力地编写全面的单元测试,但是 bug 还是会出现。 我所说的 “bug” 是什么呢? Bug 是你还没有编写的测试用例。

例 15.1. 关于 Bug
>>> import roman5
>>> roman5.fromRoman(""
0  在前面的 章节中 你注意到一个空字符串会匹配那个检查罗马数字有效性的正则表达式了吗? 对于最终版本中的正则表达式这一点仍然没有改变。这就是一个 Bug ,你希望空字符串能够像其他无效的罗马数字表示一样引发 InvalidRomanNumeralError 异常。

在重现这个 Bug 并修改它之前你应该编写一个会失败的测试用例来说明它。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
277#
 楼主| 发表于 2006-7-19 00:38 | 只看该作者
例 15.2. 测试 bug (romantest61.py)

class FromRomanBadInput(unittest.TestCase):                                      

    # previous test cases omitted for clarity (they haven't changed)

    def testBlank(self):
        """fromRoman should fail with blank string"""
        self.assertRaises(roman.InvalidRomanNumeralError, roman.fromRoman, ""
  这里很简单。以空字符串调用 fromRoman 并确保它会引发一个 InvalidRomanNumeralError 异常。 难点在于找出 Bug,既然你已经知道它了,测试就简单了。

因为你的代码存在一个 Bug,并且你编写了测试这个 Bug 的测试用例,所以测试用例将会失败:

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
278#
 楼主| 发表于 2006-7-19 00:39 | 只看该作者
例 15.3. 用 romantest61.py 测试 roman61.py 的结果
fromRoman should only accept uppercase input ... ok
toRoman should always return uppercase ... ok
fromRoman should fail with blank string ... FAIL
fromRoman should fail with malformed antecedents ... ok
fromRoman should fail with repeated pairs of numerals ... ok
fromRoman should fail with too many repeated numerals ... ok
fromRoman should give known result with known input ... ok
toRoman should give known result with known input ... ok
fromRoman(toRoman(n))==n for all n ... ok
toRoman should fail with non-integer input ... ok
toRoman should fail with negative input ... ok
toRoman should fail with large input ... ok
toRoman should fail with 0 input ... ok

======================================================================
FAIL: fromRoman should fail with blank string
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\docbook\dip\py\roman\stage6\romantest61.py", line 137, in testBlank
    self.assertRaises(roman61.InvalidRomanNumeralError, roman61.fromRoman, ""
  File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
    raise self.failureException, excName
AssertionError: InvalidRomanNumeralError
----------------------------------------------------------------------
Ran 13 tests in 2.864s

FAILED (failures=1)现在 你可以修改这个 Bug了。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
279#
 楼主| 发表于 2006-7-19 00:39 | 只看该作者
例 15.4. 修改 Bug (roman62.py)
这个文件可以在例子目录下的 py/roman/stage6/ 目录中找到。


def fromRoman(s):
    """convert Roman numeral to integer"""
    if not s:
        raise InvalidRomanNumeralError, 'Input can not be blank'
    if not re.search(romanNumeralPattern, s):
        raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s

    result = 0
    index = 0
    for numeral, integer in romanNumeralMap:
        while s[index:index+len(numeral)] == numeral:
            result += integer
            index += len(numeral)
    return result
  只需要两行代码:一行直接检查空字符串和一行 raise 语句。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
280#
 楼主| 发表于 2006-7-19 00:39 | 只看该作者
例 15.5. 用 romantest62.py 测试 roman62.py 的结果
fromRoman should only accept uppercase input ... ok
toRoman should always return uppercase ... ok
fromRoman should fail with blank string ... ok
fromRoman should fail with malformed antecedents ... ok
fromRoman should fail with repeated pairs of numerals ... ok
fromRoman should fail with too many repeated numerals ... ok
fromRoman should give known result with known input ... ok
toRoman should give known result with known input ... ok
fromRoman(toRoman(n))==n for all n ... ok
toRoman should fail with non-integer input ... ok
toRoman should fail with negative input ... ok
toRoman should fail with large input ... ok
toRoman should fail with 0 input ... ok

----------------------------------------------------------------------
Ran 13 tests in 2.834s

OK   空字符串测试用例现在通过了,说明 Bug 被改正了。
  所有其他测试用例依然通过,证明这个 Bug 修正没有影响到其他部分。 不需要再编程了。

这样编程,并没有令 Bug 修正变得简单。 简单的 Bug (就像这一个)需要简单的测试用例,复杂 Bug 则需要复杂的测试用例。 在测试为核心的氛围下,这 好像 延长了修正 Bug 的时间,因为你需要先贴切地描述出 Bug (编写测试用例)然后才去修正它。 如果测试用例没能正确通过,你需要思量这个修改错了还是测试用例本身出现了 Bug。无论如何,从长远上讲,这样在测试代码和代码之间的反复是值得的,因为这样会使 Bug 在第一时间就被修正的可能性大大提高。 而且,由于任何新的更改后你都可以轻易地重新运行 所有 测试用例,新代码破坏老代码的机会也变得微乎其微。 今天的单元测试就是明天的回归测试(regression test)。

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表