为什么我们不在单元测试中模拟域对象?

Cap*_*ain 5 java unit-testing mocking

我给你 2 个测试;其目的仅仅是确认何时service.doSomething被调用,emailService.sendEmail以该人的电子邮件作为参数调用。

@Mock 
private EmailService emailService;

@InjectMocks
private Service service;

@Captor
private ArgumentCaptor<String> stringCaptor;

@Test
public void test_that_when_doSomething_is_called_sendEmail_is_called_NO_MOCKING() {

    final String email = "billy.tyne@myspace.com";

    // There is only one way of building an Address and it requires all these fields
    final Address crowsNest = new Address("334", "Main Street", "Gloucester", "MA", "01930", "USA");
    // There is only one way of building a Phone and it requires all these fields
    final Phone phone = new Phone("1", "978-281-2965");
    // There is only one way of building a Vessel and it requires all these fields
    final Vessel andreaGail = new Vessel("Andrea Gail", "Fishing", 92000);
    // There is only one way of building a Person and it requires all these fields
    final Person captain = new Person("Billy", "Tyne", email, crowsNest, phone, andreaGail);

    service.doSomething(captain); // <-- This requires only the person's email to be initialised, it doesn't care about anything else

    verify(emailService, times(1)).sendEmail(stringCaptor.capture());

    assertThat(stringCaptor.getValue(), eq(email));   
}

@Test
public void test_that_when_doSomething_is_called_sendEmail_is_called_WITH_MOCKING() {

    final String email = "billy.tyne@myspace.com";

    final Person captain = mock(Person.class);
    when(captain.getEmail()).thenReturn(email);

    service.doSomething(captain); // <-- This requires the person's email to be initialised, it doesn't care about anything else

    verify(emailService, times(1)).sendEmail(stringCaptor.capture());   

    assertThat(stringCaptor.getValue(), eq(email));   
}
Run Code Online (Sandbox Code Playgroud)

为什么我的团队告诉我不要模拟运行测试所需的域对象,而不是实际测试的一部分?我被告知模拟仅用于测试服务的依赖项。在我看来,生成的测试代码更精简、更清晰、更易于理解。没有什么可以分散测试的目的,即验证调用是否emailService.sendEmail发生。这是我很长一段时间以来在许多工作中听到并接受的福音。但我还是不能同意。

Nat*_*hes 4

我想我理解你们团队的立场。

他们可能会说,您应该为具有难以实例化依赖关系的事物保留模拟。这包括调用数据库的存储库,以及可能拥有自己的依赖关系的其他服务。它不包括可以实例化的域对象(即使填写所有构造函数参数很痛苦)。

如果您模拟域对象,那么测试不会为您提供它们的任何代码覆盖率。我知道我宁愿尽可能多地通过服务、控制器、存储库等的测试来覆盖这些域对象,并尽量减少只是为了直接执行它们的 getter 和 setter 而编写的测试。这使得领域对象的测试可以集中于任何实际的业务逻辑。

这确实意味着如果域对象有错误,那么多个组件的测试可能会失败。我认为没关系。我仍然会对域对象进行测试(因为单独测试这些对象比确保服务测试中涵盖所有路径更容易),但我不想完全依赖域对象测试来准确地进行测试反映这些对象如何在服务中使用,这似乎要求太多。

你有一个观点,模拟允许你创建对象而无需填写所有数据(我确信真正的代码可以得到很多发布的代码更糟糕)这是一种权衡,但拥有包含实际域对象以及测试中的服务的代码覆盖率对我来说似乎是一个更大的胜利。

在我看来,你们的团队选择了在实用主义与纯粹性之间犯错误。如果其他人都达成了共识,你就需要尊重这一点。有些事情值得引起轰动。这不是其中之一。