원문: https://testing.googleblog.com/2017/01/testing-on-toilet-keep-cause-and-effect.html (Translated by Google Gemini)


이 테스트에서 어떤 라인을 안전하게 제거할 수 있을까요?

@Test
public void addPermissionToDatabase() {
  new UserAuthorizer(mockUserService, mockPermissionDb).grantPermission(USER, READ_ACCESS);

  // 이 메서드 중 하나라도 호출되지 않으면 테스트는 실패합니다.
  verify(mockUserService).isUserActive(USER);
  verify(mockPermissionDb).getPermissions(USER);
  verify(mockPermissionDb).isValidPermission(READ_ACCESS);
  verify(mockPermissionDb).addPermission(USER, READ_ACCESS);
}

정답은 상태를 변경하지 않는 메서드를 검증하는 호출들을 제거할 수 있다는 것입니다.

다른 객체에 대한 메서드 호출은 다음 두 가지 범주 중 하나에 속합니다.

  • 상태 변경(State-changing): 부작용(side effects)이 있고 테스트 대상 코드 외부의 세상을 바꾸는 메서드. 예: sendEmail(), saveRecord(), logAccess().
  • 비-상태 변경(Non-state-changing): 테스트 대상 코드 외부 세상에 대한 정보를 반환하고 아무것도 수정하지 않는 메서드. 예: getUser(), findResults(), readFile().

일반적으로 상태를 변경하지 않는 메서드가 호출되었는지 검증하는 것은 피해야 합니다.

  • 종종 중복적입니다: 세상의 상태를 바꾸지 않는 메서드 호출은 그 자체로는 의미가 없습니다. 테스트 대상 코드는 그 메서드 호출의 반환 값을 사용하여 다른 작업을 수행할 것이고, 바로 그 작업을 검증(assert)하면 됩니다.
  • 테스트를 불안정하게 만듭니다: 메서드 호출 방식이 바뀔 때마다 테스트를 업데이트해야 합니다. 예를 들어, 테스트가 mockUserService.isUserActive(USER)가 호출되기를 기대하고 있다면, 테스트 대상 코드가 user.isActive()를 호출하도록 수정될 경우 테스트는 실패할 것입니다.
  • 테스트 가독성을 떨어뜨립니다: 테스트에 추가적인 검증 구문이 많아지면, 어떤 메서드 호출이 실제로 세상의 상태에 영향을 미치는지 파악하기 더 어려워집니다.
  • 잘못된 안정감을 줍니다: 테스트 대상 코드가 어떤 메서드를 호출했다는 사실만으로는, 그 코드가 메서드의 반환 값을 가지고 올바른 일을 했다는 것을 의미하지 않습니다.

이런 메서드들이 호출되었는지 검증하는 대신, when(mockUserService.isUserActive(USER)).thenReturn(false)와 같이 테스트에서 다양한 조건을 시뮬레이션하는 데 사용하세요. 그리고 나서 테스트 대상 코드의 반환 값을 검증하거나, 상태를 변경하는 메서드 호출을 검증하세요.

상태를 변경하지 않는 메서드 호출을 검증하는 것은 검증할 다른 출력이 없을 때 유용할 수 있습니다. 예를 들어, 코드가 RPC 결과를 캐싱해야 한다면, RPC를 호출하는 메서드가 한 번만 호출되었는지 검증할 수 있습니다.

불필요한 검증을 제거하면 테스트는 이렇게 보입니다.

@Test
public void addPermissionToDatabase() {
  new UserAuthorizer(mockUserService, mockPermissionDb).grantPermission(USER, READ_ACCESS);

  // 상태를 변경하는 메서드만 검증합니다.
  verify(mockPermissionDb).addPermission(USER, READ_ACCESS);
}

훨씬 간단해졌죠! 하지만 기억하세요. 메서드가 호출되었는지 검증하기 위해 목(mock)을 사용하는 대신, 실제 객체나 페이크(fake) 객체를 사용하여 메서드를 실제로 실행하고 그것이 제대로 작동하는지 확인하는 것이 훨씬 더 좋습니다. 예를 들어, 위 테스트는 addPermission()이 호출되었는지 검증하는 대신, 페이크 데이터베이스를 사용하여 데이터베이스에 권한이 존재하는지 확인할 수 있습니다.