首页 > 解决方案 > 测试行为与实现

问题描述

我的任务是在没有测试的情况下对代码库进行小改动,并且我第一次尝试使用 TDD 来实现更改,但我正在努力解决测试实现和行为之间的差异。我正在开发的系统呈现用户可配置的列表项。用户可以通过由 XML DAO 控制的全局偏好系统来更改列表项的行为:

class Preference {
  boolean getX();
  boolean getY();
}

我的任务是向这个 DAO 添加 Z,我已经实现了这个更改并添加了测试以确保值按我的预期进行序列化和反序列化。

然后,以下列表项类应接收 Z 值:

class ListItemA extends ListItem {
  private boolean Z;

  ListItemA(boolean Z){
    this.Z = Z;
  }

  OpenAction getOpenAction(){
    OpenAction action = new OpenAction();

    if(Z){
       action.addValue('Filter', true);
    }

    return action;
  }
}

我还编写了测试以确保返回的操作反映 Z 字段。这两者之间的联系是我苦苦挣扎的地方,渲染列表项时调用了一个控制器类:

class Controller {
  private Preference preference;

  Controller(Preference preference){
    this.preference = preference;
  }

  List<ListItem> getListItems(State state){
    // a bunch condition statements concerning the state
    List<ListItem> items = new List();

    if(state.items.includes('ListItemA')){
      ListItemA item = new ListItemA();
    }

    return items;
  }

}

当实例化 ListItemA 时,我现在需要从首选项字段中传递 getZ(),但 TDD 说如果没有首先失败的测试,我无法进行此更改。

我可以模拟首选项,模拟状态,生成列表,然后编写相同的测试以确保 OpenAction 反映 Z 的值,但这似乎我正在测试实现并编写一个脆弱的测试。此外,ListItem 测试中会有很多重复的测试代码,因为它们正在做出相同的断言。另一种选择是废弃 ListItem 测试并在 Controller 测试中添加所有断言,但这似乎我正在测试另一个系统。

我的问题是:

标签: javaunit-testingtestingtddlegacy-code

解决方案


如何在测试实现和编写过于广泛的测试之间划清界限?

当广泛的测试跨越多个不稳定的行为时,它们往往是最有问题的——每次其中一种行为发生变化时,测试也会发生变化。这在单个编辑会话中通常不是问题 - 因为即使是不稳定的行为也会在足够短的时间内“稳定”。

如果您正在使用的遗留代码已捕获其更改历史记录(例如:源代码控制系统),那么您可以查看代码的哪些部分是稳定的,并假设启发式“昨天的天气”将成立。

我可以嘲笑偏好,嘲笑国家,

你可以这样做,但这可能不是你最好的起点。Mocks 非常适合测试某些类型的设计——尤其是协议——但你在测试协议时获得的好处并不一定会转移到其他样式。

如果测试对象不依赖于共享可变状态,则可以使用其他技术来生成更细粒度的测试,而无需引入模拟。

如何在没有测试文化的工作场所测试遗留系统?

在最坏的情况下?编写测试,实施更改,发布更改并丢弃测试。将注意力集中在提供可以测试的高质量代码上,并将政治冲突推迟到以后。


推荐阅读