首页 > 其他分享 >pytest-mock mock的高层封装

pytest-mock mock的高层封装

时间:2023-06-01 12:35:39浏览次数:65  
标签:封装 patch assert pytest mocker test os mock

pytest-mock

pytest-mock是一个pytest的插件,安装即可使用。 它提供了一个名为mocker的fixture,仅在当前测试function或method生效,而不用自行包装。

object

mock一个object,是最常见的需求。 由于function也是一个object,以下以function举例。

import os


def rm(filename):
    os.remove(filename)


def test_rm(mocker):
    filename = 'test.file'
    mocker.patch('os.remove')
    rm(filename)
    os.remove.assert_called_once_with(filename)

这里在给os.remove打了一个patch,让它变成了一个MagicMock。 然后利用assert_called_once_with,查看它是否被调用一次,并且参数为filename

注意:只能对已经存在的东西使用mock。

method

有时,仅仅需要mock一个object里的method,而无需mock整个object。 例如,在对当前object的某个method进行测试时。 这时,可以用patch.object

class ForTest:
    field = 'origin'

    def method():
        pass


def test_for_test(mocker):
    test = ForTest()
    mock_method = mocker.patch.object(test, 'method')
    test.method()
    assert mock_method.called

    assert 'origin' == test.field
    mocker.patch.object(test, 'field', 'mocked')
    assert 'mocked' == test.field

上例中,分别对field和method进行了mock。 当然,对一个给定module的function,也能使用。

def test_patch_object_listdir(mocker):
    mock_listdir = mocker.patch.object(os, 'listdir')
    os.listdir()
    assert mock_listdir.called

用spy包装

如果只是想用MagicMock包装一个东西,而又不想改变其功能,可以用spy

def test_spy_listdir(mocker):
    mock_listdir = mocker.spy(os, 'listdir')
    os.listdir()
    assert mock_listdir.called

与上例中的patch.object不同的是,上例的os.listdir()不会真的执行,而本例中则会真的执行。

 

pytest-mock

This plugin installs a mocker fixture which is a thin-wrapper around the patching API provided by the mock package, but with the benefit of not having to worry about undoing patches at the end of a test:

import os

class UnixFS:

    @staticmethod
    def rm(filename):
        os.remove(filename)

def test_unix_fs(mocker):
    mocker.patch('os.remove')
    UnixFS.rm('file')
    os.remove.assert_called_once_with('file')

pytest-mock mock的高层封装_Python

 

pytest-mock mock的高层封装_sed_02

 

pytest-mock mock的高层封装_Python_03

 

pytest-mock mock的高层封装_Python_04

 

pytest-mock mock的高层封装_sed_05

 

pytest-mock mock的高层封装_python_06

Professionally supported pytest-mock is now available


Usage

The mocker fixture has the same API as mock.patch, supporting the same arguments:

def test_foo(mocker):
    # all valid calls
    mocker.patch('os.remove')
    mocker.patch.object(os, 'listdir', autospec=True)
    mocked_isfile = mocker.patch('os.path.isfile')

The supported methods are:

These objects from the mock module are accessible directly from mocker for convenience:


Spy

The spy acts exactly like the original method in all cases, except it allows use of mock features with it, like retrieving call count. It also works for class and static methods.

def test_spy(mocker):
    class Foo(object):
        def bar(self):
            return 42

    foo = Foo()
    mocker.spy(foo, 'bar')
    assert foo.bar() == 42
    assert foo.bar.call_count == 1

Since version 1.11, it is also possible to query the return_value attribute to observe what the spied function/method returned.

Since version 1.13, it is also possible to query the side_effect attribute to observe any exception thrown by the spied function/method.


Stub

The stub is a mock object that accepts any arguments and is useful to test callbacks, for instance. May be passed a name to be used by the constructed stub object in its repr (useful for debugging).

def test_stub(mocker):
    def foo(on_something):
        on_something('foo', 'bar')

    stub = mocker.stub(name='on_something_stub')

    foo(stub)
    stub.assert_called_once_with('foo', 'bar')


Improved reporting of mock call assertion errors

