我应该在带有不可金道的单元测试中使用真实的对象或模拟



如果我必须测试使用可变实体的服务,我将构建我需要的最小对象(真实的对象)并将其传递给我的服务。示例:

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;
}

相关内容

  • 没有找到相关文章

最新更新