원문: https://testing.googleblog.com/2015/01/testing-on-toilet-prefer-testing-public.html (Translated by Google Gemini)


아래 클래스는 테스트가 필요할까요?

class UserInfoValidator {
    public void validate(UserInfo info) {
        if (info.getDateOfBirth().isInFuture()) {
            throw new ValidationException();
        }
    }
}

이 클래스의 메서드에는 로직이 포함되어 있으니 테스트하는 것이 좋을 수 있습니다. 하지만 이 클래스를 사용하는 유일한 코드가 아래와 같다면 어떨까요?

public class UserInfoService {
    private UserInfoValidator validator;
    public void save(UserInfo info) {
        validator.validate(info); // 값이 유효하지 않으면 예외를 던짐
        writeToDatabase(info);
    }
}

답은 다음과 같습니다: 아마도 UserInfoValidator는 테스트가 필요 없을 것입니다. 왜냐하면 모든 경로를 UserInfoService를 통해 테스트할 수 있기 때문입니다. 여기서 핵심적인 구분은 **이 클래스가 Public API가 아니라 구현 세부사항(implementation detail)**이라는 점입니다.

Public API는 수많은 사용자에 의해 호출될 수 있으며, 사용자들은 메서드에 가능한 모든 입력 조합을 전달할 수 있습니다. 여러분은 이 API들이 잘 테스트되었는지 확인하고 싶을 것입니다. 그래야 사용자들이 API를 사용할 때 문제를 겪지 않을 것이기 때문입니다. Public API의 예시로는 코드베이스의 다른 파트에서 사용되는 클래스(예: 클라이언트 사이드에서 사용되는 서버 사이드 클래스)나 코드베이스 전반에 걸쳐 사용되는 공통 유틸리티 클래스가 있습니다.

구현 세부사항 클래스는 오직 Public API를 지원하기 위해 존재하며, 매우 제한된 수의 사용자(종종 단 한 명)에 의해서만 호출됩니다. 이러한 클래스들은 때때로 그것들을 사용하는 Public API를 테스트함으로써 간접적으로 테스트될 수 있습니다. 물론 구현 세부사항 클래스를 테스트하는 것이 여전히 유용한 경우도 많습니다. 예를 들어, 해당 클래스가 복잡하거나 Public API 클래스에 대한 테스트를 작성하기 어려운 경우입니다.

하지만 그런 클래스들을 테스트할 때조차, 종종 Public API만큼 깊이 있게 테스트할 필요는 없습니다. 왜냐하면 어떤 입력값들은 절대로 그 메서드에 전달되지 않을 수 있기 때문입니다. (위 코드 샘플에서, 만약 UserInfoServiceUserInfo가 절대 null이 아니도록 보장한다면, UserInfoValidator.validatenull이 인자로 전달될 때 어떤 일이 일어나는지 테스트하는 것은 쓸모가 없을 것입니다. 그런 일은 절대 일어나지 않으니까요.)

구현 세부사항 클래스는 때때로 별도의 클래스에 존재하는 private 메서드로 생각할 수 있습니다. 일반적으로 private 메서드 역시 직접 테스트하고 싶지 않기 때문입니다. 또한 Java에서 패키지-전용(package-private)으로 만드는 것처럼, 구현 세부사항 클래스의 가시성을 제한하려고 노력해야 합니다.

구현 세부사항 클래스를 너무 자주 테스트하면 몇 가지 문제가 발생합니다:

  • 코드를 유지보수하기 더 어려워집니다. 구현 세부사항 클래스의 메서드 시그니처를 변경하거나 리팩토링을 할 때처럼, 테스트를 더 자주 업데이트해야 하기 때문입니다. 만약 테스트가 Public API를 통해서만 이루어진다면, 이러한 변경사항들은 테스트에 전혀 영향을 미치지 않을 것입니다.
  • 만약 어떤 동작을 구현 세부사항 클래스를 통해서만 테스트한다면, 코드에 대한 잘못된 자신감을 얻을 수 있습니다. 동일한 코드 경로가 Public API를 통해 실행될 때는 제대로 작동하지 않을 수 있기 때문입니다.

또한 리팩토링을 할 때 더 신중해야 합니다. 만약 모든 경로가 Public API를 통해 테스트되지 않는다면, Public API의 모든 동작이 보존될 것이라고 보장하기 더 어려울 수 있기 때문입니다.