简体   繁体   English

模拟在测试方法中实例化的Python类

[英]Mocking a Python class that is instantiated in method under test

I have a system under test (Class Printer below), which uses another class (Class ContentContainer below). 我有一个受测试的系统(下面的类Printer ),它使用另一个类(下面的Class ContentContainer )。 In one method (Method retrieve_and_show_content below), this class is instantiated. 在一个方法(下面的方法retrieve_and_show_content )中,实例化此类。 In the test for this method (Method test_printer_03 below), I want to instantiate a mock instead of the real class. 在这个方法的测试中(下面的方法test_printer_03 ),我想实例化一个mock而不是真正的类。 However, it doesn't work like this. 但是,它不会像这样工作。

I read here that I should change the object that a name points to with another one. 在这里读到我应该更改名称指向的对象与另一个。 It seems like the name of the object that I want to replace is simply ContentContainer , while the name of the object that I am actually replacing is TestMockClass.ContentContainer . 看起来我要替换的对象的名称只是ContentContainer ,而我实际替换的对象的名称是TestMockClass.ContentContainer Is this observation correct? 这个观察是否正确? If so, how do I change this? 如果是这样,我该如何更改? If I simply remove the prefix TestMockClass in the patch statement, I get a TypeError: Need a valid target to patch. You supplied: 'ContentContainer' 如果我只是在patch语句中删除前缀TestMockClass ,我会得到一个TypeError: Need a valid target to patch. You supplied: 'ContentContainer' TypeError: Need a valid target to patch. You supplied: 'ContentContainer' . TypeError: Need a valid target to patch. You supplied: 'ContentContainer'

#TestMockClass.py
import unittest
from mock import Mock, patch

class Printer():
    def __init__(self, name, cc):
        self.name = name
        self.cc = cc
    def show_content(self):
        text = '{0} says: {1}'.format(self.name, self.cc.content())
        return text
    def retrieve_and_show_content(self):
        cc_tmp = ContentContainer()
        text = '{0} says: {1}'.format(self.name, cc_tmp.content())
        return text

class ContentContainer():
    def __init__(self):
        self.method_counter()
    def content(self):
        return 'Content from ContentContainer'
    def method_counter(self):
        pass

class Test(unittest.TestCase):

    '''No mocking'''
    def test_printer_01(self):
        cc = ContentContainer()
        sut = Printer('P01', cc)
        result = sut.show_content()
        expected_result = 'P01 says: Content from ContentContainer'
        self.assertEqual(result, expected_result, 
                         msg = '\nRetrieved:\n{0} \nExpected:\n{1}'.format(result, expected_result))
        result = sut.retrieve_and_show_content()
        expected_result = 'P01 says: Content from ContentContainer'
        self.assertEqual(result, expected_result, 
                         msg = '\nRetrieved:\n{0} \nExpected:\n{1}'.format(result, expected_result))

    '''Create a mock object, which is the input of the method under test'''
    def test_printer_02(self):
        mock_cc = Mock()
        mock_cc.content.return_value = 'Mocked content'
        sut = Printer('P02', mock_cc)
        result = sut.show_content()
        expected_result = 'P02 says: Mocked content'
        self.assertEqual(result, expected_result, 
                         msg = '\nRetrieved:\n{0} \nExpected:\n{1}'.format(result, expected_result))
        self.assertFalse(mock_cc.method_counter.called, 'Method method_counter shall not be called')

    '''Create a mock class, which is instantiated inside the method under test'''
    @patch('TestMockClass.ContentContainer')
    def test_printer_03(self, mock_cc):
        mock_cc.content.return_value = 'Mocked content'
        sut = Printer('P03', mock_cc)
        result = sut.retrieve_and_show_content()
        expected_result = 'P03 says: Mocked content'
        self.assertEqual(result, expected_result, 
                         msg = '\nRetrieved:\n{0} \nExpected:\n{1}'.format(result, expected_result))
        self.assertFalse(mock_cc.method_counter.called, 'Method method_counter shall not be called')

if __name__ == "__main__":
    unittest.main()

When this unittest is run, the output is: 运行此单元测试时,输出为:

AssertionError: 
Retrieved:
P03 says: Content from ContentContainer 
Expected:
P03 says: Mocked content

Two things: 两件事情:

  1. Because ContentContainer is now in the same file as the tests, you actually need to patch __main__.ContentContainer : 因为ContentContainer现在与测试位于同一个文件中,所以实际上需要修补__main__.ContentContainer

     @patch('__main__.ContentContainer') 
  2. Because ContentContainer is a class and you're calling content on an instance of that class, you actually wish to mock content on that instance, not on the class. 因为ContentContainer是一个类,并且您在该类的实例上调用content ,所以您实际上希望模拟该实例上的content ,而不是类。 You therefore need to do: mock_cc.return_value.content.return_value = 'Mocked content' (note the additional .return_value in there to make sure you're mocking the instance, not the class). 因此,您需要执行以下操作: mock_cc.return_value.content.return_value = 'Mocked content' (请注意其中的附加.return_value以确保您正在.return_value实例,而不是类)。 This is because calling a class creates an instance. 这是因为调用类会创建一个实例。 So, the instance is the return value of the call on the class. 因此,实例是类上调用的返回值。

The test should thus look like: 因此测试应如下所示:

@patch('__main__.ContentContainer')
def test_printer_03(self, mock_cc):
    mock_cc.return_value.content.return_value = 'Mocked content'
    ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM