unittest 的 Python 单元测试
摘抄自:
Python单元测试——深入理解unittest
Python3 — unittest框架的使用
python单元测试
一、基础概念
unittest官方文档:https://docs.python.org/3.5/library/unittest.html
- TestCase:一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。
- TestSuite:而多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。
- TestLoader:是用来加载TestCase到TestSuite中的,其中有几个loadTestsFrom__()方法,就是从各个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuite实例。
- TextTestRunner:是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。
- TextTestResult:测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息
那么整个流程就是:首先是要写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,整个过程集成在unittest.main模块中
二、测试案例分析
1、示例1
# 这是unittest文档上的例子
import random
import unittest
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
self.seq = range(10)
def test_shuffle(self):
# make sure the shuffled sequence does not lose any elements
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, range(10))
# should raise an exception for an immutable sequence
self.assertRaises(TypeError, random.shuffle, (1,2,3))
def test_choice(self):
element = random.choice(self.seq)
self.assertTrue(element in self.seq)
def test_sample(self):
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
self.assertTrue(element in self.seq)
if __name__ == '__main__':
unittest.main()
小知识:
python assert断言是声明布尔值必须为真的判定,如果发生异常就说明表达式为假。
可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触发异常。
self.assertEqual(a,b,msg=msg) #判断a与.b是否一致,msg类似备注,可以为空
self.assertNotEqual(a,b,msg=msg) #判断a与b是否不一致
self.assertTrue(a,msg=none) #判断a是否为True
self.assertFalse(b,msg=none) #判断b是否为false
继续:
TestSequenceFunctions继承自unittest.TestCase,重写了setUp()方法,并且定义了三个以'test'开头的方法,那这个TestSequenceFunctions类到底是个什么呢?它是一个测试用例,还是三个测试用例?说是三个测试用例的话,它本身继承自TestCase,说是一个测试用例的话,里面又有三个test_*()方法,明显是三个测试用例。其实,我们只要看一些TestLoader是如何加载测试用例的,就一清二楚了,在loader.TestLoader类中有一个loadTestsFromTestCase()方法:
def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all tests cases contained in testCaseClass"""
if issubclass(testCaseClass, suite.TestSuite):
raise TypeError("Test cases should not be derived from TestSuite." \
" Maybe you meant to derive from TestCase?")
testCaseNames = self.getTestCaseNames(testCaseClass)
if not testCaseNames and hasattr(testCaseClass, 'runTest'):
testCaseNames = ['runTest']
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
return loaded_suite
getTestCaseNames()是从TestCase这个类中找所有以“test”开头的方法,然后注意第9行,在构造TestSuite对象时,其参数使用了一个map方法,即对testCaseNames中的每一个元素,使用testCaseClass为其构造对象,其结果是一个TestCase的对象集合,可以用下面的代码来分步说明:
testcases = []
for name in testCaeNames:
testcases.append(TestCase(name))
loaded_suite = self.suiteClass(tuple(testcases))
可见,对每一个以test开头的方法,都为其构建了一个TestCase对象,值得注意的是,如果没有定义test开头的方法,而是将测试代码写到了一个名为runTest的方法中,那么会为该runTest方法构建TestCase对象,如果定义了test开头的方法,就会忽略runTest方法。
也就是说:
每一个以test开头的方法,都会为其构建TestCase对象,也就是说TestSequenceFunctions类中其实定义了三个TestCase,之所以写成这样,是为了方便,因为这几个测试用例的fixture是相同的,如果每一个测试用例单独写成一个TestCase的话,会有很多的冗余代码。
2、示例2
前置条件(setUp)、后置条件(tearDown)和Test Suite的使用
import unittest
class MyTestCase(unittest.TestCase): # 继承unittest.TestCase
@classmethod
def setUpClass(cls):
# 必须使用 @classmethod 装饰器,所有test运行前运行一次
print('这是所有case的前置条件')
@classmethod
def tearDownClass(cls):
# 必须使用 @classmethod 装饰器, 所有test运行完后运行一次
print('这是所有case的后置条件')
def setUp(self):
# 每个测试用例执行之前的操作
print('这是每条case的前置条件')
def tearDown(self):
# 每个用例执行之后的操作
print('这是每条case的后置条件')
def test_Third(self): # 测试用例的命名必须以test开头,否则不予执行
print('03: 第三条case')
def test_First(self):
print('01: 第一条case')
@unittest.skip('不执行这条case') # 跳过这条case
def test_Second(self):
print('02: 第二条case')
def test_Fourth(self):
print('04: 第四条case')
if __name__ == '__main__':
# unittest.main() # 使用main()直接运行时,将按case的名称顺序执行
suite = unittest.TestSuite()
suite.addTest(MyTestCase("test_Third")) # 将需要执行的case添加到Test Suite中,没有添加的不会被执行
suite.addTest(MyTestCase("test_Second"))
suite.addTest(MyTestCase("test_First"))
runner = unittest.TextTestRunner()
runner.run(suite) # 将根据case添加的先后顺序执行
结果
这是所有case的前置条件
这是每条case的前置条件
03: 第三条case
这是每条case的后置条件
这是每条case的前置条件
01: 第一条case
这是每条case的后置条件
这是所有case的后置条件
3、注意
如果你使用的是PyCharm,那么在使用unittest测试的时候,会发现你使用了addText,执行顺序还是默认的执行顺序(按照ASCII执行),这是IDE工具的问题,解决方法如下:
1、点击pycharm的右上角下拉菜单,点击Edit configurations
2、将Python tests里的对应文件的py.test for...或者unittest for...的文件删除(选中后点击左上角的减号)
3、点击 +,在下拉菜单中选择Python,然后在右边的script path里...选中所要运行的文件
4、最后点击ok即可,再在所要运行的文件处(最好是main处)点击右键就会发现run unittest变成了run
三、生成测试报告(python2版本)
pip install html-testRunner
import unittest
import HTMLTestRunner
class MyTestCase(unittest.TestCase): # 继承unittest.TestCase
@classmethod
def setUpClass(cls):
# 必须使用 @classmethod 装饰器,所有test运行前运行一次
print('这是所有case的前置条件')
@classmethod
def tearDownClass(cls):
# 必须使用 @classmethod 装饰器, 所有test运行完后运行一次
print('这是所有case的后置条件')
def setUp(self):
# 每个测试用例执行之前的操作
print('这是每条case的前置条件')
def tearDown(self):
# 每个用例执行之后的操作
print('这是每条case的后置条件')
def test_Third(self): # 测试用例的命名必须以test开头,否则不予执行
print('03: 第三条case')
def test_First(self):
print('01: 第一条case')
@unittest.skip('不执行这条case') # 跳过这条case
def test_Second(self):
print('02: 第二条case')
def test_Fourth(self):
print('04: 第四条case')
if __name__ == '__main__':
# unittest.main() # 使用main()直接运行时,将按case的名称顺序执行
suite = unittest.TestSuite()
suite.addTest(MyTestCase("test_Third")) # 将需要执行的case添加到Test Suite中,没有添加的不会被执行
suite.addTest(MyTestCase("test_Second"))
suite.addTest(MyTestCase("test_First"))
f = open('res.html', 'wb') # 打开一个保存结果的html文件
runner = HTMLTestRunner.HTMLTestRunner(stream=f, title='测试报告', description='测试情况')
# 生成执行用例的对象
runner.run(suite)
如果我们有很多个模块,每个模块下面都写了很多python文件,每个python文件里面都有测试用例,那怎么把这个目录下的用例都执行了呢,就要先找到这个目录下的所有python文件,然后找到里面的测试用例,逐个执行,代码如下:
import unittest
import HTMLTestRunner
suite = unittest.TestSuite() # 创建测试套件
# 找到某个目录下所有的以test开头的Python文件里面的测试用例
all_cases = unittest.defaultTestLoader.discover('.', 'test_*.py')
for case in all_cases:
suite.addTests(case) # 把所有的测试用例添加进来
fp = open('res.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='all_tests', description='所有测试情况')
runner.run(suite)
我们在后续进行持续集成的时候,要让代码自动运行,就会用到Jenkins了,但是上面产生的测试报告都是html格式的,Jenkins不认识,就在Jenkins里面显示不出来。那咱们就要产生一些Jenkins认识的测试报告,Jenkins认识xml格式的报告,那咱们就产生xml格式的呗,就需要用一个新的模块,xmlrunner,安装直接 pip install xmlrunner即可,代码如下:
import unittest
import xmlrunner
#导入这个模块
class My(unittest.TestCase):
def test1(self,a,b,c):
self.assertEqual(a+b,c)
if __name__=='__main__':
test_suite = unittest.TestSuite()
test_suite.addTest(unittest.makeSuite(My))
runner = xmlrunner.XMLTestRunner(output='report')#指定报告放的目录
runner.run(test_suite
四、Webtest
这个模块很重要,官网已经写得很详细清楚了,因此这里不再赘述,直接上网址
https://docs.pylonsproject.org/projects/webtest/en/latest/
记得去看哦
上一篇: Java 开发单元测试手册
下一篇: III.软件工程--单元测试
推荐阅读
-
Python 中回调的含义解释
-
Python爬虫--Pycharm写的爬虫程序,爬遍了糗事百科的所有糗事图片,室友看了直呼牛_pycharm创建的爬虫项目(1)--需要这方面系统学习的朋友,可以戳这里免费获取!
-
Python 太棒了,获取一个压缩文件的密码只需要一分钟!
-
数学和英语不好,学习 Python 难吗?看完这篇文章,相信你会坚定自己的选择!
-
你玩过这些用 Python 编写的超棒程序/脚本吗?
-
用于计算人名出现次数最多的 Python 代码 用于计算人名出现次数的 python 代码
-
全面解释 python @property 的用法和含义
-
详细解释 Python 中的 __getitem__ 方法和切片对象
-
刷问卷调查星型股票的 python 实现(面向对象)
-
学习 python 后的经验教训 学习 python 后的经验教训