先看一段代码:
import unittest #导入此模块,用于单元测试 class MyUnitTest(unittest.TestCase): #TestCase为测试用例的父类 def setUp(self): print("之前运行..") def tearDown(self): print("之后运行.") print("~" * 10) def test2(self): print("这是test2") self.assertEqual(1 + 1, 3) #用于参数是否相等判断 def test1(self): print("这是test1") self.assertTrue(1 + 1 == 2) #用于真假判断 def willnotbecalled(self): print("这个方法不会自动调用") if __name__ == "__main__": unittest.main() #加载并运行该测试
测试用例中红色标注的函数在每个蓝色测试函数前运行,黄色标注在每个蓝色测试函数后运行,以test开头的函数都会运行测试。同时提示产生错误的函数。此为unittesst模块的基本用法。
结果如下:
.F之前运行..
这是test1
之后运行.
~~~~~~~~~~
之前运行..
这是test2
之后运行.
~~~~~~~~~~
======================================================================
FAIL: test2 (__main__.MyUnitTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\admin\untitled0.py", line 19, in test2
self.assertEqual(1 + 1, 3)
AssertionError: 2 != 3
----------------------------------------------------------------------
Ran 2 tests in 0.002s
FAILED (failures=1)
如果想跳过其中某项测试,比如说test1,则可以在其前加入注释符 @unittest.skip("说明文字") ,运行,则测试1会被跳过测试。
如果你想预期某项测试提示失败,可在其前加上注释符 @unittest.expectedFailure ,运行,对预期的失败会打印x字母。
手动的测试次数有限效率太低,不容易发现隐藏的问题,所以需要构建自动化测试(比如实现自动运行10000次)模块。
测试套件也可以方便的对测试用例分组,红黄两段代码为创建两个分组,蓝段代码创建两个测试实例(注意蓝色代码不要放到上述定义的测试类中)(addTest灰色为错误用法):
import unittest
class MyUnitTestA(unittest.TestCase):
def test_a1(self):
print("测试套件A的test_a1")
self.assertNotEqual(1 + 1, 3)
def test_a2(self):
print("测试套件A的test_a2")
self.assertTrue(1 + 1 == 2)
def not_called_by_default(self):
print("测试套件A:这个方法不会被默认调用")
class MyUnitTestB(unittest.TestCase):
def test_b1(self):
print("测试套件B的test_b1")
self.assertTrue(4 + 4 == 8)
def test_b2(self):
print("测试套件B的test_b2")
self.assertNotEqual(4 * 4, 15)
def not_called_by_default(self):
print("测试套件B:这个方法不会被默认调用")
# def suite(self):
# print("测试套件内部...")
# suite_a = unittest.makeSuite(MyUnitTestA)
# suite_b = unittest.makeSuite(MyUnitTestB)
# suite_b.addTest(MyUnitTestB, "not_called_by_default") # 加入默认不测试的函数
# return unittest.TestSuite((suite_a, suite_b)) # 返回一个元组,包含之前的两个TestSuite对象
def suite():
print("测试套件内部...")
suite_a = unittest.makeSuite(MyUnitTestA)
suite_b = unittest.makeSuite(MyUnitTestB)
suite_b.addTest(MyUnitTestB('not_called_by_default')) # 加入默认不测试的函数,结果运行了这个测试函数not_called_by_default
return unittest.TestSuite((suite_a, suite_b)) # 返回一个元组,包含之前的两个TestSuite对象
if __name__ == "__main__":
unittest.main(defaultTest='suite')
结果:
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s
OK
测试套件内部...
测试套件A的test_a1
测试套件A的test_a2
测试套件B的test_b1
测试套件B的test_b2
测试套件B:这个方法不会被默认调用
实例:兽人之袭代码基础上添加测试模块,test_wargame.py
import unittest from wargame.knight import Knight from wargame.orcrider import OrcRider from wargame.abstractgameunit import AbstractGameUnit from wargame.gameutils import weighted_random_selection from wargame.hut import Hut from wargame.attackoftheorcs import AttackOfTheOrcs class TestWarGame(unittest.TestCase): def setUp(self): self.knight = Knight() self.enemy = OrcRider() def test_injured_unit_selection(self): for i in range(100): # 测试100次,受伤单位如果不是骑士或兽人,断言错误 injured_unit = weighted_random_selection(self.knight, self.enemy) self.assertIsInstance(injured_unit, AbstractGameUnit, "受伤单位必须是一个AbstractGameUnit变量") if __name__ == '__main__': unittest.main()
因为之前选择单位引入了bug,可能选到None,手动测试不一定能激活,

自动100次测试,就产生了错误:
File "C:\python\lib\unittest\case.py", line 753, in fail raise self.failureException(msg) AssertionError: None is not an instance of <class 'wargame.abstractgameunit.AbstractGameUnit'> : 受伤单位必须是一个AbstractGameUnit变量
如果存在多个测试文件,如:

可以写成批处理脚本,也可以命令行中在工作目录使用命令 python -m unittest discover 来批量执行测试。结果如下:
(venv) G:\pyprojects\shourenzhixi2.0.0>python -m unittest discover ./test/
调用test_hut.test_acquire_hut..
?[1m干得好! 屋子 4 被访问?[0m
.F
======================================================================
FAIL: test_injured_unit_selection (test_wargame.TestWarGame)
----------------------------------------------------------------------
Traceback (most recent call last):
File "G:\pyprojects\shourenzhixi2.0.0\test\test_wargame.py", line 25, in test_injured_unit_selection
self.assertIsInstance(injured_unit, AbstractGameUnit, "受伤单位必须是一个AbstractGameUnit变量")
AssertionError: None is not an instance of <class 'wargame.abstractgameunit.AbstractGameUnit'> : 受伤单位必须是一个AbstractGameUnit变量
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
如何使用unittest.Mock库来单元测试?之前的测试都是用了代码中的实例,Mock可以创建空对象,替换测试程序中的某些部分。有了Mock对象,你可以专注要测试的功能,而不必担心这个功能的依赖的某个函数。
比如进行一个compute()的单元测试,该单元调用了其他一些非常耗时数据处理的函数,如果你知道这些函数提供什么样的信息,就可以用Mock对象模拟定义其行为。
>>> import unittest >>> from unittest.mock import Mock >>> mockobj=Mock() >>> mockobj <Mock id='2492896906112'> >>> mockobj.foo #创建了一个新Mock对象 <Mock name='mock.foo' id='2492904378032'>
>>> mockobj.mock_calls #此时返回空列表
[]
>>> mockobj.foo()
<Mock name='mock.foo()' id='2492904482944'> #创建一个子Mock对象
>>> mockobj.foo2(return_value=20)
<Mock name='mock.foo2()' id='2492904528816'> #再次创建一个Mock子对象
>>> mockobj.mock_calls
[call.foo(), call.foo2(return_value=20)] #列表包含两个对象
以下代码用Mock对象代替原来的类方法的例子,红色表示原方法,黄色表示用mockObj.foo代替原方法MyClassA;用mockObj.foo2代替原方法MyClassA.foo2:
import unittest from unittest.mock import Mock,call class MyClassA: def foo(self): return 100 #返回100 def foo2(self,num): return num+200 #返回foo+200 def compute(self): x1=self.foo() x2=self.foo2(x1) print("x1=%d,x2=%d" %(x1,x2)) result=x1+x2 print("在classA.compute中,result=x1+x2=",result) return result #400 class TestA(unittest.TestCase): def test_compute(self): a=MyClassA() mockObj=Mock() a.foo=mockObj.foo #用mockObj.foo表示a.foo a.foo2=mockObj.foo2 a.foo.return_value=100 # a.foo2.return_value=300 result=a.compute() #此时compute方法内部调用mockObj.foo,mockObj.foo2 self.assertEqual(result,400) test_call_list=mockObj.mock_calls print("测试列表=",test_call_list) reference_call_list=[call.foo(),call.foo2()]#期望引用的形式 self.assertEqual(test_call_list,reference_call_list) if __name__ == '__main__': unittest.main()
Mock库还可以以补丁装饰器的形式改变行为:
格式为:patch(原方法,new=用那个对象替换原方法),例如:import unittest
from unittest.mock import Mock,call class MyClassA: def foo(self): return 100 #返回100 def foo2(self,num): return num+200 #返回foo+200 def compute(self): x1=self.foo() x2=self.foo2(x1) print("x1=%d,x2=%d" %(x1,x2)) result=x1+x2 print("在classA.compute中,result=x1+x2=",result) return result #400 class TestA(unittest.TestCase): def test_compute(self): print("运行补丁。。。") with unittest.mock.patch('__main__.MyClassA.foo',new=Mock(return_value=500)): a=MyClassA() result=a.compute() #MyClassA.compute()中对self.foo()的调用被Mock对象的return_value所代替,x=self.foo()变为了x=500,实际没有调用foo方法
self.assertEqual(result,400) if __name__ == '__main__': unittest.main()
结果:
....FMyUnitTestA.test_a1 MyUnitTestA.test_a2 MyUnitTestB.test_b1 MyUnitTestB.test_b2 运行补丁。。。 x1=500,x2=700 在classA.compute中,result=x1+x2= 1200 ====================================================================== FAIL: test_compute (__main__.TestA) ---------------------------------------------------------------------- Traceback (most recent call last): File "C:\Users\admin\untitled0.py", line 30, in test_compute self.assertEqual(result,400) AssertionError: 1200 != 400 ---------------------------------------------------------------------- Ran 5 tests in 0.004s FAILED (failures=1)
如何检验你单元测试覆盖了多少代码呢?可以借助第三方包coverage。pip安装,能生成测试覆盖率报告。具体查阅coverage文档。
浙公网安备 33010602011771号