PyTest的模拟:改进我的XML写入/解析单元测试 [英] Mocking with PyTest: Improving my XML write/parse unit test

查看:71
本文介绍了PyTest的模拟:改进我的XML写入/解析单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我对pytestmock相当陌生,但是仍然对junit以及使用mockito进行groovy的模拟(具有便捷的when(...).thenAnswer/Return函数)有经验

So I am rather new with pytest and mock, but still have experience with junit and mocking with mockito for groovy (which comes with an handy when(...).thenAnswer/Return function)

我写了一个简单的类来解析和编写xml文件.此类存在的唯一目的是要进行模拟,以便对我当前正在使用的插件进行单元测试.这个个人项目还用作学习工具,可以帮助我完成工作(基于devOps python)

I wrote a simple class to parse and write xml files. This class sole purpose for existence is to be mocked in order to unit test the plugin I am currently working on. This personal project is also used as a learning tool to help me in my work duties (devOps python based)

显然,我也需要对其进行测试.

Obviously, I needed to test it too.

这是课程:

from lxml import etree

from organizer.tools.exception_tools import ExceptionPrinter


class XmlFilesOperations(object):

    @staticmethod
    def write(document_to_write, target):
        document_to_write.write(target, pretty_print=True)

    @staticmethod
    def parse(file_to_parse):
        parser = etree.XMLParser(remove_blank_text=True)

        try:
            return etree.parse(file_to_parse, parser)
        except Exception as something_happened:
            ExceptionPrinter.print_exception(something_happened)

这是它的单元测试:

import mock

from organizer.tools.xml_files_operations import XmlFilesOperations

FILE_NAME = "toto.xml"

@mock.patch('organizer.tools.xml_files_operations.etree.ElementTree')
def test_write(mock_document):

    XmlFilesOperations.write(mock_document, FILE_NAME)
    mock_document.write.assert_called_with(FILE_NAME, pretty_print=True)

@mock.patch('organizer.tools.xml_files_operations.etree')
def test_parse(mock_xml):

    XmlFilesOperations.parse(FILE_NAME)
    mock_xml.parse.assert_called()

此外,这是此python环境使用的pipfile:

Also, here is the pipfile used for this python environment:

[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]
lxml = "*"
pytest = "*"
pytest-lazy-fixture = "*"
mock = "*"
MKLpy = "*"

我想通过使用test_parse函数中的assert_called_with函数来改进此测试.但是,要使其正常工作,我需要获取XmlFilesOperations.parse方法中使用的确切解析器,因此我也想对其进行模拟.为此,我需要etree.XMLParser(remove_blank_text=True)调用以返回模拟对象

I would like to improve this test by making use of the assert_called_with function in the test_parse function. However to make it work I need to get the exact parser that is used in the XmlFilesOperations.parse method so I imagined mocking it too. For this I need the etree.XMLParser(remove_blank_text=True) call to return a mocked object

这是我尝试过的:

import mock
import pytest
from lxml import etree

from organizer.tools.xml_files_operations import XmlFilesOperations

FILE_NAME = "toto.xml"

@pytest.fixture()
def mock_parser():
    parser = mock.patch('organizer.tools.xml_files_operations.etree.XMLParser').start()
    with mock.patch('organizer.tools.xml_files_operations.etree.XMLParser', return_value=parser):
        yield parser

    parser.stop()

@mock.patch('organizer.tools.xml_files_operations.etree')
def test_parse(mock_xml, mock_parser):

    XmlFilesOperations.parse(FILE_NAME)
    mock_xml.parse.assert_called_with(FILE_NAME, mock_parser)

我得到以下错误:

    def raise_from(value, from_value):
>       raise value
E       AssertionError: expected call not found.
E       Expected: parse('toto.xml', <MagicMock name='XMLParser' id='65803280'>)
E       Actual: parse('toto.xml', <MagicMock name='etree.XMLParser()' id='66022384'>)

因此,调用返回的模拟对象与我创建的模拟对象不同.

So the mocked object returned by the call is not the same mocked object that I created.

有了Mockito,我会做这样的事情:

With Mockito, I would have done something like this:

parser = etree.XmlParser()
when(etree.XMLParser(any()).thenReturn(parser)

它会起作用. 我该如何解决?

And it would work. How could I fix that ?

推荐答案

方法的主要问题是模拟对象的顺序.夹具首先被调用,在模拟解析器时,它不使用模拟的etree,而是真实的,而在测试中,解析器是从模拟的etree使用的,这是由该模拟创建的另一个模拟.
此外,您确实检查了解析器 method 而不是解析器本身.

The main problem with your approach is the sequence of mocking the objects. The fixture is called first, and while mocking the parser, it does not use the mocked etree, but the real one, while in the test the parser is used from the mocked etree, which is another mock created by that mock.
Additionally, you did check for the parser method instead of the parser itself.

以下是不使用固定装置的情况:

Here is what should work without using a fixture:

@mock.patch('organizer.tools.xml_files_operations.etree.XMLParser')
@mock.patch('organizer.tools.xml_files_operations.etree')
def test_parse(mock_xml, mock_parser):
    XmlFilesOperations.parse(FILE_NAME)
    mock_xml.parse.assert_called_with(FILE_NAME, mock_parser())

另一种可能性是更换固定装置和补丁,以便以正确的顺序使用它们:

Another possibility is to exchange the fixture and the patch, so that they are used in the correct order:

@pytest.fixture()
def mock_etree():
    with mock.patch('organizer.tools.xml_files_operations.etree') as mocked_etree:
        yield mocked_etree


@mock.patch('organizer.tools.xml_files_operations.etree.XMLParser')
def test_parse(mock_xml_parser, mock_etree):
    XmlFilesOperations.parse(FILE_NAME)
    mock_etree.parse.assert_called_with(FILE_NAME, mock_xml_parser())

最后,如果只想使用灯具,则可以使它们相互依赖:

Finally, if you want to use only fixtures, you can make them dependent on each other:

@pytest.fixture()
def mock_etree():
    with mock.patch('organizer.tools.xml_files_operations.etree') as mocked_etree:
        yield mocked_etree


@pytest.fixture()
def mock_parser(mock_etree):
    parser = mock.Mock()
    with mock.patch.object(mock_etree, 'XMLParser', parser):
        yield parser


def test_parse(mock_parser, mock_etree):
    XmlFilesOperations.parse(FILE_NAME)
    mock_etree.parse.assert_called_with(FILE_NAME, mock_parser())

这篇关于PyTest的模拟:改进我的XML写入/解析单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