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

phy*_*ion 2 python unit-testing mocking

我有一个受测试的系统(Printer下面的类),它使用另一个类(ContentContainer下面的类).在一个方法(retrieve_and_show_content下面的方法)中,实例化该类.在这个方法的测试(test_printer_03下面的方法)中,我想实例化一个mock而不是真正的类.但是,它不会像这样工作.

在这里读到我应该更改名称指向的对象与另一个.看起来我想要替换ContentContainer的对象的名称很简单,而我实际替换的对象的名称是TestMockClass.ContentContainer.这个观察是否正确?如果是这样,我该如何更改?如果我只是删除TestMockClass补丁语句中的前缀,我会得到一个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()
Run Code Online (Sandbox Code Playgroud)

运行此单元测试时,输出为:

AssertionError: 
Retrieved:
P03 says: Content from ContentContainer 
Expected:
P03 says: Mocked content
Run Code Online (Sandbox Code Playgroud)

Sim*_*ser 10

两件事情:

  1. 因为ContentContainer现在与测试位于同一个文件中,所以实际上需要修补__main__.ContentContainer:

    @patch('__main__.ContentContainer')
    
    Run Code Online (Sandbox Code Playgroud)
  2. 因为ContentContainer是一个类,并且您正在调用该类content的实例,所以您实际上希望模拟content该实例,而不是类.因此,您需要这样做:( mock_cc.return_value.content.return_value = 'Mocked content'注意其中的附加内容.return_value以确保您正在模拟实例,而不是类).这是因为调用类会创建一个实例.因此,实例是类上调用的返回值.

因此测试应如下所示:

@patch('__main__.ContentContainer')
def test_printer_03(self, mock_cc):
    mock_cc.return_value.content.return_value = 'Mocked content'
    ...
Run Code Online (Sandbox Code Playgroud)