测试行为与实施

Testing behaviour vs implementation

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

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

我的任务是将 Z 添加到此 DAO,我已经实施了此更改并添加了测试以确保值按照我的预期进行序列化和反序列化。

下面的列表项 class 应该接收 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 在呈现列表项时调用:

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 测试并在控制器测试中添加所有断言,但这似乎我正在测试另一个系统。

我的问题是:

How do I draw the line between testing implementation and writing tests that are too broad?

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

如果您正在使用的遗留代码已捕获其更改历史记录(例如:源代码控制系统),那么您可以查看代码的哪些部分是稳定的,并假设启发式 "yesterday's weather" 将举行。

I could mock the preference, mock the state,

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

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

How do I go about testing a legacy system in a workplace with a no-testing culture?

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