# 14.3. `roman.py`, 第 3 阶段
现在 `toRoman` 对于有效的输入 (`1` 到 `3999` 整数) 已能正确工作,是正确处理那些无效输入 (任何其他输入) 的时候了。
## 例 14.6. `roman3.py`
这个文件可以在例子目录下的 `py/roman/stage3/` 目录中找到。
如果您还没有下载本书附带的样例程序, 可以 [下载本程序和其他样例程序](http://www.woodpecker.org.cn/diveintopython/download/diveintopython-exampleszh-cn-5.4b.zip "Download example scripts")。
```
"""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"""
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
def fromRoman(s):
"""convert Roman numeral to integer"""
pass
```
| | |
| --- | --- |
| \[1\] | 这个写法很 Pythonic:一次进行多个比较。这等价于`if not ((0 < n) and (n < 4000))`,但是更容易让人理解。这是在进行范围检查,可以将过大的数、负数和零查出来。 |
| \[2\] | 你使用 `raise` 语句引发自己的异常。你可以引发任何内建异常或者已定义的自定义异常。第二个参数是可选的,如果给定,则会在异常未被处理时显示于追踪信息 (trackback) 之中。 |
| \[3\] | 这是一个非整数检查。非整数无法转化为罗马数字表示。 |
| \[4\] | 函数的其他部分未被更改。 |
## 例 14.7. 观察 `toRoman` 如何处理无效输入
```
>>> import roman3
>>> roman3.toRoman(4000)
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "roman3.py", line 27, in toRoman
raise OutOfRangeError, "number out of range (must be 1..3999)"
OutOfRangeError: number out of range (must be 1..3999)
>>> roman3.toRoman(1.5)
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "roman3.py", line 29, in toRoman
raise NotIntegerError, "non-integers can not be converted"
NotIntegerError: non-integers can not be converted
```
## 例 14.8. 用 `romantest3.py` 测试 `roman3.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 ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok
toRoman should fail with 0 input ... ok
```
| | |
| --- | --- |
| \[1\] | `toRoman` 仍然能通过[已知值测试](testing_for_success.html#roman.testtoromanknownvalues.example "例 13.2. testToRomanKnownValues"),这很令人鼓舞。所有[第 2 阶段](stage_2.html "14.2. roman.py, 第 2 阶段")通过的测试仍然能通过,这说明新的代码没有对原有代码构成任何负面影响。 |
| \[2\] | 更令人振奋的是所有的[无效输入测试](testing_for_failure.html#roman.tobadinput.example "例 13.3. 测试 toRoman 的无效输入")现在都通过了。`testNonInteger` 这个测试能够通过是因为有了 `int(n) <> n` 检查。当一个非整数传递给 `toRoman` 时,`int(n) <> n` 检查出问题并引发 `NotIntegerError` 异常,这正是 `testNonInteger` 所期待的。 |
| \[3\] | `testNegative` 这个测试能够通过是因为 `not (0 < n < 4000)` 检查引发了 `testNegative` 期待的 `OutOfRangeError` 异常。 |
```
======================================================================
FAIL: fromRoman should only accept uppercase input
---------------------------------------------------------------------- Traceback (most recent call last):
File "C:\docbook\dip\py\roman\stage3\romantest3.py", line 156, in testFromRomanCase
roman3.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\stage3\romantest3.py", line 133, in testMalformedAntecedent
self.assertRaises(roman3.InvalidRomanNumeralError, roman3.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\stage3\romantest3.py", line 127, in testRepeatedPairs
self.assertRaises(roman3.InvalidRomanNumeralError, roman3.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\stage3\romantest3.py", line 122, in testTooManyRepeatedNumerals
self.assertRaises(roman3.InvalidRomanNumeralError, roman3.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\stage3\romantest3.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\stage3\romantest3.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 ----------------------------------------------------------------------
Ran 12 tests in 0.401s
FAILED (failures=6)
```
| | |
| --- | --- |
| \[1\] | 你已将失败降至 6 个,而且它们都是关于 `fromRoman` 的:已知值测试、三个独立的无效输入测试,大小写检查和完备性检查。这意味着 `toRoman` 通过了所有可以独立通过的测试 (完备性测试也测试它,但需要 `fromRoman` 编写后一起测试)。这就是说,你应该停止对 `toRoman` 的代码编写。不必再推敲,不必再做额外的检查 “恰到好处”。停下来吧!现在,别再敲键盘了。 |
> 注意
> 全面的单元测试能够告诉你的最重要的事情是什么时候停止编写代码。当一个函数的所有单元测试都通过了,停止编写这个函数。一旦整个模块的单元测试通过了,停止编写这个模块。
- 版权信息
- 第 1 章 安装 Python
- 1.1. 哪一种 Python 适合您?
- 1.2. Windows 上的 Python
- 1.3. Mac OS X 上的 Python
- 1.4. Mac OS 9 上的 Python
- 1.5. RedHat Linux 上的 Python
- 1.6. Debian GNU/Linux 上的 Python
- 1.7. 从源代码安装 Python
- 1.8. 使用 Python 的交互 Shell
- 1.9. 小结
- 第 2 章 第一个 Python 程序
- 2.1. 概览
- 2.2. 函数声明
- 2.3. 文档化函数
- 2.4. 万物皆对象
- 2.5. 代码缩进
- 2.6. 测试模块
- 第 3 章 内置数据类型
- 3.1. Dictionary 介绍
- 3.2. List 介绍
- 3.3. Tuple 介绍
- 3.4. 变量声明
- 3.5. 格式化字符串
- 3.6. 映射 list
- 3.7. 连接 list 与分割字符串
- 3.8. 小结
- 第 4 章 自省的威力
- 4.1. 概览
- 4.2. 使用可选参数和命名参数
- 4.3. 使用 type、str、dir 和其它内置函数
- 4.4. 通过 getattr 获取对象引用
- 4.5. 过滤列表
- 4.6. and 和 or 的特殊性质
- 4.7. 使用 lambda 函数
- 4.8. 全部放在一起
- 4.9. 小结
- 第 5 章 对象和面向对象
- 5.1. 概览
- 5.2. 使用 from _module_ import 导入模块
- 5.3. 类的定义
- 5.4. 类的实例化
- 5.5. 探索 UserDict:一个封装类
- 5.6. 专用类方法
- 5.7. 高级专用类方法
- 5.8. 类属性介绍
- 5.9. 私有函数
- 5.10. 小结
- 第 6 章 异常和文件处理
- 6.1. 异常处理
- 6.2. 与文件对象共事
- 6.3. for 循环
- 6.4. 使用 `sys.modules`
- 6.5. 与目录共事
- 6.6. 全部放在一起
- 6.7. 小结
- 第 7 章 正则表达式
- 7.1. 概览
- 7.2. 个案研究:街道地址
- 7.3. 个案研究:罗马字母
- 7.4. 使用 {n,m} 语法
- 7.5. 松散正则表达式
- 7.6. 个案研究:解析电话号码
- 7.7. 小结
- 第 8 章 HTML 处理
- 8.1. 概览
- 8.2. sgmllib.py 介绍
- 8.3. 从 HTML 文档中提取数据
- 8.4. BaseHTMLProcessor.py 介绍
- 8.5. locals 和 globals
- 8.6. 基于 dictionary 的字符串格式化
- 8.7. 给属性值加引号
- 8.8. dialect.py 介绍
- 8.9. 全部放在一起
- 8.10. 小结
- 第 9 章 XML 处理
- 9.1. 概览
- 9.2. 包
- 9.3. XML 解析
- 9.4. Unicode
- 9.5. 搜索元素
- 9.6. 访问元素属性
- 9.7. Segue [9]
- 第 10 章 脚本和流
- 10.1. 抽象输入源
- 10.2. 标准输入、输出和错误
- 10.3. 查询缓冲节点
- 10.4. 查找节点的直接子节点
- 10.5. 根据节点类型创建不同的处理器
- 10.6. 处理命令行参数
- 10.7. 全部放在一起
- 10.8. 小结
- 第 11 章 HTTP Web 服务
- 11.1. 概览
- 11.2. 避免通过 HTTP 重复地获取数据
- 11.3. HTTP 的特性
- 11.4. 调试 HTTP web 服务
- 11.5. 设置 User-Agent
- 11.6. 处理 Last-Modified 和 ETag
- 11.7. 处理重定向
- 11.8. 处理压缩数据
- 11.9. 全部放在一起
- 11.10. 小结
- 第 12 章 SOAP Web 服务
- 12.1. 概览
- 12.2. 安装 SOAP 库
- 12.3. 步入 SOAP
- 12.4. SOAP 网络服务查错
- 12.5. WSDL 介绍
- 12.6. 以 WSDL 进行 SOAP 内省
- 12.7. 搜索 Google
- 12.8. SOAP 网络服务故障排除
- 12.9. 小结
- 第 13 章 单元测试
- 13.1. 罗马数字程序介绍 II
- 13.2. 深入
- 13.3. romantest.py 介绍
- 13.4. 正面测试 (Testing for success)
- 13.5. 负面测试 (Testing for failure)
- 13.6. 完备性检测 (Testing for sanity)
- 第 14 章 测试优先编程
- 14.1. roman.py, 第 1 阶段
- 14.2. roman.py, 第 2 阶段
- 14.3. roman.py, 第 3 阶段
- 14.4. roman.py, 第 4 阶段
- 14.5. roman.py, 第 5 阶段
- 第 15 章 重构
- 15.1. 处理 bugs
- 15.2. 应对需求变化
- 15.3. 重构
- 15.4. 后记
- 15.5. 小结
- 第 16 章 函数编程
- 16.1. 概览
- 16.2. 找到路径
- 16.3. 重识列表过滤
- 16.4. 重识列表映射
- 16.5. 数据中心思想编程
- 16.6. 动态导入模块
- 16.7. 全部放在一起
- 16.8. 小结
- 第 17 章 动态函数
- 17.1. 概览
- 17.2. plural.py, 第 1 阶段
- 17.3. plural.py, 第 2 阶段
- 17.4. plural.py, 第 3 阶段
- 17.5. plural.py, 第 4 阶段
- 17.6. plural.py, 第 5 阶段
- 17.7. plural.py, 第 6 阶段
- 17.8. 小结
- 第 18 章 性能优化
- 18.1. 概览
- 18.2. 使用 timeit 模块
- 18.3. 优化正则表达式
- 18.4. 优化字典查找
- 18.5. 优化列表操作
- 18.6. 优化字符串操作
- 18.7. 小结
- 附录 A. 进一步阅读
- 附录 B. 五分钟回顾
- 附录 C. 技巧和窍门
- 附录 D. 示例清单
- 附录 E. 修订历史
- 附录 F. 关于本书
- 附录 G. GNU Free Documentation License
- G.0. Preamble
- G.1. Applicability and definitions
- G.2. Verbatim copying
- G.3. Copying in quantity
- G.4. Modifications
- G.5. Combining documents
- G.6. Collections of documents
- G.7. Aggregation with independent works
- G.8. Translation
- G.9. Termination
- G.10. Future revisions of this license
- G.11. How to use this License for your documents
- 附录 H. GNU 自由文档协议
- H.0. 序
- H.1. 适用范围和定义
- H.2. 原样复制
- H.3. 大量复制
- H.4. 修改
- H.5. 合并文档
- H.6. 文档合集
- H.7. 独立著作聚集
- H.8. 翻译
- H.9. 终止协议
- H.10. 协议将来的修订
- H.11. 如何为你的文档使用本协议
- 附录 I. Python license
- I.A. History of the software
- I.B. Terms and conditions for accessing or otherwise using Python
- 附录 J. Python 协议
- J.0. 关于译文的声明
- J.A. 软件的历史
- J.B. 使用 Python 的条款和条件