pytest-mock快速入门


这是pytest-mock的简要介绍。

模拟

的意义

如果要测试

模块或flask应用程序,通常会在pytest中编写测试用例。
但是,如果您的测试包括与HTTP和外界通信的过程,该怎么办?
您可以在测试期间创建端点,也可以在测试期间创建if分支。但是,在创建端点时,存在必须更改后端的问题,如果存在,则实现会变得复杂。
就是pytest-mock出现的地方。

什么是pytest-mock?

unittest.mock的薄包装。这是一个模拟现有库和功能(现有产品的模型)的插件库,您可以避免在pytest期间模拟对象的实际行为。例如,如果测试目标使用执行HTTP POST的库,则可以通过将其替换为模拟来避免实际的POST操作。如果您咀嚼得更多,它会显示"就像在张贴,但实际上并没有在张贴"。

如何安装pytest-mock

1
pip install pytest-mock

之后,只需在pytest中调用参数mocker即可使用模拟程序。

补丁

首先,我将介绍一个没有pytest-mock的测试。

1
2
3
4
5
6
mock_project
├── __init__.py
├── some_file.py
└── tests
    ├── __init__.py
    └── test_some_file.py

some_file.py

1
2
3
from random import random
def generate_random():
    return random()

测试/ test_some_file.py

1
2
3
4
5
import pytest
from mock_project import some_file
class TestA:
    def test_01(self):
        assert some_file.generate_random() == 1

这肯定会下降。即使您实际上随机生成数字,变成1的可能性也非常小。
那我们该怎么办?

在这里,我们将使用一个模拟。

模拟时,您需要指定要导入到的对象。

fake_random模拟some_file

下的random

测试/ test_some_file.py

1
2
3
4
5
6
7
8
9
10
import pytest
import some_file

def fake_random():
    return 1

class TestA:
    def test_01(self, mocker):
        mocker.patch.object(some_file,"random",fake_random)
        assert some_file.generate_random() == 1

实际操作如下。 (已处理)

1
2
3
4
5
6
7
>> pytest test_mock.py
=========== test session starts ===========
platform darwin -- Python 3.7.3                                                                                    

tests/test_some_file.py . [100%]

=========== 1 passed in 0.79s ===========

另一个补丁

mocker.patch.object令人惊讶的是,有一种方法可以使用mocker.patch进行模拟。
mocker.patch.object输入模块作为第一个参数,而mocker.patch输入字符串。
请参见下面的代码。

测试/ test_some_file.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pytest
from mock_project import some_file
import random

# SAMPLE ONE
def fake_random():
    return 1

class TestA:
    def test_01(self, mocker):
        mocker.patch.object(some_file,"random",fake_random)
        assert some_file.generate_random() == 1

    def test_02(self, mocker):
        mocker.patch("mock_project.some_file.random",fake_random)
        assert some_file.generate_random() == 1

执行时,如下所示。

1
2
3
4
5
6
7
>> pytest test_mock.py
=========== test session starts ===========
platform darwin -- Python 3.7.3                                                                                    

tests/test_some_file.py .. [100%]

=========== 1 passed in 0.79s ===========

符合预期。
两者都可以做相同的事情,因此使用哪种取决于情况,也取决于您的口味,但是如果我选择,我会尽可能选择mocker.patch.object

函数模拟

也可以模拟具有

参数的函数。

some_file.py

1
2
3
4
5
def amplify_10(x):
    return 10 * x

def process_10(x):
    return amplify_10(x)

测试/ test_some_file.py

1
2
3
4
5
6
7
8
9
10
11
12
import some_file
import random

def fake_amplify_10(x):
    return  int(x/10)

class TestB:

    def test_01(self, mocker):
        mocker.patch.object(some_file,"amplify_10",fake_amplify_10)
        assert some_file.process_10(10) == 1
        print("amplify test")

执行时,如下所示。

1
2
3
4
5
6
7
>> pytest test_mock.py
=========== test session starts ===========
platform darwin -- Python 3.7.3                                                                                    

tests/test_some_file.py . [100%]

=========== 1 passed in 0.60s ===========

类或库模拟

也可以模拟其他文件中的类和模块,如下所示。

some_file.py

1
2
3
4
import MeCab
def parse_sent(x):
    mecab = MeCab.Tagger("-d /tmp/xxxxx")
    return mecab.parse(x).split()

测试/ test_some_file.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TestC:

    def test_01(self, mocker):
        mock_MeCab = mocker.Mock()
        mock_MeCab_Tagger = mocker.Mock()

        def fake_parse(x):
            return "a b c"
        mock_MeCab_Tagger.parse = fake_parse

        mock_MeCab.Tagger = mocker.Mock(return_value=mock_MeCab_Tagger)

        mocker.patch.object(some_file,"MeCab",mock_MeCab)

        res = some_file.parse_sent("ハロー、今日わ")

        assert res == ["a","b","c"]

如果也执行此操作,将如下所示。

1
2
3
4
5
6
7
>> pytest test_mock.py
=========== test session starts ===========
platform darwin -- Python 3.7.3                                                                                    

tests/test_some_file.py . [100%]

=========== 1 passed in 0.58s ===========

为对此进行解释,此处出现的mocker.Mock中的return_value表示调用时返回的内容。

1
2
x=mocker.Mock(return_value="hi")
print(x("zzzzzzz")) # hi

换句话说,如果您逐一查看上述类,它将如下所示。

some_file.py

1
2
3
4
5
6
7
# MeCab -> mock_MeCab
mecab = mock_MeCab.Tagger("-d /tmp/xxxxx")
# mock_MeCab.Tagger -> mocker.Mock(return_value=mock_MeCab_Tagger) -> mock_instance
mecab = mocker.Mock(return_value=mock_MeCab_Tagger)("-d /tmp/xxxxx") # 2つ目の引数はcall
# mock_instance("xxx") -> mock_MeCab_Tagger
mecab.parse = mock_MeCab_Tagger.parse
mecab.parse = fake_parse

所以some_file.py执行以下操作:

some_file.py

1
2
def parse_sent(x):
    return fake_parse(x).split()

摘要

我如上所述介绍了pytest-mock。这次我们引入了覆盖功能和覆盖模块,但是您还可以记录它被调用了多少次以及被调用了哪些参数。如果您有机会使用它,请尝试一下。