python - pytest-mocks 并声明类级别的固定装置

标签 python mocking pytest

我在使用 pytest-mock 和模拟 open 时遇到问题。

我希望测试的代码如下所示:

import re
import os

def get_uid():
    regex = re.compile('Serial\s+:\s*(\w+)')
    uid = "NOT_DEFINED"
    exists = os.path.isfile('/proc/cpuinfo')
    if exists:
        with open('/proc/cpuinfo', 'r') as file:
            cpu_details = file.read()
            uid = regex.search(cpu_details).group(1)
    return uid

所以测试文件是:

import os
import pytest

from cpu_info import uid

@pytest.mark.usefixtures("mocker")
class TestCPUInfo(object):
    def test_no_proc_cpuinfo_file(self):
        mocker.patch(os.path.isfile).return_value(False)
        result = uid.get_uid()
        assert result == "NOT_FOUND"

    def test_no_cpu_info_in_file(self):
        file_data = """
Hardware    : BCM2835
Revision    : a020d3
        """
        mocker.patch('__builtin__.open', mock_open(read_data=file_data))
        result = uid.get_uid()
        assert result == "NOT_DEFINED"

    def test_cpu_info(self):
        file_data = """
Hardware    : BCM2835
Revision    : a020d3
Serial      : 00000000e54cf3fa
        """
        mocker.patch('__builtin__.open', mock_open(read_data=file_data))
        result = uid.get_uid()
        assert result == "00000000e54cf3fa"

测试运行给出:

pytest
======================================= test session starts ========================================
platform linux -- Python 3.5.3, pytest-4.4.1, py-1.8.0, pluggy-0.9.0
rootdir: /home/robertpostill/software/gateway
plugins: mock-1.10.4
collected 3 items

cpu_info/test_cpu_info.py FFF                                                                [100%]

============================================= FAILURES =============================================
______________________________ TestCPUInfo.test_no_proc_cpuingo_file _______________________________

self = <test_cpu_info.TestCPUInfo object at 0x75e6eaf0>

    def test_no_proc_cpuingo_file(self):
>       mocker.patch(os.path.isfile).return_value(False)
E       NameError: name 'mocker' is not defined

cpu_info/test_cpu_info.py:9: NameError
___________________________________ TestCPUInfo.test_no_cpu_info ___________________________________

self = <test_cpu_info.TestCPUInfo object at 0x75e69d70>

        def test_no_cpu_info(self):
            file_data = """
    Hardware    : BCM2835
    Revision    : a020d3
            """
>           mocker.patch('__builtin__.open', mock_open(read_data=file_data))
E           NameError: name 'mocker' is not defined

cpu_info/test_cpu_info.py:18: NameError
____________________________________ TestCPUInfo.test_cpu_info _____________________________________

self = <test_cpu_info.TestCPUInfo object at 0x75e694f0>

        def test_cpu_info(self):
            file_data = """
    Hardware    : BCM2835
    Revision    : a020d3
    Serial      : 00000000e54cf3fa
            """
>           mocker.patch('__builtin__.open', mock_open(read_data=file_data))
E           NameError: name 'mocker' is not defined

cpu_info/test_cpu_info.py:28: NameError
===================================== 3 failed in 0.36 seconds =====================================

我认为我已经正确地声明了模拟装置,但它似乎不是......我做错了什么?

最佳答案

在您的测试中模拟使用并没有太多问题。其实只有两个:

访问mocker固定装置

如果您需要访问 fixture 的返回值,请将其名称包含在测试函数参数中,例如:

class TestCPUInfo:
    def test_no_proc_cpuinfo_file(self, mocker):
        mocker.patch(...)

pytest 在运行测试时会自动将测试参数值映射到固定装置值。

使用mocker.patch

mocker.patch 只是 unittest.mock.patch 的垫片,仅此而已;它的存在只是为了方便,这样您就不必到处导入 unittest.mock.patch 。这意味着 mocker.patchunittest.mock.patch 具有相同的签名,当您对正确使用有疑问时,可以随时查阅 stdlib 的文档。

在您的情况下,mocker.patch(os.path.isfile).return_value(False) 不是 patch 方法的正确用法。来自 docs :

target should be a string in the form 'package.module.ClassName'.

...

patch() takes arbitrary keyword arguments. These will be passed to the Mock (or new_callable) on construction.

这意味着该行

mocker.patch(os.path.isfile).return_value(False)

应该是

mocker.patch('os.path.isfile', return_value=False)

测试行为与实际实现逻辑之间的差异

现在剩下的都是与您的实现有关的错误;您必须调整测试以测试正确的行为或修复实现错误。

示例:

assert result == "NOT_FOUND"

将始终引发,因为代码中甚至不存在“NOT_FOUND”

assert result == "NOT_DEFINED"

将始终引发,因为 uid = "NOT_DEFINED" 将始终被正则表达式搜索结果覆盖,因此永远不会返回。

工作示例

假设您的测试是唯一的事实来源,我修复了上述模拟使用的两个错误,并调整了 get_uid() 的实现以使测试通过:

import os
import re

def get_uid():
    regex = re.compile(r'Serial\s+:\s*(\w+)')
    exists = os.path.isfile('/proc/cpuinfo')
    if not exists:
        return 'NOT_FOUND'
    with open('/proc/cpuinfo', 'r') as file:
        cpu_details = file.read()
        match = regex.search(cpu_details)
        if match is None:
            return 'NOT_DEFINED'
        return match.group(1)

测试:

import pytest
import uid


class TestCPUInfo:

    def test_no_proc_cpuinfo_file(self, mocker):
        mocker.patch('os.path.isfile', return_value=False)
        result = uid.get_uid()
        assert result == "NOT_FOUND"

    def test_no_cpu_info_in_file(self, mocker):
        file_data = """
Hardware    : BCM2835
Revision    : a020d3
        """
    mocker.patch('builtins.open', mocker.mock_open(read_data=file_data))
        result = uid.get_uid()
        assert result == "NOT_DEFINED"

    def test_cpu_info(self, mocker):
        file_data = """
Hardware    : BCM2835
Revision    : a020d3
Serial      : 00000000e54cf3fa
        """
    mocker.patch('builtins.open', mocker.mock_open(read_data=file_data))
        result = uid.get_uid()
        assert result == "00000000e54cf3fa"

请注意,我使用的是 Python 3,因此我无法修补 __builtin__ 并只能求助于修补 builtins;除此之外,代码应该与 Python 2 变体相同。另外,由于无论如何都会使用 mocker,因此我使用了 mocker.mock_open,从而节省了 unittest.mock.mock_open 的额外导入。

关于python - pytest-mocks 并声明类级别的固定装置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55876352/

相关文章:

Python/Tweepy 转发以下 Twitter 帐户

python - Django 播客迁移 : TypeError: __init__() missing 1 required positional argument: 'on_delete'

java - 完全模拟和部分模拟有什么区别?

python - 使用 py.test xdist 控制测试的分布

Python pandas 数据透视/堆栈操作

python - 在 Django 中有效地获取相关模型的数量

c# - 我应该测试 UDP 服务器代码吗?如果是,为什么以及如何测试?

node.js - 使用mockery模拟fs模块

python - 链接pytest装置

python - 测试 pyqt 应用程序 - Qwidget : must construct a qapplication before a qwidget