This plugin monkeypatches the mock library to improve pytest output for failures of mock call assertions like Mock.assert_called_with() by hiding internal traceback entries from the mock module.

It also adds introspection information on differing call arguments when calling the helper methods. This features catches AssertionError raised in the method, and uses py.test's own advanced assertions to return a better diff:

mocker = <pytest_mock.MockFixture object at 0x0381E2D0>

    def test(mocker):
        m = mocker.Mock()
        m('fo')
>       m.assert_called_once_with('', bar=4)
E       AssertionError: Expected call: mock('', bar=4)
E       Actual call: mock('fo')
E
E       pytest introspection follows:
E
E       Args:
E       assert ('fo',) == ('',)
E         At index 0 diff: 'fo' != ''
E         Use -v to get the full diff
E       Kwargs:
E       assert {} == {'bar': 4}
E         Right contains more items:
E         {'bar': 4}
E         Use -v to get the full diff


test_foo.py:6: AssertionError
========================== 1 failed in 0.03 seconds ===========================

This is useful when asserting mock calls with many/nested arguments and trying to quickly see the difference.

This feature is probably safe, but if you encounter any problems it can be disabled in your pytest.ini file:

[pytest]
mock_traceback_monkeypatch = false

Note that this feature is automatically disabled with the --tb=native option. The underlying mechanism used to suppress traceback entries from mock module does not work with that option anyway plus it generates confusing messages on Python 3.5 due to exception chaining


Use standalone "mock" package

New in version 1.4.0.

Python 3 users might want to use a newest version of the mock package as published on PyPI than the one that comes with the Python distribution.

[pytest]
mock_use_standalone_module = true

This will force the plugin to import mock instead of the unittest.mock module bundled with Python 3.4+. Note that this option is only used in Python 3+, as Python 2 users only have the option to use the mock package from PyPI anyway.


Note about usage as context manager

Although mocker's API is intentionally the same as mock.patch's, its use as context manager and function decorator is not supported through the fixture:

def test_context_manager(mocker):
    a = A()
    with mocker.patch.object(a, 'doIt', return_value=True, autospec=True):  # DO NOT DO THIS
        assert a.doIt() == True

The purpose of this plugin is to make the use of context managers and function decorators for mocking unnecessary.


Requirements

  • Python 2.7, Python 3.4+
  • pytest
  • mock (for Python 2)


Install

Install using pip:

$ pip install pytest-mock


Changelog

Please consult the changelog page.


Why bother with a plugin?

There are a number of different patch usages in the standard mock API, but IMHO they don't scale very well when you have more than one or two patches to apply.

It may lead to an excessive nesting of with statements, breaking the flow of the test:

import mock

def test_unix_fs():
    with mock.patch('os.remove'):
        UnixFS.rm('file')
        os.remove.assert_called_once_with('file')

        with mock.patch('os.listdir'):
            assert UnixFS.ls('dir') == expected
            # ...

    with mock.patch('shutil.copy'):
        UnixFS.cp('src', 'dst')
        # ...

One can use patch as a decorator to improve the flow of the test:

@mock.patch('os.remove')
@mock.patch('os.listdir')
@mock.patch('shutil.copy')
def test_unix_fs(mocked_copy, mocked_listdir, mocked_remove):
    UnixFS.rm('file')
    os.remove.assert_called_once_with('file')

    assert UnixFS.ls('dir') == expected
    # ...

    UnixFS.cp('src', 'dst')
    # ...

But this poses a few disadvantages:

  • test functions must receive the mock objects as parameter, even if you don't plan to access them directly; also, order depends on the order of the decorated patch functions;
  • receiving the mocks as parameters doesn't mix nicely with pytest's approach of naming fixtures as parameters, or pytest.mark.parametrize;
  • you can't easily undo the mocking during the test execution;

An alternative is to use contextlib.ExitStack to stack the context managers in a single level of indentation to improve the flow of the test:

import contextlib
import mock

def test_unix_fs():
    with contextlib.ExitStack() as stack:
        stack.enter_context(mock.patch('os.remove'))
        UnixFS.rm('file')
        os.remove.assert_called_once_with('file')

        stack.enter_context(mock.patch('os.listdir'))
        assert UnixFS.ls('dir') == expected
        # ...

        stack.enter_context(mock.patch('shutil.copy'))
        UnixFS.cp('src', 'dst')
        # ...

