我刚刚开始使用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
的内部结构。我应该在哪里进行交易?清洁代码或清洁测试?
答案 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
。如果那是不可能的,我会保持原样。如果您的六行模拟代码是您担心的测试用例的唯一部分,那么您没问题。