一种用存根替换实际组件的方法

时间:2013-01-23 10:59:17

标签: python unit-testing python-3.x

我遇到安排代码以使其易于测试的问题。我的代码中有两个主要模块:缓存生成器和修改器构建器,两者的复杂程度大致相同。修饰符构建器用于缓存生成器的子对象的一种方法。

我已经拥有完整的测试套件,涵盖了修改器构建器的功能。我想添加涵盖缓存生成器的所有功能的测试,但为了显着降低这些测试的复杂性,我需要将修改器构建器替换为一些存根,它根据传递给它的参数返回预定义的“固定数据”。

我的实际问题在于选择使用存根替换实际修改器构建器的方式,这看起来很好,并且仍然便于测试。看看下面的代码:

来自GitHub的代码:

cacheGenerator / generator.py:

class CacheGenerator:
    def __init__(self, logger):
        ...
        self._converter = Converter(logger)

    def run(self, dataHandler):
        ...
        data = self._converter.convert(data)

cacheGenerator / converter.py:

class Converter:
    ...

    def convert(self, data):
        ...
        self._buildModifiers(data)

    def _buildModifiers(self, data):
        ...
        builder = ModifierBuilder(data['expressions'], self._logger)
        ...
           modifiers, buildStatus = builder.buildEffect(...)

用stub替换修饰符构建器有哪些方法?我想至少存在以下几种变体:

  1. 对代码的更改:在转换器的 init ()中实例化修改器构建器,并将其实例指定为对象属性。为了测试 - 创建实转换器的子类,重写 init (),我们用stub替换real modifier builder,然后继承缓存生成器,用类似的方式替换带有子类的实转换器。但是,这种方法需要修改修饰符构建器:我需要从 init ()方法中拆分数据,这是不合需要的
  2. 与1)类似,但移动使用修饰符构建器的Converter()._ buildModifiers()方法的部分来分离方法以使它们易于覆盖
  3. 与1)类似,但在cle的 init ()中仅指定修饰符构建器类(不是实例)。这允许我们按原样保留修改器构建器。
  4. 从缓存生成器的最顶层传递修饰符构建器类(以便我们需要替换以进行测试的类可由缓存生成器实例化控制)
  5. 是否存在其他任何变体?像一些导入魔法?
  6. 1-4中的一些变体看起来是可以接受的,但理想情况下我想保持代码尽可能接近(原始),所以我正在研究存储子对象的替代方法。

2 个答案:

答案 0 :(得分:1)

我通常更喜欢2,因为它使意图明确,是一个小改动,它可能对其他重用我的工作的代码有用。

或者,请查看为您构建ModifierBuilder的{​​{3}}或dependency injection

最后,您可以通过导入模块然后为符号分配新值来使用factory

import cacheGenerator.converter
cacheGenerator.converter.ModifierBuilder = ...

当然,这会更改每个人的符号(也适用于所有其他测试),因此您需要保存旧值并在测试后将其恢复。

如果你对这个解决方案感到不安/不安,那么你是对的:这是一个绝望的措施。如果您真的无法更改原始代码,请将其作为最后的手段使用。

答案 1 :(得分:1)

当我需要在测试中模拟/伪造对象时,我使用Fudge

在您的情况下,我建议使用patched_context。有了它,您可以修补对Converter方法的调用。

然后你可以这样做:

补丁调用_converter.convert

test.py:

from cacheGenerator.generator import CacheGenerator
from cacheGenerator.converter import Converter
from fudge import patched_context

import unittest

class Test_cacheGenerator(unittest.testCase):

    def test_run(self):

        def fakeData(convertself, data):
            # Create data to be returned to 
            # data = self._converter.convert(data)
            fakedata = ...
            return fakedata


        # We tell Fudge to patch the call to `Converter.convert`
        # and instead call our defined function 
        cache = cacheGenerator(...)
        with patched_context(Converter, 'convert', fakeData)
            cache.run()

或者您可以修补self._buildModifiers内的Converter来电:

def test_run(self):
        cache = cacheGenerator(...)

        def fakeBuildModifiers(convertself, data):
            # set up variables that convert._buildModifiers usually sets up
            convertself.modifiers = ...
            convertself.buildStatus = ...

        # We tell Fudge to patch the call to `Coverter._buildModifiers`
        # and instead call our defined function 
        cache = cacheGenerator(...)
        with patched_context(Converter, '_buildModifiers', fakeBuildModifiers):
            cache.run()

或者,您也可以使用Fudge fake object

from fuge import Fake

...
    def test_run(self):
        cache = cacheGenerator(...)

        fakeData = ...
        fakeConverter = Fake('Converter').provides('convert').returns(fakeData)

        # Fake our `Converter` so that our any calls to `_converter.convert` are
        # made to `fakeConverter.convert` instead.
        cache._converter = fakeConverter

        cache.run()

在最后一种情况下,由于您要修补整个_converter对象,如果您正在调用任何其他方法,则还需要对它们进行修补。

(Fake('Converter'.provides('convert').returns(fakeData)
                 .provides(....).returns()
                 .provides(....).returns()
)