单元测试“胶水”代码或代码,没有任何实际逻辑

时间:2012-10-12 13:56:22

标签: java unit-testing

我的问题将在一个简化的例子(Java中):

public class VehicleRepository {
  // DAOs responsible for fetching objects from data store (injected)
  private IChassisDAO chassisDAO;
  private IEngineDAO engineDAO;
  private IWheelDAO wheelDAO;
  private IAccessoryDAO accessoryDAO;

  public Vehicle getLuxuryCar() {
    VehicleBuilder builder = getVehicleBuilder();
    builder.setChassis(chassisDAO.findBySize(ChassisSize.NORMAL));
    builder.setEngine(engineDAO.findByPower(EnginePower.HIGH));
    builder.setWheelTemplate(wheelDAO.findBySize(WheelSize.NORMAL));
    builder.setAccessories(accessoryDAO.findByType(Accessory.LUXURY));
    return builder.build();
  }

  public Vehicle getOffroadCar() {
    VehicleBuilder builder = getVehicleBuilder();
    builder.setChassis(chassisDAO.findBySize(ChassisSize.LARGE));
    builder.setEngine(engineDAO.findByPower(EnginePower.HIGH));
    builder.setWheelTemplate(wheelDAO.findBySize(WheelSize.LARGE));
    return builder.build();
  }

  // Similar operations ...
}

你会如何进行单元测试? 我对此有点想过,我已经确定了几个选项:

在不模仿构建器的情况下对返回的车辆进行测试
从界面的角度来看,这是最好的选择,但它不会成为VehicleRepository上的独立单元测试。与仅测试VehicleBuilder相比,我无法看到与VehicleBuilder一起测试VehicleRepository的任何真正优势。 VehicleBuilder也可能具有复杂的逻辑(例如检查附件存在),这会导致多个执行路径,这会使VehicleRepository的测试变得非常复杂。

重构代码以测试构建器的状态
一个例子是将getLuxuryCar操作拆分为两个操作:

public Vehicle getLuxuryCar() {
  VehicleBuilder builder = getLuxuryCarBuilder();
  return builder.build();
}

// Visible for testing in the same package
protected VehicleBuilder getLuxuryCarBuilder() {
  VehicleBuilder builder = getVehicleBuilder();
  builder.setChassis(chassisDAO.findBySize(ChassisSize.NORMAL));
  builder.setEngine(engineDAO.findByPower(EnginePower.HIGH));
  builder.setWheelTemplate(wheelDAO.findBySize(WheelSize.NORMAL));
  builder.addAccessory(accessoryDAO.findByType(Accessory.LUXURY));
  return builder;
}

我更喜欢状态测试和行为测试,测试可能会更有弹性,但我不太喜欢生成的代码(尤其是getLuxuryCar()方法,它并没有真正做任何事情)。

模拟VehicleBuilder并测试行为
我相信这样的单元测试只是所测试的实际操作的复制品,并且不会增加任何真正的好处。它也将非常脆弱,完全依赖于被测试操作的内部。可能的单元测试(使用JMock)将类似于:

@Test
public void shouldBuildLuxuryCar() {
  context.checking(new Expectations() {{
    oneOf(chassisDAOmock).findBySize(ChassisSize.NORMAL);
    oneOf(vehicleBuilderMock).setChassis(with(any(Chassis.class)));

    oneOf(engineDAOmock).findByPower(EnginePower.HIGH);
    oneOf(vehicleBuilderMock).setEngine(with(any(Engine.class)));

    oneOf(wheelDAOmock).findBySize(WheelSize.NORMAL));
    oneOf(vehicleBuilderMock).setWheelTemplate(with(any(Wheel.class)));

    oneOf(accessoryDAOmock).findByType(Accessory.LUXURY));
    oneOf(vehicleBuilderMock).setAccessories(with(any(Set.class)));

    oneOf(vehicleBuilderMock).build();
  }});

  context.assertIsSatisfied();
}

不要对其进行单元测试
我倾向于这个选项,因为我对任何替代方案都不满意,而且操作似乎并不需要任何测试。为了澄清,我仍然会单独测试VehicleBuilder,而不是VehicleRepository。然而,这似乎并不是普遍的看法,例如可以看出这个问题:Unit Testing - What not to test

1 个答案:

答案 0 :(得分:3)

我认为你的观点都有其优点,这仅仅是我会做的建议。

集成测试更适合此代码,使用注入的DAO对象生成模拟数据或具有测试数据源。

然而,您的问题是针对单元测试的,在这种情况下,我认为针对返回的车辆对象进行测试(不嘲笑构建器)是最佳选择。为了避免复杂性,我会继续对返回的车辆进行简单的测试(检查它不是空的,也许是它的'类不变量),而不是关于车辆类型的细节(这个测试留给车辆的建造者/生产者)。

测试的这个优点是:

  1. 如果车辆/建造商后来扩展到需要其他属性(例如所有车辆都应该刹车),但开发人员忽略更新存储库,则测试将失败。
  2. 它将作为一个完整性测试,至少确保代码执行毫无例外。