But this is arguably a little more complex than using pytest-mock.


Contributing

Contributions are welcome! After cloning the repository, create a virtual env and install pytest-mock in editable mode with dev extras:

$ pip install --editable .[dev]
$ pre-commit install

Tests are run with tox, you can run the baseline environments before submitting a PR:

$ tox -e py27,py36,linting

Style checks and formatting are done automatically during commit courtesy of pre-commit.


License

Distributed under the terms of the MIT license.


Security contact information

To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.

标签:封装,patch,assert,pytest,mocker,test,os,mock
From: https://blog.51cto.com/u_11908275/6393203

相关文章

  • pytest 参数化
    前言pytest.mark.parametrize装饰器可以实现测试用例参数化。parametrizing1.这里是一个实现检查一定的输入和期望输出测试功能的典型例子#contentoftest_expectation.py#coding:[email protected]("test_input,expected",......
  • 简单封装一下pymysql库
      简单封装一下pymysql库my_pymysql.py#!/bin/python#-*-coding:utf-8-*-importpymysqlimportnumpydefget_connect(**kwargs):mysqldb=pymysql.connect(host=kwargs.get('host'),user=kwargs.get('user'),passw......
  • 【React工作记录七十七】React+hook+ts+ant design封装一个input和select搜索的组件
    前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从头再来歌谣的意志是永恒的放弃很容易但是坚持一定很酷微信公众号前端小歌谣需求分析首先我们需要实现......
  • 【React工作记录七十八】React+hook+ts+ant design封装一个table的组件
    前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从头再来歌谣的意志是永恒的放弃很容易但是坚持一定很酷微信公众号前端小歌谣需求分析在前端项目中最常......
  • Pytest - Fixture(11) - 重命名fixture函数名称(name)
    Pytest-重命名fixture函数名称(name)fixture设置参数name=value后,可以重命名fixture函数名称,运行时传入重命名后的fixture函数名即可。使用重命名的fixture函数,可以使用装饰器:@pytest.mark.usefixtures();importpytest#编写[email protected](name="open_br......
  • Pytest - Fixture(12) - 配置文件conftest.py
    Pytest-配置文件-conftest.py前言如果在多个测试文件中的用到相同的fixture函数,则可以将其移动到conftest.py文件中conftest.py是专门存放fixture的配置文件;例如:如果测试用例都需要进行用户登录的时候,仅需将登录的功能放到conftest.py文件中,而不需要在每个用......
  • Pytest
    pytest1.插件pytest-html生成html格式的自动化测试报告pytest--html=report/report.htmltest_sdk2_1.pypytest-xdist测试用例分布式执行,多CPU分发pytest-ordering用于改变测试用例的执行顺寻pytest-rerunfailures用例失败后重跑allure-pytest用于生成美观的测试报告......
  • Axios三层封装
    Axios三层封装在实际项目中axios都是要经过封装再使用的,企业级项目一般都是三层封装1.工具函数层对axios工具进行增强,如:设置公共的请求服务器、设置请求拦截器、设置响应拦截器…创建一个文件夹utils,用来放axios。创建文件,随便取名,这里我取request.js引入importaxiosf......
  • WebClient发送get、post请求(form、json)(功能封装)
    1.情景展示Spring3.0引入了RestTemplate,但是在后来的官方源码中介绍,RestTemplate有可能在未来的版本中被弃用,所谓替代RestTemplate,在Spring5中引入了WebClient作为非阻塞式ReactiveHttp客户端。WebClient处理单个HTTP请求的响应时长并不比RestTemplate更快,但是它处理并发的能......
  • Pytest - Fixture(10) - 测试用例传参给Fixture
    Pytest-测试用例传参给Fixture大多数时候我们在fixture封装的是登陆、获取cookie等操作,但是一个系统可能不止一个用户,有多个用户;在写测试用例的时候,如何告诉fixture我们需要登录哪个用户?可以通过测试用例给fixture传递参数,指定登陆用户账户信息。传单个参数fi......