如果我必须测试使用可变实体的服务,我将构建我需要的最小对象(真实的对象)并将其传递给我的服务。示例:
User joe = new User();
joe.setEmail("joe@example.com");
resetPasswordService.resetPassword(joe);
verif(emailServiceMock).sendEmail("joe@example.com", "Your password has been reset!");
显然用户有很多字段,但我没有设置它们,因为resetPasswordService
不需要它们。这是非常友好的,因为如果我重命名不是电子邮件的用户字段,则不会更改此测试。
当我尝试使用不可疑的对象进行相同操作时,问题出现。我将坚持同一示例,并将用户从实体变成不变的。
@Value.Immutable
public abstract class User {
public abstract String getEmail();
public abstract PostalAddress getPostalAddress();
//more fields
}
User joe = new ImmutableUserBuilder().email("joe@example.com").build();
resetPasswordService.resetPassword(joe);
verif(emailServiceMock).sendEmail("joe@example.com", "Your password has been reset!");
java.lang.illegalstateexception:无法构建用户,某些必需的属性未设置[postaladdress,signupdate,city,....]
当构建器试图构建对象时,这会失败。那我该怎么办?
- 为用户使用模拟,即使每次模拟返回模拟童话去世 ,它也会返回模拟
- 创建一个测试DSL,并拥有某种工厂来构建整个用户树结构,而我不需要的所有字段?看起来很重,不太友好。这使得测试的要求不是那么透明。
- 在用户
@Nullable
中制作所有字段,并让构建器不验证对象吗?这会使我面临生产中不完整对象的风险,对 - 我错过了其他一些选择?
我知道用户应该是实体,而不是不可变的价值对象。我在此示例中使用了用户,因为它易于理解。
简单答案:您唯一的如果必须使用模拟。
含义:当您需要以"真实"类不支持的方式进行对象的行为时。或当您必须验证在模拟上调用。
so:当您可以编写一个测试案例,该测试用例可以执行您想要的操作而无需使用模拟 - 然后去做。
模拟框架是工具。您不使用它们,因为您可以,但是因为它们为您解决了您无法解决的问题(轻松)。
除此之外:如所解释的,默认值应该是避免模拟。另一方面,编程始终是平衡的努力和"投资回报率"。这就是为什么我可以轻松地使用上面的单词。事实证明,使用模拟会导致写下2,3条易于理解的代码行...但是使用"真实"类要复杂得多(或依赖于某些隐式关于该类如何工作的假设) - 然后使用模拟可以是更好的选择。
从这个意义上讲,答案是:不要将答案和规则作为黄金标准。最后,这始终是关于人类判断的。
您的测试当前依赖于密码重置功能的实现详细信息。
这是您想要测试的行为:
- 给定用户
- 当该用户请求密码重置 时
- 然后发送电子邮件
假设您稍后决定更改密码重置功能,以便该电子邮件包括其名称:
亲爱的乔,
您已请求密码重置...
您的测试现在将使用NullPointerException
失败,因为您将测试策略基于User
实例永远不需要名称的假设。一个完全无害的变化导致我们的测试仍应通过时失败。
解决方案:使用真实对象。如果您在不同的测试中找到创建大量用户,请重构用户创建其自己的功能:
private User getUser()
{
User joe = new User();
joe.setEmail("joe@example.com");
joe.setName("Joe");
joe.setAge(20);
joe.setHeight(180);
return joe;
}