首页 > 解决方案 > 在进行单元测试时,在不需要时模拟测试对象的依赖关系是一种好习惯吗?

问题描述

例如,让我们看 3 个类:

C

甲(乙,丙)

所以 A 依赖于 B 和 C。如果 B 和 C 已经过单元测试 - 在为 A 编写单元测试时,如果不需要,用 B 和 C 的模拟对象创建测试对象 A 被认为是一种好习惯至?

标签: javaunit-testingdependency-injectionmockitostandards

解决方案


TL;DR:是的,这是一种很好的做法,因为在 A 类中创建对象 B 和 C 是有原因的,我们希望确保 A 类正确处理交互。


在单元测试中,您需要测试单元测试所针对的类。因此,您不希望在 A 类的单元测试中测试 B 类和 C 类的功能。但是,我们可以假设 A 类将对其 B 类和 C 类的实例做一些有意义的事情,否则在类 A 的构造函数。这就是你模拟它们的原因:要么测试这些实例的目的是否已经实现,要么允许你测试类中的条件。换句话说:您的类是否正确处理与其依赖项的交互?

为了让这些概念更清楚一点,我在下面写了一些伪代码。我用 Mockito 编写了这个,但是另一个单元测试框架可以正常工作:

public class A {

  private final RepositoryB repositoryB;
  private final HelperC helperC;

  @Inject
  public A(RepositoryB repositoryB, HelperC helperC){
    this.repositoryB = repositoryB;
    this.helperC = helperC;
  }

  public List<ObjectD> performATask(boolean doSomeMagic){
    List<ObjectD> ds = repositoryB.getAll();

    if(doSomeMagic){
      helperC.doSomeMagic(ds);
    }

    return ds;
  }
}
public class ATest {

  @Rule
  public MockitoRule rule = MockitoJUnit.rule();

  @Mock
  private RepositoryB repositoryB;

  @Mock
  private HelperC helperC;
  
  @InjectMocks
  private A a;

  @Test
  public performATask_CallMethodWithNoMagic_MuggleListIsReturned(){
    List<ObjectD> ds = a.performATask(false);

    Mockito.verify(repositoryB).getAll();
    Mockito.verifyNoInteractions(helperC);
  }

  @Test
  public performATask_CallMethodWithMagic_MagicalListIsReturned(){
    Mockito.when(repositoryB.getAll()).thenReturn(createMuggleTestList());

    List<ObjectD> ds = a.performATask(true);

    Mockito.verify(repositoryB).getAll();
    Mockito.verify(helperC).doSomeMagic(anyList());
  }

  private List<ObjectD> createMuggleTestList(){
    // Create it!
  }
}

您在这里看到的是,A 类确实依赖于 B 类(RepositoryB)和 C(HelperC)。在 A 类中,您可以看到 RepositoryB 类用于从数据库中检索数据。数据以列表形式返回。根据传递的布尔参数,HelperC 将或不会对检索到的数据执行一些魔术。我们不在乎它有什么魔力,只关心它是基于布尔参数发生的。同样,我们不关心 RepositoryB 如何从数据库中检索其数据。这就是那些各自类的单元测试所担心的。我们想知道的是:A 类是否仍然调用 RepositoryB.getAll 并且是否调用 helperC.doSomeMagic?

如果调用 performATask(false),则第一个单元测试测试是否只调用了 RepositoryB.getAll(并且完全忽略了 HelperC)。第二个单元测试测试是否调用了 RepositoryB.getAll 以及如果调用了 performATask(true) 则测试了 HelperC.doSomeMagic。另请注意,RepositoryB 模拟用于使用 Mockito.when() 返回测试列表。这在第二个单元测试中很重要:

调用模拟方法实际上不会做任何事情;毕竟,这是一个模拟。但是如果在第二个单元测试中调用 HelperC.doSomeMagic,则 HelperC 上的验证需要一个 List。这是有道理的,因为您要确保始终使用 List 而不是其他东西(例如 null)调用 HelperC.doSomeMagic,因为这是 A 类的理想行为。但是 RepositoryB.getAll() 将 - 如果 RepositoryB 是一个模拟 -不返回任何内容,因此将返回 null。然后,此验证将失败(因此您的单元测试将失败,这是理所当然的):

Mockito.verify(helperC).doSomeMagic(anyList()); 它希望将一个列表传递给 doSomeMagic,而不是 null。

所以mock也用于在特定条件下返回一个List。(在这种情况下, Mockito.when 用于返回在单元测试中创建的测试列表。)


推荐阅读