【问题标题】:tdd - should I mock here or use real implementationtdd - 我应该在这里模拟还是使用真正的实现
【发布时间】:2011-06-27 19:52:05
【问题描述】:

我正在编写程序参数解析器,只是为了在 TDD 中做得更好,但我遇到了以下问题。假设我的解析器定义如下:

class ArgumentsParser {
    public ArgumentsParser(ArgumentsConfiguration configuration) {
        this.configuration = configuration;
    }

    public void parse(String[] programArguments) {
        // all the stuff for parsing
    }
}

我想有 ArgumentsConfiguration 实现,如:

class ArgumentsConfiguration {
    private Map<String, Class> map = new HashMap<String, Class>();

    public void addArgument(String argName, Class valueClass) {
        map.add(argName, valueClass);
    }

    // get configured arguments methods etc.
}

这是我目前的阶段。现在我在测试中使用:

@Test
public void shouldResultWithOneAvailableArgument() {
    ArgumentsConfiguration config = prepareSampleConfiguration(); 
    config.addArgument("mode", Integer.class);
    ArgumentsParser parser = new ArgumentsParser(configuration);
    parser.parse();
    // ....
}

我的问题是这种方式是否正确?我的意思是,可以在测试中使用真正的 ArgumentsConfiguration 吗?还是我应该嘲笑它?默认(当前)实现非常简单(只是包装了 Map),但我想它可能会更复杂,比如从某种数据源中获取配置。那么嘲笑这种“昂贵”的行为是很自然的。但是这里的首选方式是什么?

编辑: 也许更清楚:我是否应该在不编写任何实现的情况下模拟 ArgumentsConfiguration(只需定义其公共方法),使用模拟进行测试并稍后处理实际实现,还是应该在测试中使用最简单的,让他们覆盖这个间接实施。但是如果是这样,那么测试稍后提供的另一个配置实现呢?

【问题讨论】:

    标签: unit-testing tdd mocking


    【解决方案1】:

    那么嘲笑这种“昂贵”的行为是很自然的。

    这不是重点。你不是在模拟复杂的类。

    你在嘲笑完全隔离类。

    完全隔离可确保测试证明类遵循其接口并且没有隐藏的实现怪癖。

    此外,完全隔离使调试失败的测试变得更加容易。它可以是测试、被测类或模拟对象。理想情况下,测试和模拟非常简单,不需要任何调试,只留下待测类。

    【讨论】:

    • 所以你选择完全专注于 Parser 实现,只定义配置接口(并模拟它),然后使用完全独立的测试创建它的实现?
    • @veilsoen:这是一种常见的做法:独立开发,独立测试。当您有一个非常简单的类以至于应用程序类就像应用程序类的 Mock 一样简单时,就会发生权衡。这在 Java 中经常发生,其中应用程序类只是 getter 和 setter。 Mock 并不比应用程序类简单。
    • 感谢您的回答。现在似乎很清楚,但还有一个问题。由于重构(应用 SRP 等)而创建的依赖项呢?提取它们只会导致它们仍在测试中使用。当我发现这种“新”依赖时,我是否也应该嘲笑它们?
    • @veilsoen:是的,您需要独立测试每个类。对于抽象超类或接口,您可以创建一个具体的子类或实现,它是如此微不足道,以至于它实际上是一个模拟类。
    【解决方案2】:

    正确的答案是你应该模拟任何你不想直接测试的东西(例如:被测对象所具有的与特定测试用例不直接相关的任何依赖项)。

    【讨论】:

      【解决方案3】:

      在这种情况下,因为您的 ArgumentsConfiguration 非常简单,所以我建议您使用真正的实现,直到您的需求需要更复杂的东西。您的 ArgumentsConfiguration 类中似乎没有任何逻辑,因此使用真实对象是安全的。如果到了配置更复杂的时候,那么您可能应该采取的方法不是创建与某些数据源对话的配置,而是从该数据源生成 ArgumentsConfiguration 对象。然后您可以进行测试,确保它从数据源正确生成配置,并且您不需要不必要的抽象。

      【讨论】:

      • 我认为我不同意这一点 - 将您的类测试基于依赖类的当前实现似乎是错误的。如果稍后将 ArgumentsConfiguration 更改为从属性文件中读取怎么办?解析器的测试将中断。如果您改用模拟的 ArgumentsConfiguration,测试将继续进行。
      • @SteveT 这绝对是风格问题,但模拟 ArgumentsConfigurations 是对模拟的过度使用。它所做的只是保存键/值对。如果从属性文件中读取 ArgumentsConfiguration,则 ArgumentsParser 不必更改。所有需要改变的是创建 ArgumentsConfiguration 的工厂。如果您发现 ArgumentsParser 依赖于 ArgumentsConfiguration 的创建位置,那么这是一种表明需要更改设计的气味。这就是我发现过多的嘲笑可以隐藏糟糕的设计的地方。
      猜你喜欢
      • 1970-01-01
      • 2023-02-09
      • 1970-01-01
      • 2011-02-03
      • 2012-05-29
      • 1970-01-01
      • 1970-01-01
      • 2019-08-27
      • 1970-01-01
      相关资源
      最近更新 更多