Python Testing Cookbook
读书笔记
python
testing
Chapter 1: Using Unittest To Develop Basic Tests
配置虚拟环境
在开始写代码测试前,先创建一个独立的测试开发环境,这样可以避免各种包和现有开发环境互相影响,适合进行测试。
一般可以通过virtualenv
来创建虚拟环境,这里是官方文档和一篇写得比较好的中文版指南。如果你和我一样使用Anaconda
的Python发行版的话,可以使用conda
create
命令来进行操作,指南戳这里。
Anaconda 是一个用来进行大规模数据处理,预测分析和科学计算的Python发行包,里面内置了iPython,NumPy,SciPy等近200种常用包,如果你用python用来做这些事情比较多的话,建议可以直接下载这个。官方地址:https://store.continuum.io/cshop/anaconda/
Asserting the basics
使用例子:
class RomanNumeralConverter(object):
def __init__(self, roman_numeral):
self.roman_numeral = roman_numeral
self.digit_map = {"M":1000, "D":500, "C":100, "L":50, "X":10, "V":5, "I":1}
def convert_to_decimal(self):
val = 0
for char in self.roman_numeral:
val += self.digit_map[char]
return val
import unittest
class RomanNumeralConverterTest(unittest.TestCase):
def test_parsing_millenia(self):
value = RomanNumeralConverter("M")
self.assertEquals(1000, value.convert_to_decimal())
def test_parsing_century(self):
value = RomanNumeralConverter("C")
self.assertEquals(100, value.convert_to_decimal())
def test_parsing_half_century(self):
value = RomanNumeralConverter("L")
self.assertEquals(50, value.convert_to_decimal())
def test_parsing_decade(self):
value = RomanNumeralConverter("X")
self.assertEquals(10, value.convert_to_decimal())
def test_parsing_half_decade(self):
value = RomanNumeralConverter("V")
self.assertEquals(5, value.convert_to_decimal())
def test_parsing_one(self):
value = RomanNumeralConverter("I")
self.assertEquals(1, value.convert_to_decimal())
def test_empty_roman_numeral(self):
value = RomanNumeralConverter("")
self.assertTrue(value.convert_to_decimal() == 0)
self.assertFalse(value.convert_to_decimal() > 0)
def test_no_roman_numeral(self):
value = RomanNumeralConverter(None)
self.assertRaises(TypeError, value.convert_to_decimal)
if __name__ == "__main__":
unittest.main()
-
使用方法
- 建立测试类,命名方式为要测试的类名+Test,并继承unittest类,如
ClassXxxTest(unittest.TestCase)
- 在测试类中建立测试方法,如
test_xxx_xxx
方法 - 在类外
import unittest
,并在主程序入口执行unittest.main()
- 建立测试类,命名方式为要测试的类名+Test,并继承unittest类,如
-
基本的
assert
语句:
assertEquals(first, second[, msg])
assertTrue(expression[, msg])
assertFalse(expression[, msg])
assertRaises(exception, callable, ...)
-
尽量使用
assertEquals
语句而非assertTrue
和assertFalse
,因为其他几个只会报错,而assertEquals
可以显示两个值分别是多少,提供了更多的信息。 -
Unittest可以使用
self.fail([msg])
来产生失败测试,但尽量使用assert
语句改写,因为这个语句在测试正确的情况下用不到。
使用setUp
和tearDown
函数在测试前后进行有关处理
如需要每个测试都需要新建实例进行初始化操作的话,可以定义在setUp
中;需要在测试中打开文件的话,在tearDown
中使用close
方法。
将测试类打包成test suite
进行测试
if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromTestCase( \
RomanNumeralConverterTest)
unittest.TextTestRunner(verbosity=2).run(suite)
在测试方法中插入注释信息,在每一次该方法运行和失败时显示
def test_parsing_century(self):
"This test method is coded to fail for demo."
value = RomanNumeralConverter("C")
self.assertEquals(10, value.convert_to_decimal())
运行一部分测试用例
当测试例子变得很多的时候,每一次都全部运行需要花费很长的时间,此时我们可以用这个方法运行一部分测试用例。
if __name__ == "__main__":
import sys
suite = unittest.TestSuite()
if len(sys.argv) == 1:
suite = unittest.TestLoader().loadTestsFromTestCase(\
RomanNumeralConverterTest)
else:
for test_name in sys.argv[1:]:
suite.addTest(\
RomanNumeralConverterTest(test_name))
unittest.TextTestRunner(verbosity=2).run(suite)
用独立的test文件测试几个test suite
这是将几个test suite都放在主程序中
if __name__ == "__main__":
import unittest
from recipe5 import *
suite1 = unittest.TestLoader().loadTestsFromTestCase( \
RomanNumeralConverterTest)
suite2 = unittest.TestLoader().loadTestsFromTestCase( \
RomanNumeralComboTest)
suite = unittest.TestSuite([suite1, suite2])
unittest.TextTestRunner(verbosity=2).run(suite)
还可以将几个不同的test suite定义在测试模块中
def combos():
return unittest.TestSuite(map(RomanNumeralConverterTest,\
["test_combo1", "test_combo2", "test_combo3"]))
def all():
return unittest.TestLoader().loadTestsFromTestCase(\
RomanNumeralConverterTest)
用以下方法调用所有的suite,如需调用某一个或某几个,参照修改即可。不同的suite可以用来实现不同功能的测试。
if __name__ == "__main__":
for suite_func in [combos, all]:
print "Running test suite '%s'" % suite_func.func_name
suite = suite_func()
unittest.TextTestRunner(verbosity=2).run(suite)
将老的assert
测试代码改成单元测试代码
这是老的测试类
class RomanNumeralTester(object):
def __init__(self):
self.cvt = RomanNumeralConverter()
def simple_test(self):
print "+++ Converting M to 1000"
assert self.cvt.convert_to_decimal("M") == 1000
通过unittest.FunctionTestCase
方法将其转换成unittest方法,然后添加到suite里。传统的assert方法在一个assert失败后就会报错退出,改成这种形式后,会将所有的测试用例都测试后才退出,并展示错误信息。
import unittest
if __name__ == "__main__":
tester = RomanNumeralTester()
suite = unittest.TestSuite()
for test in [tester.simple_test, tester.combo_test1, \
tester.combo_test2, tester.other_test]:
testcase = unittest.FunctionTestCase(test)
suite.addTest(testcase)
unittest.TextTestRunner(verbosity=2).run(suite)
将有多个assertion
的复杂测试方法拆散成每次测试一个简单功能的小测试方法
def test_convert_to_decimal(self):
self.assertEquals(0, self.cvt.convert_to_decimal(""))
self.assertEquals(1, self.cvt.convert_to_decimal("I"))
self.assertEquals(2010, self.cvt.convert_to_decimal("MMX"))
self.assertEquals(4000, self.cvt.convert_to_decimal("MMMM"))
应该写成
def test_to_decimal1(self):
self.assertEquals(0, self.cvt.convert_to_decimal(""))
def test_to_decimal2(self):
self.assertEquals(1, self.cvt.convert_to_decimal("I"))
def test_to_decimal3(self):
self.assertEquals(2010, self.cvt.convert_to_decimal("MMX"))
def test_to_decimal4(self):
self.assertEquals(4000, self.cvt.convert_to_decimal("MMMM"))
这样的好处是前者发生错误时只会报一个错,且第一个assert语句出错时不会执行后面的测试,而第二种方法会检测所有用例,并给出详细的错误统计。
如果我们有很多组要测试的值,这里面会出现大量的重复代码,有没有简单点儿的方法呢?我们可以手动改变python的命名空间实现批量加入函数.
先来看一段代码:
def make_add(n):
def func(x):
return x+n
return func
if __name__ == '__main__':
for i in xrange(1,10):
locals()['add_%d'%i] = make_add(i)
print add_1(7)
print add_9(19)
在python中函数可以作为参数传递,所以make_add
方法可以生成一个加n的函数返回。而在主程序的循环里,我们将add_n
方法通过make_add
函数来生成,再通过加入locals()
添加到本地命名空间,这相当于在本地创建了从add_1
到add_9
的9个函数。
这个搞明白后,就可以动手改写前面的代码了。
v_s = [
(1000, "M"),
(100, "C"),
(50, "L"),
(10, "X"),
(5, "V"),
(1, "I"),
]
def make_test(v, s):
def func(self):
value = RomanNumeralConverter(s)
self.assertEquals(v, value.convert_to_decimal())
return func
for for i, (j, k) in enumerate(v_s, 1):
locals()['test_to_decimal%d' % i] = make_test(v, s)
这段代码可以实现前面第二种写法的功能,当你想要添加新的测试用例时,只需在列表v_s
中添加即可。
通过迭代实现批量测试
当测试用例很多的时候,还可以使用下面的方法实现批量添加。我们自己写了一个生成assert语句的函数。但这种情况类似于上面讲过的第一种方法,即将很多assert
语句写在了同一个测试函数中,如果有一个发生错误,它后面的例子都不会被测试。
def test_bad_inputs(self):
r = self.cvt.convert_to_roman
d = self.cvt.convert_to_decimal
edges = [("equals", r, "", None),\
("equals", r, "I", 1.2),\
("raises", d, TypeError, None),\
("raises", d, TypeError, 1.2)\
]
[self.checkout_edge(edge) for edge in edges]
def checkout_edge(self, edge):
if edge[0] == "equals":
f, output, input = edge[1], edge[2], edge[3]
print("Converting %s to %s..." % (input, output))
self.assertEquals(output, f(input))
elif edge[0] == "raises":
f, exception, args = edge[1], edge[2], edge[3:]
print("Converting %s, expecting %s" % \
(args, exception))
self.assertRaises(exception, f, *args)