【问题标题】:Mocking a class's construction模拟类的构造
【发布时间】:2012-04-16 09:08:34
【问题描述】:

我刚刚开始使用 Python 的 mock 库来帮助编写更简洁和独立的单元测试。我的情况是我有一个类可以从非常多毛的格式中读取数据,我想在这个类上测试一个方法,它以干净的形式呈现数据 格式。

class holds_data(object):
    def __init__(self, path):
        """Pulls complicated data from a file, given by 'path'.

        Stores it in a dictionary. 
        """
        self.data = {}
        with open(path) as f:
            self.data.update(_parse(f))

    def _parse(self, file):
        # Some hairy parsing code here
        pass

    def x_coords(self):
        """The x coordinates from one part of the data
        """
        return [point[0] for point in self.data['points']]

上面的代码是我所拥有的代码的简化。实际上_parse 是一种相当重要的方法,我在功能级别上对其进行了测试。

不过,我希望能够在单元测试级别测试x_coords。如果我通过给它一个路径来实例化这个类,它会违反rules of unit tests,因为:

如果满足以下条件,则测试不是单元测试:

  • 它涉及文件系统

所以,我希望能够为holds_data 修补__init__ 方法,然后只需填写x_coords 所需的self.data 部分。比如:

from mock import patch
with patch('__main__.holds_data.__init__') as init_mock:
    init_mock.return_value = None
    instance = holds_data()
    instance.data = {'points':[(1,1),(2,2),(3,4)]}
    assert(instance.x_coords == [1,2,3])

上面的代码可以工作,但感觉它是以一种相当迂回的方式进行这个测试。有没有更惯用的方法来修补构造函数,或者这是正确的方法?另外,在我的课堂或测试中是否有一些我遗漏的代码气味?

编辑:需要明确的是,我的问题是在初始化过程中,我的类进行了大量的数据处理来组织将由x_coords 之类的方法呈现的数据。我想知道修补所有这些步骤的最简单方法是什么,而无需提供完整的输入示例。我只想在我控制它使用的数据的情况下测试x_coords 的行为。

我这里是否有代码异味的问题归结为这个问题:

如果我将x_coords 重构为一个以holds_data 作为参数的独立函数,我相信这会更容易。如果“更容易测试 == 更好的设计”成立,这将是要走的路。但是,它需要x_coords 函数来更多地了解holds_data 的内部结构,而我通常会对此感到满意。我应该在哪里进行权衡?更简洁的代码还是更简洁的测试?

【问题讨论】:

    标签: python unit-testing mocking


    【解决方案1】:

    既然您只对测试一种方法感兴趣,为什么不直接模拟整个 HoldsData 类并将x_coords 方法固定在上面?

    >>> mock = MagicMock(data={'points': [(0,1), (2,3), (4,5)]})
    >>> mock.x_coords = HoldsData.__dict__['x_coords']
    >>> mock.x_coords(mock)
    [0, 2, 4]
    

    这样您就可以完全控制x_coords 的输入和输出(通过副作用或返回值)。

    注意:在 py3k 中你可以只做mock.x_coords = HoldsData.x_coords,因为有no more unbound methods。

    这也可以在模拟对象的构造函数中完成:

    MagicMock(data={'points': [(0,1), (2,3), (4,5)]}, x_coords=HoldsData.__dict__['x_coords'])
    

    【讨论】:

    • 是的,这就是我要找的。谢谢。
    • 3 年了,但可能对某人有用:一旦你有了 mock 对象,你就可以做 holds_data.x_coords(mock) 并省去为事物分配方法的麻烦。
    【解决方案2】:

    由于这个原因,您基本上遇到了这个问题:

    如果满足以下条件,则测试不是单元测试:

    • 它涉及文件系统

    如果你想遵循这个规则,你应该修改_parse 方法。特别是,它不应将文件作为输入。 _parse 的任务是解析数据,但哪里数据来自哪里不是那个方法关心的问题。

    您可以有一个包含相同数据的字符串,然后将其传递给_parse。同样,数据可能完全来自数据库或其他地方。当_parse 仅将数据作为输入时,您可以更轻松地对该方法进行单元测试。

    看起来像这样:

    class HoldsData(object):
        def __init__(self, path):
            self.data = {}
            file_data = self._read_data_from_file(path)
            self.data.update(self._parse(file_data))
    
        def _read_data_from_file(self, path):
            # read data from file
            return data
    
        def _parse(self, data):
            # do parsing
    

    干净的代码当然会带来干净的测试。最好的情况是模拟数据并为_parse 提供输入,然后稍后测试x_coords。如果这不可能,我会保持原样。如果您的六行模拟代码是您担心的测试用例中唯一的一部分,那么您很好。

    【讨论】:

    • 这实际上是我的真实课堂的运作方式。 __init__ 方法将一个文件读入一个字符串列表,_parse 以此为基础工作。问题是这种方法很多,我想模拟除x_coords 使用的一小部分之外的所有内容。我不确定我是否正确地做那部分。
    • 查看我的编辑。我的问题是提供完全伪造的输入是不可行的。我只想测试单个方法x_coords 的行为,而不是_parse 的行为(我对此进行了功能测试)。
    • 在这种情况下,您可以重构 _parse 并将其拆分为单独的函数。那可能吗?然后您可以提供可以解析的输入的一部分,然后您可以测试x_coords。
    • 我真的不能把它分解成单独的方法。我正在从文件格式中读取数据,如果您为它编写语法,它将是上下文相关的。我基本上必须一次性解析它(我有一个有限状态机可以做到这一点好的)。我只是在寻找最方便的方法来使 all 消失以进行x_coords 的测试,我想尽可能地隔离它。如果这实际上是一个非常不寻常的情况(我是嘲笑的新手,这可能是我所知道的),我会接受我必须使用我的黑客,尽管我想知道为什么会这样' t 更容易 =)。
    • 例如,如果我将x_coords 重构为一个以holds_data 作为参数的独立函数,我相信这会更容易。如果更容易测试==更好的设计,这将是要走的路。但是,它需要x_coords 函数来更多地了解holds_data 的内部结构,而我通常会对此感到满意。我应该在哪里进行权衡?更简洁的代码还是更简洁的测试?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-28
    • 1970-01-01
    • 1970-01-01
    • 2013-09-03
    • 2017-09-30
    • 2021-10-25
    相关资源
    最近更新 更多