这是pytest-mock的简要介绍。
模拟
的意义
如果要测试
模块或flask应用程序,通常会在pytest中编写测试用例。
但是,如果您的测试包括与HTTP和外界通信的过程,该怎么办?
您可以在测试期间创建端点,也可以在测试期间创建if分支。但是,在创建端点时,存在必须更改后端的问题,如果存在,则实现会变得复杂。
就是pytest-mock出现的地方。
什么是pytest-mock?
unittest.mock的薄包装。这是一个模拟现有库和功能(现有产品的模型)的插件库,您可以避免在pytest期间模拟对象的实际行为。例如,如果测试目标使用执行HTTP POST的库,则可以通过将其替换为模拟来避免实际的POST操作。如果您咀嚼得更多,它会显示"就像在张贴,但实际上并没有在张贴"。
如何安装pytest-mock
1 | pip install pytest-mock |
之后,只需在pytest中调用参数
补丁
首先,我将介绍一个没有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的可能性也非常小。
那我们该怎么办?
在这里,我们将使用一个模拟。
模拟时,您需要指定要导入到的对象。
用
下的
测试/ 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 =========== |
另一个补丁
请参见下面的代码。
测试/ 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 =========== |
符合预期。
两者都可以做相同的事情,因此使用哪种取决于情况,也取决于您的口味,但是如果我选择,我会尽可能选择
函数模拟
也可以模拟具有
参数的函数。
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 =========== |
为对此进行解释,此处出现的
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
1 2 | def parse_sent(x): return fake_parse(x).split() |
摘要
我如上所述介绍了pytest-mock。这次我们引入了覆盖功能和覆盖模块,但是您还可以记录它被调用了多少次以及被调用了哪些参数。如果您有机会使用它,请尝试一下。