.Tang

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

目录:

1.unittest.TestCase中常用的断言方法

  1.1 subTest子测试

  1.2 套件测试

  1.3 批量测试单个用例

2. 加载器

  2.1加载器协议

  2.2.执行器 TestRunner

3.已现成的测试函数用例

4.Mock

Mock对象的参数:

  4.1 return_value

  4.2 side_effect

  4.3 spec

  4.4 wraps

MagicMock对象额外方法

  4.5mock_add_spec方法

5. patch 用模拟对象替换真实对象

6.代码覆盖率coverage


 

1. unittest.TestCase类中的常用的断言方法

方法用途
assertEqual(a, b) 核实 a == b
assertNotEqual(a, b) 核实 a != b
assertTrue(x) 核实 x 为True
assertFalse(x) 核实 x 为False
assertIn(itemlist) 核实itemlist
assertNotIn(itemlist) 核实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>]>
 

2.2.执行器TestTunner

 用于接受用例或套件,执行测试并返回结果

 

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文件

 

posted on 2018-09-05 14:32  .Tang  阅读(909)  评论(0编辑  收藏  举报