目录:
2.2.执行器 TestRunner
Mock对象的参数:
MagicMock对象额外方法
1. unittest.TestCase类中的常用的断言方法
方法 | 用途 |
---|---|
assertEqual(a, b) | 核实 a == b |
assertNotEqual(a, b) | 核实 a != b |
assertTrue(x) | 核实 x 为True |
assertFalse(x) | 核实 x 为False |
assertIn(item, list) | 核实item在list中 |
assertNotIn(item, list) | 核实item不在list中 |
1.1 子测试:记录错误并测试完所有的代码
class DemoTest(unittest.TestCase): def test_subtest(self): for i in range(5): with self.subTest(name=i): # 子测试参数用于输出 self.assertEqual(i % 2, 0) >>> (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest test_a.DemoTest.test_subtest ====================================================================== FAIL: test_subtest (test_a.DemoTest) (name=1) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/apple/PycharmProjects/work/practice/tests/test_a.py", line 19, in test_subtest self.assertEqual(i % 2, 0) AssertionError: 1 != 0 ====================================================================== FAIL: test_subtest (test_a.DemoTest) (name=3) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/apple/PycharmProjects/work/practice/tests/test_a.py", line 19, in test_subtest self.assertEqual(i % 2, 0) AssertionError: 1 != 0 ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=2)
1.2测试套件:将多个用例或套件的实例组合起来,完成产品功能组级别的测试。
分别为每个参与测试方法创建实例,并加入套件。
class UserTest(unittest.TestCase): def test_user(self): self.assertTrue(True) # 判断是否为真 class CartTest(unittest.TestCase): def test_cart(self): self.assertFalse(False) suite = unittest.TestSuite() suite.addTests((UserTest('test_user'), # 创建实例并加入套件suite CartTest('test_cart'),) ) unittest.TextTestRunner(verbosity=2).run(suite) # TextTestRunner执行器
# code end!! >>> (djProj_py3) appledeMacBook-Air-7:tests apple$ python test_a.py test_user (__main__.UserTest) ... ok test_cart (__main__.CartTest) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
1.3 批量测试单个用例,可通过重写runTest
class TestDamo(unittest.TestCase): def add(self): self.assertTrue(1) def add1(self): self.assertFalse('') def runTest(self): tests = (self.add, self.add1) for test in tests: with self.subTest(t=test): test() >>> (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest -v test_a.py runTest (test_a.TestDamo) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
2.加载器
完整的流程是:1.discover递归目录,查找所有文件名相符的模块。loadTestsFromModule 在模块内获取所有的用例类型,再通过以loadTestsFromTestCase
为用例的全部测试方法创建实例。最终,将之组合成测试套件交给执行器。
注: loadTestsFromTestCase调用了getTestCaseNames查找类型中包含 特定前缀(test)的测试方法,无则选择runTest;
loadTestsFromModule按照加载协议(load_tests),先调用load_tests函数返回自定义测试套件。仅在没有协议实现时,才返回所用的用例类型;
可以自己创建加载器对象或使用默认的defaultTestLoader实例。
class TestDamo(unittest.TestCase): def test_add(self): self.assertTrue(1) def test_add1(self): self.assertFalse('') def runTest(self): self.assertFalse('a') class Test1Damo(unittest.TestCase): def runTest(self): self.assertFalse('')
loader = unittest.defaultTestLoader a = loader.loadTestsFromTestCase(TestDamo) print(a) b = loader.loadTestsFromModule(sys.modules[__name__]) print(b) >>> (djProj_py3) appledeMacBook-Air-7:tests apple$ python test_a.py <unittest.suite.TestSuite tests=[<__main__.TestDamo testMethod=test_add>, <__main__.TestDamo testMethod=test_add1>]> <unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<__main__.Test1Damo testMethod=runTest>]>,
<unittest.suite.TestSuite tests=[<__main__.TestDamo testMethod=test_add>,
<__main__.TestDamo testMethod=test_add1>]>
]>
# 2.1改写加载器协议
def load_tests(loader, standard_tests, pattern): suite = unittest.TestSuite() suite.addTests(map(TestDamo, ('test_add', 'test_add1'))) return suiteb = loader.loadTestsFromModule(sys.modules[__name__])
b = loader.loadTestsFromModule(sys.modules[__name__])
print(b)
>>> (djProj_py3) appledeMacBook-Air-7:tests apple$ python test_a.py
# 改写加载器协议后,只加载了协议指定TestDamo用例
<unittest.suite.TestSuite tests=[<__main__.TestDamo testMethod=test_add>, <__main__.TestDamo testMethod=test_add1>]>
用于接受用例或套件,执行测试并返回结果
3.已现成的测试函数用例
用FunctionTestCase包装,它本身是继承的unittest.TestCase
def test(): assert False result = unittest.FunctionTestCase(test).run() print(result.failures)
>>>
(djProj_py3) appledeMacBook-Air-7:tests apple$ python test_a.py [(<unittest.case.FunctionTestCase tec=<function test at 0x10cc62bf8>>,
'Traceback (most recent call last):\n File "test_a.py", line 99, in test\n assert False\nAssertionError\n')]
4.Mock
Mock以__getattr__拦截被mock替换对象的属性访问,动态创建‘替换对象成员’。 且新建成员同是模拟类型,以实现链式属性设置和访问。
我对mock的理解:测试对象功能尚未完成或者依赖其他环境(例如db),可用mock替换该测试对象并指定返回结果。其作用:先完成测试逻辑,
接触开发次序依赖
4.1 return_value
设置测试对象的返回值
>>> import unittest >>> from unittest.mock import Mock >>> m = Mock() >>> m.func.return_value = 1 >>> import unittest >>> from unittest.mock import Mock >>> m = Mock() >>> def func(a, b):return a+b ... >>> m.func.return_value = 99 >>> func(50, 50) 100 >>> m.func(50, 50) 99
>>> m.func(50, 50, 1)
99
结论:通过Mock对象指定测试用例返回值后,再通过Mock调用测试对象,并不会去执行而是直接返回return_value的值
4.2 side_effect
构造参数side_effect指定可调用对象,迭代器,异常。用来替代return_value返回
# side_effect指定为可调用对象 >>> m = Mock(side_effect=lambda x: x+10) >>> m(1) 11 # side_effect指定迭代器 >>> m = Mock() >>> m.next = Mock(side_effect=[1,2,3]) >>> m.next <Mock name='mock.next' id='4508837816'> >>> m.next() 1 >>> m.next() 2 >>> m.next() 3 >>> m.next() StopIteration # side_effect指定为异常 >>> m.next = Mock(side_effect=KeyError('key error')) >>> m.test = Mock(side_effect=KeyError('key error')) >>> m.test() Traceback (most recent call last): File "<stdin>", line 1, in <module> raise effect KeyError: 'key error' # side_effect的可调用对象返回值为unittest.mock.DEFAULT时,实际返回值为return_value的值 >>> m = Mock(side_effect=lambda x: 100 if x>0 else unittest.mock.DEFAULT, return_value=99) >>> m(1) 100 >>> m(0) 99 # side_effect设置为None时,返回值为return_value >>> m.side_effect = None >>> m(1) 99
4.3 spec
可从列表 或 某个类型提取属性名字,用以约束模拟对象mock。
# 从列表提取属性名字 >>> m = Mock(spec=['a', 'b']) >>> m.a <Mock name='mock.a' id='4508838992'> >>> m.b <Mock name='mock.b' id='4508838712'> >>> m.c AttributeError: Mock object has no attribute 'c' # 从类提取属性名字 >>> class A: ... a = 1 ... def b(self): ... return 2 ... >>> m = Mock(spec=A) >>> m.a <Mock name='mock.a' id='4508836584'> >>> m.b <Mock name='mock.b' id='4508838096'> >>> m.c AttributeError: Mock object has no attribute 'c' # spec参数并不能阻止通过赋值创建属性 >>> m.c = 3 # 创建成功 >>> m.c 3 # spec_set可以阻止赋值创建属性 >>> m = Mock(spec_set=['a']) >>> m.a <Mock name='mock.a' id='4508838264'> >>> m.b AttributeError: Mock object has no attribute 'b' >>> m.b = 3 # 创建失败 AttributeError: Mock object has no attribute 'b' # create_autospec约束参数列表,使mock模拟的对象与测试对象参数一致 >>> m = unittest.mock.create_autospec(A, spec_set=True, instance=True) >>> m.test() TypeError: missing a required argument: 'a' >>> m.test(a=1) TypeError: missing a required argument: 'b' >>> m.test(a=1,b=2) <MagicMock name='mock.test()' id='4508838208'> >>> m.test.return_value = 1 # 指定模拟对象的返回值 >>> m.test(1,2) 1 # 从lambda对象中提取参数,限制模拟对象m.test >>> m = Mock() >>> m.test = unittest.mock.create_autospec(lambda a,b: 2, return_value=1) # 从lambda中提取参数a, b >>> m.test(1) >>> m.test = unittest.mock.create_autospec(lambda a,b: 2, return_value=1) >>> m.test(1,2) TypeError: missing a required argument: 'b' >>> m.test(1,2) 1
4.4 wraps参数
可以将模拟对象的访问传值传递给真是对象, 这样可以在模拟和真实对象间切换,而非删除代码
>>> class A: ... def add(self, a, b):return a+b >>> m = Mock(spec_set=A, wraps=A()) >>> m.add(50, 50) 100 # 但是一旦设置return_value,则不再传递参数给真实对象 >>> m.add.return_value=99 >>> m.add(50, 50) 99
4.5 MagicMock
额外提供mock_add_spec方法,用于调整spec设置
# 重置spec_set的参数(True:spec_set, False:spec) >>> m.mock_add_spec(['a'], True) # 阻止所有属性的访问 = Mock(spec_set=[]) >>> m.mock_add_spec([], True) # 取消spec or spec_set设置 >>> m.mock_add_spec(None)
5. patch
使用patch将真实对象替换成模拟的对象(真是对象 x + y, 模拟对象 a去替换真实对象x,变成 a + y)
测试用例为:
test_a.py
from unittest.mock import patch
import requests
def logic(url): data = requests.get(url=url) return data.status_code
5.1 patch 上下文管理器用法
class DemoTest(unittest.TestCase): def test_1(self): # 设置固定数据 data = SimpleNamespace(url='https://www.baidu.com', code=200) with patch("test_a.logic", lambda url: data.code) as m: # 将test_a.py下的logic方法 用 lambda去替换。 self.assertEqual(m(data.url), data.code) (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest -v test_a.py test_1 (test_a.DemoTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
5.2 patch 装饰器用法
5.2.1 @patch('requests.get') 替换为 get (get自己起的名字)
class DemoTest(unittest.TestCase): @patch('requests.get') def test_1(self, get): data = SimpleNamespace(url='https://www.baidu.com', code=200) get.return_value.status_code = data.code self.assertEqual(logic(data.url), data.code) (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest -v test_a.py test_1 (test_a.DemoTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
5.2.2 将test_a.logic替换成 lambda url: 200
class DemoTest(unittest.TestCase): @patch('test_a.logic', lambda url: 200) # arg: url, return_value: 200 def test_1(self): data = SimpleNamespace(url='https://www.baidu.com', code=200) self.assertEqual(logic(data.url), data.code) (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest -v test_a.py test_1 (test_a.DemoTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
6.coverage代码覆盖率 pip install coverage
pip install coverage (djProj_py3) appledeMacBook-Air-7:tests apple$ coverage run --source . -m unittest test_a.DemoTest # 仅测试当前目录下的文件 . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK (djProj_py3) appledeMacBook-Air-7:tests apple$ coverage report # 测试结果生成文本 (djProj_py3) appledeMacBook-Air-7:tests apple$ coverage html # 测试结果生成HTML文件