[python] Mock单元测试总结

Python写单元大多数都会用到unittest和mock,测试代码覆盖率都会用到coverage

Unittest

unittest.mock 官网 https://docs.python.org/3/library/unittest.mock.html

unittest就不详细介绍了,注意几点:

  • 测试类继承unittest.TestCase
  • 测试类、测试方法名字最好以test开头,很多工具能根据名字来自动运行,很方便
  • 测试类里面的setUp/tearDown会在每个case执行之前/之后执行,setUpClass/tearDownClass加上@classmethod在整个测试类开始和结束的时候执行
  • 测试文件的main函数里面加上unittest.main(),就可以直接用python命令运行了

Mock

在这里插入图片描述

单元测试里面比较精髓的就是mock了,介绍几种常见的场景:

1. Mock一个函数 patch

其实有好几种方法,个人比较推荐下面这种,看上去很清晰:

mock.patch

def multiple(a, b):
	return a*b

class TestProducer(unittest.TestCase):
	def setUp(self):
		self.calculator = Calculator()

	@mock.patch('multiple')
	def test_multiple(self, mock_multiple):
		mock_multiple.return_value = 3
		self.assertEqual(multiple(8, 14), 3)

mock.patch 返回一个对象, 可以通过 start/stop 方法来应用

class DemoTestCase(unittest.TestCase):
    def setUp(self):
        super(DemoTestCase, self).setUp()
        self._patcher = mock.patch("pkg.mod.dep_mod.func", autospec=True)
        self._func_mock = self._patcher.start()
        self._func_mock.return_value = 1

    def tearDown(self):
        super(DemoTestCase, self).tearDown()
        # cleanup
        self._patcher.stop()

    def test_demo(self):
        # real test code
        self._func_mock.assert_called_with(arg1, arg2)

使用装饰器和上下文管理器

装饰器:

# -*- coding: utf-8 -*-
from unittest.mock import patch

from strategy.module import ProductionClass


@patch('strategy.module.ProductionClass.method', return_value=3)
def test(method):
    print(ProductionClass().method(3, 4, key='value'))
    method.assert_called_with(3, 4, key='value')


test()

上下文管理器:

# -*- coding: utf-8 -*-
from unittest.mock import patch

from strategy.module import ProductionClass


def test():
    with patch.object(ProductionClass, 'method', return_value=3) as mock_method:
        print(ProductionClass().method(3, 4, key='value'))
        mock_method.assert_called_with(3, 4, key='value')

test()

对于其他对象,比如函数,只需要将 patch 中的路径变为函数的路径即可。

2. Mock一个对象里面的方法 object

mock.patch.object

class Calculator(object):
    def add(self, a, b):
        return a+b


class TestProducer(unittest.TestCase):
    def setUp(self):
        self.calculator = Calculator()

    @mock.patch.object(Calculator, 'add')
    def test_add(self, mock_add):
        mock_add.return_value = 3
        self.assertEqual(self.calculator.add(8, 14), 3)

3. Mock的函数每次被调用返回不同的值 side_effect

让Mock的函数每次被调用返回不同的值,而1,2中的方法每次调用都会返回同样的值

class TestProducer(unittest.TestCase):
    @mock.patch.object(Calculator, 'add')
    def test_effect(self, mock_add):
        mock_add.side_effect = [1, 2, 3]
        self.assertEqual(self.calculator.add(8, 14), 1)
        self.assertEqual(self.calculator.add(8, 14), 2)
        self.assertEqual(self.calculator.add(8, 14), 3)

4. 让Mock的函数抛出exception

def is_error(self):
    try:
        os.mkdir("11")
        return False
    except Exception as e:
        return True   


class TestProducer(unittest.TestCase):
    @mock.patch('os.mkdir')
    def test_exception(self, mock_mkdir):
        mock_mkdir.side_effect = Exception
        self.assertEqual(self.calculator.is_error(), True)

side_effect 可以是一个函数、iterable 或 exception(类或实例)。

关于更多详情见:

https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect

5. Mock多个函数,主要是注意顺序

​ 参数由下到上

@mock.patch.object(Calculator, 'add')
@mock.patch('test_unit.multiple')
def test_both(self, mock_multiple, mock_add):
    mock_add.return_value = 1
    mock_multiple.return_value = 2
    self.assertEqual(self.calculator.add(8, 14), 1)
    self.assertEqual(multiple(8, 14), 2)

6. Mock contextlib 上下文管理

如果当前单元测试仅有部分测试依赖该组件也可以通过上下文管理的方式进行管理, 更加灵活.

import contextlib

class DemoTestCase(unittest.TestCase):
    @contextlib.contextmanger
    def _mock_context(self):
        patcher = mock.patch("pkg.mod.dep_mod.func", autospec=True)
        try:
            func_mock = patcher.start()
            func_mock.return_value = 1
            yield func_mock
        finally:
            patcher.stop()

    def test_demo(self):
        with self._mock_context() as func_mock:
            # real test code
            func_mock.assert_called_with(arg1, arg2)

7. Mock和MagicMock的区别

​ 详解:

​ https://segmentfault.com/a/1190000008742467

​ https://blog.csdn.net/qq_16077957/article/details/80023068

8. pytest fixture

我们写单元测试时,有些涉及到复杂的功能、数据库等待session范围,需要使用到一些高级特性了

详解:

https://www.cnblogs.com/huizaia/p/10331469.html

https://www.cnblogs.com/linuxchao/p/linuxchao-pytest-fixture.html

9. pytest 多组参数 parametrize

@pytest.mark.parametrize(“参数名”,列表数据)

参数名:用来接收每一项数据,并作为测试用例的参数。
列表数据:一组测试数据。

@pytest.mark.parametrize("a, b, c", [(1, 2, 3), (4, 5, 9)])
def test_add01(a, b, c):
    res = a + b
    assert res == c

Coverage

打命令coverage加测试文件,就可以得到覆盖率,可以生成html格式的报告,每次运行一个文件都会生成一个.coverage文件,需要将combine所有结果才能得到一个完整的报告。

具体的命令参数参看:http://nedbatchelder.com/code/coverage/cmd.html

更加有用的是配置文件,参看:http://nedbatchelder.com/code/coverage/config.html

参考:https://devjuntuhome.wordpress.com/2020/03/03/python%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E4%B9%8Bmock%E7%AF%87/

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Moke丶青

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值