嘲弄一个班级的建设

时间:2012-04-02 17:32:05

标签: python unit-testing mocking

我刚刚开始使用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因为:

  

如果出现以下情况,则测试不是单元测试:

     
      
  • 它触及文件系统
  •   

因此,我希望能够为__init__修补holds_data方法,然后填写self.data所需的x_coords部分。类似的东西:

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的内部结构。我应该在哪里进行交易?清洁代码或清洁测试?

2 个答案:

答案 0 :(得分:6)

由于您只对测试一种方法感兴趣,为什么不只是模拟整个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'])

答案 1 :(得分:1)

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

  

如果出现以下情况,则测试不是单元测试:

     
      
  • 它触及文件系统
  •   

如果您希望遵循此规则,则应修改_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。如果那是不可能的,我会保持原样。如果您的六行模拟代码是您担心的测试用例的唯一部分,那么您没问题。