我的问题将是一个简化的例子(在 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 ...
}
Run Code Online (Sandbox Code Playgroud)
你会如何对这个类进行单元测试?我想了一会儿,我已经确定了几个选项:
在不模拟构建器的情况下测试返回的 Vehicle
从接口的角度来看,这是最好的选择,但它不会是 VehicleRepository 上的孤立单元测试。与仅测试 VehicleBuilder 相比,我看不出将 VehicleRepository 与 VehicleBuilder 一起测试有什么真正的好处。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;
}
Run Code Online (Sandbox Code Playgroud)
我更喜欢状态测试而不是行为测试,并且测试可能会更有弹性,但我不太喜欢结果代码(尤其是 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();
}
Run Code Online (Sandbox Code Playgroud)
不要对它进行单元测试
我倾向于这个选项,因为我对任何替代方案都不满意,而且操作似乎并不真正需要任何测试。为了澄清起见,我仍将单独对 VehicleBuilder 进行单元测试,但不会对 VehicleRepository 进行单元测试。然而,这似乎不是主流意见,例如可以从以下问题中看出:单元测试 - 什么不测试
我认为你的观点都有道理,这只是我会做什么的建议。
集成测试更适合此代码,使用生成模拟数据或具有测试数据源的注入 DAO 对象。
然而,您的问题特定于单元测试,在这种情况下,我认为针对返回的车辆对象进行测试(不模拟构建器)是最好的选择。为了避免复杂性,我将对返回的车辆进行简单的测试(检查它是否为空,也许是它的类不变量),而不是有关车辆类型的详细信息(此测试留给车辆的制造商/生产商)。
该测试的优点是: