python——罗马数字和阿拉伯数字的互相转换以及单元测试

(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中有没有引发给定的异常,如果有则测试成功,如果没有则测试失败。

猜你喜欢

转载自blog.csdn.net/watermelon12138/article/details/88258506