(1)roman.py
# -*-coding:utf-8-*-
"""
this mode implements two functions, one implements the change from integer to roman numeral,
another implements the opposite.
"""
"""
functional requirements:
(1)toroman应该能将1到3999之间的整数转换成对应的罗马数字
(2)toroman在遇到1到3999以外的整数应该失败
(3)toroman在遇到非整数时应该失败
(4)fromroman应该能将1到3999之间有效的罗马数字转换对应的成阿拉伯数字
(5)fromroman在遇到格式不正确的罗马数字应该失败
(6)toroman和fromroman应该满足完备性,即fromroman(toroman(n)) == n
(7)toroman应该返回的是全部大写的罗马数字
(8)fromroman应该只接收全部大写的罗马数字
"""
import re
class RomanError(Exception): # 定义异常
pass
class OutOfRangeError(RomanError):
pass
class NotIntegerError(RomanError):
pass
class InvalidRomanNumeralError(RomanError):
pass
# define roman numeral map
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 numeral"""
if not 0 < n < 4000: # 当n=0或者n>3999再或者n为负数时
raise OutOfRangeError
if not int(n) == n: # n为非整数时
raise NotIntegerError
result = ''
for numeral, integer in romanNumeralMap:
while n >= integer:
result += numeral
n -= integer
return result
# 定义有效的正则表达式来检查罗马数字是否合格
romanNumeralPattern = r"^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
result = 0
index = 0
for numeral, integer in romanNumeralMap:
while s[index:index+len(numeral)] == numeral:
result += integer
index += len(numeral)
return result
(2)roman.py模块的单元测试
# -*-coding:utf-8-*-
"""Unit test for roman.py"""
import roman
import unittest
index = 0 # 测试计数
# 正面测试
class KnownValuesToTest(unittest.TestCase):
"""
用已知的样本值来测试toroman和fromroman
"""
knownValues = ((1, 'I'),
(2, 'II'),
(3, 'III'),
(4, 'IV'),
(5, 'V'),
(6, 'VI'),
(7, 'VII'),
(8, 'VIII'),
(9, 'IX'),
(10, 'X'),
(50, 'L'),
(100, 'C'),
(500, 'D'),
(1000, 'M'),
(31, 'XXXI'),
(148, 'CXLVIII'),
(294, 'CCXCIV'),
(312, 'CCCXII'),
(421, 'CDXXI'),
(528, 'DXXVIII'),
(621, 'DCXXI'),
(782, 'DCCLXXXII'),
(870, 'DCCCLXX'),
(941, 'CMXLI'),
(1043, 'MXLIII'),
(1110, 'MCX'),
(1226, 'MCCXXVI'),
(1301, 'MCCCI'),
(1485, 'MCDLXXXV'),
(1509, 'MDIX'),
(1607, 'MDCVII'),
(1754, 'MDCCLIV'),
(1832, 'MDCCCXXXII'),
(1993, 'MCMXCIII'),
(2074, 'MMLXXIV'),
(2152, 'MMCLII'),
(2212, 'MMCCXII'),
(2343, 'MMCCCXLIII'),
(2499, 'MMCDXCIX'),
(2574, 'MMDLXXIV'),
(2646, 'MMDCXLVI'),
(2723, 'MMDCCXXIII'),
(2892, 'MMDCCCXCII'),
(2975, 'MMCMLXXV'),
(3051, 'MMMLI'),
(3185, 'MMMCLXXXV'),
(3250, 'MMMCCL'),
(3313, 'MMMCCCXIII'),
(3408, 'MMMCDVIII'),
(3501, 'MMMDI'),
(3610, 'MMMDCX'),
(3743, 'MMMDCCXLIII'),
(3844, 'MMMDCCCXLIV'),
(3888, 'MMMDCCCLXXXVIII'),
(3940, 'MMMCMXL'),
(3999, 'MMMCMXCIX'))
def TestToRomanKnownValues(self):
"""toroman must give correct result with known input"""
for integer, numeral in self.knownValues:
result = roman.toroman(integer)
self.assertEqual(numeral, result)
global index
index += 1
print('###################'+str(index))
print(1)
def testFromRomanKnownValues(self):
"""fromroman must give correct result with known input"""
for integer, numeral in self.knownValues:
result = roman.fromroman(numeral)
self.assertEqual(integer, result)
global index
index += 1
print('###################'+str(index))
print(2)
# 负面测试
class ToRomanBadInputTest(unittest.TestCase):
"""
检查toroman在遇到大于3999的正整数或0或负数或非整数会不会失败
"""
def testTooLarger(self):
"""toroman should fail with the input larger than 3999"""
self.assertRaises(roman.OutOfRangeError, roman.toroman, 4000)
global index
index += 1
print('###################'+str(index))
print(3)
def testZero(self):
"""toroman should fail if the input is 0"""
self.assertRaises(roman.OutOfRangeError, roman.toroman, 0)
global index
index += 1
print('###################'+str(index))
print(4)
def testNegative(self):
"""toroman should fail with negative input"""
self.assertRaises(roman.OutOfRangeError, roman.toroman, -1)
global index
index += 1
print('###################'+str(index))
print(5)
def testNonInteger(self):
"""toroman shoule fail with no-integer input"""
self.assertRaises(roman.NotIntegerError, roman.toroman, 0.5)
global index
index += 1
print('###################'+str(index))
print(6)
class FromRomanBadInput(unittest.TestCase):
"""
检查fromroman在遇到非法的罗马数字时会不会出错
"""
def testTooManyRepeatedNumeral(self):
"""罗马字符最多允许充数3次,并且V(5),L(50),D(500)只允许重复2次"""
for numeral in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
self.assertRaises(roman.InvalidRomanNumeralError, roman.fromroman, numeral)
global index
index += 1
print('###################'+str(index))
print(7)
def testRepeatedPairs(self):
"""fromroman should fail with repeated pairs of numerals"""
for numeral in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'):
self.assertRaises(roman.InvalidRomanNumeralError, roman.fromroman, numeral)
global index
index += 1
print('###################'+str(index))
print(8)
def testMalformedAntecedent(self):
"""fromroman should fail with malformed antecedents"""
for numeral in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):
self.assertRaises(roman.InvalidRomanNumeralError, roman.fromroman, numeral)
global index
index += 1
print('###################'+str(index))
print(9)
class SanityCheck(unittest.TestCase):
"""检验toroman和fromroman的完备性"""
def testSanity(self):
"""fromroman(toRoman(n))==n for all n"""
for integer in range(1, 4000):
numeral = roman.toroman(integer)
result = roman.fromroman(numeral)
self.assertEqual(integer, result)
global index
index += 1
print('###################'+str(index))
print(10)
class LowerOrUpperCheck(unittest.TestCase):
"""检查toroman是否返回的是全大写罗马数字
检查fromroman是否只能接收全大写罗马数字
"""
def testReturnOfToRoman(self):
"""the return of toroman should be upper roman numeral"""
for integer in range(1, 4000):
result = roman.toroman(integer)
self.assertEqual(result, result.upper())
global index
index += 1
print('###################'+str(index))
print(11)
def testAcceptOfFromRoman(self):
"""the accept of fromroman should be upper roman numeral"""
for integer in range(1, 4000):
result = roman.toroman(integer)
self.assertRaises(roman.InvalidRomanNumeralError, roman.fromroman, result.lower())
global index
index += 1
print('###################'+str(index))
print(12)
if __name__ == '__main__':
# 同时测试多个案例方法一:
# # 构造测试集
# suite = unittest.TestSuite()
# suite.addTest(TestCase("KnownValuesToTest"))
# suite.addTest(TestCase("ToRomanBadInputTest"))
# suite.addTest(TestCase("FromRomanBadInput"))
# suite.addTest(TestCase("SanityCheck"))
# suite.addTest(TestCase("LowerOrUpperCheck"))
# # 执行测试
# runner = unittest.TextTestRunner()
# runner.run(suite)
# 同时测试多个案例方法二:
# 此用法可以同时测试多个类
# suite1 = unittest.TestLoader().loadTestsFromTestCase(KnownValuesToTest)
# suite2 = unittest.TestLoader().loadTestsFromTestCase(ToRomanBadInputTest)
# suite3 = unittest.TestLoader().loadTestsFromTestCase(FromRomanBadInput)
# suite4 = unittest.TestLoader().loadTestsFromTestCase(SanityCheck)
# suite5 = unittest.TestLoader().loadTestsFromTestCase(LowerOrUpperCheck)
# suite = unittest.TestSuite([suite1, suite2, suite3, suite4, suite5])
# unittest.TextTestRunner(verbosity=5).run(suite)
# 同时测试多个案例方法三:
unittest.main()
(3)总结
用unittest模块来完成单元测试,每一个测试案例作为一个类出现并且继承unittest.TestCase这个类。
(1)每个测试案例对应的类中可以有多个函数来完成该测试案例中需要测试的内容
(2) 每个测试案例对应的类都继承unittest.TestCase这个类,所以可以使用TestCase类中的方法。比如:①assertEqual(value1, value2)可以测试两个值是否相等,如果不相等则它会抛出异常,测试失败,如果两个值相等则相反。②assertRaises(exception, function, parameter)它接受这几个参数:预期的异常、测试的函数,以及传递给函数的参数。其中exception可以是自己自定义的异常也可以是Exception类中定义好的异常,function必须是函数的引用如只写函数名而不能加括号。assertRaises()函数的作用是判断给定的function中有没有引发给定的异常,如果有则测试成功,如果没有则测试失败。