Python:在单元测试中进行模拟

时间:2017-06-14 23:58:21

标签: python unit-testing mocking

我的情况类似于:

class BaseClient(object):
    def __init__(self, api_key):
        self.api_key = api_key
        # Doing some staff.

class ConcreteClient(BaseClient):
    def get_some_basic_data(self):
        # Doing something.

    def calculate(self):
        # some staff here
        self.get_some_basic_data(param)
        # some calculations

然后我想使用模拟calculate函数来测试get_some_basic_data函数。

我正在做这样的事情:

import unittest
from my_module import ConcreteClient

def my_fake_data(param):
    return [{"key1": "val1"}, {"key2": "val2"}]

class ConcreteClientTest(unittest.TestCase):
    def setUp(self):
        self.client = Mock(ConcreteClient)

    def test_calculate(self):

        patch.object(ConcreteClient, 'get_some_basic_data',
                     return_value=my_fake_data).start()
        result = self.client.calculate(42)

但它没有像我期望的那样工作..正如我所想,self.get_some_basic_data(param)my_fake_data函数返回我的列表,但看起来它仍然是一个Mock对象,这对我来说是不可取的

这里有什么问题?

1 个答案:

答案 0 :(得分:1)

您在这里遇到两个主要问题。引发当前问题的主要问题是因为 你实际上是在嘲笑。现在,由于您实际上正在为object修补ConcreteClient,因此您需要确保仍在使用真实 ConcreteClient但模拟测试时要模拟的实例的属性。您实际上可以在文档中看到此插图。不幸的是,确切的行没有明确的锚点,但是如果你按照这个链接:

https://docs.python.org/3/library/unittest.mock-examples.html

指出的部分:

  

如果你使用patch()为你创建一个模拟,你可以得到一个   使用with语句的“as”形式引用mock:

参考代码为:

class ProductionClass:
    def method(self):
        pass

with patch.object(ProductionClass, 'method') as mock_method:
    mock_method.return_value = None
    real = ProductionClass()
    real.method(1, 2, 3)

mock_method.assert_called_with(1, 2, 3)

这里要注意的关键项目是如何调用所有内容。请注意,创建了类的真实实例。在您的示例中,当您这样做时:

self.client = Mock(ConcreteClient)

您正在ConcreteClient上创建 specced Mock对象。因此,最终这只是一个Mock对象,其中包含ConcreteClient的属性。您实际上不会持有ConcreteClient真实实例。

解决这个问题。在修补对象后,只需创建一个真实的实例。此外,为了让您的生活更轻松,您不必手动启动/停止patch.object,使用上下文管理器,它将为您节省很多麻烦。

最后,您的第二个问题是return_value。您的return_value实际上正在返回未被调用的 my_fake_data函数。您实际上想要数据本身,因此它需要是该函数的 return 。您可以将数据本身作为return_value

考虑到这两个更正,您的测试现在应该如下所示:

class ConcreteClientTest(unittest.TestCase):

    def test_calculate(self):

        with patch.object(ConcreteClient, 'get_some_basic_data',
                     return_value=[{"key1": "val1"}, {"key2": "val2"}]):

            concrete_client = ConcreteClient(Mock())
            result = concrete_client.calculate()

        self.assertEqual(
            result,
            [{"key1": "val1"}, {"key2": "val2"}]
        )

我冒昧地在get_some_basic_data中返回calculate的结果,只是为了有一些东西要比较。我不确定您的真正的代码是什么样的。但是,最终,您的测试结构如何应该如此,如上所述。