원문: https://testing.googleblog.com/2018/02/testing-on-toilet-cleanly-create-test.html (Translated by Google Gemini)


테스트를 작성할 때, 우리는 종종 테스트 대상 코드가 소비할 객체들을 만들어야 합니다. 이 객체들은 진짜 객체일 수도 있고, 페이크(fake)나 목(mock) 객체일 수도 있습니다. 이러한 객체들을 생성하는 코드는 빠르게 지저분해지고, 테스트의 가독성을 해치며, 유지보수를 어렵게 만들 수 있습니다.

다음 예제를 살펴보겠습니다.

@Test
public void testWriteSecureMessage() {
  // 테스트를 위한 설정
  Message message = new Message();
  message.setId("42");
  message.setBody("Four score and seven years ago...");
  message.setSignature("1a2b3c4d5e");

  User sender = new User();
  sender.setId("Abc.123");
  sender.setName("Abraham Lincoln");

  // 테스트 실행
  writer.writeSecureMessage(message, sender);

  // 상태 검증
  assertEquals("Sent secure message with signature 1a2b3c4d5e", logger.getInfo());
}

이 테스트에서 객체 생성은 꽤 장황합니다. 테스트의 핵심 로직을 이해하기 어렵게 만들고, 테스트가 실제로 무엇을 검증하려는지 불분명하게 만듭니다.

이런 문제는 테스트 데이터 빌더(Test Data Builder) 를 사용하여 해결할 수 있습니다. 빌더는 객체 생성을 캡슐화하는 간단한 클래스입니다. 일반적으로 빌더는 객체의 각 필드에 대해 withFieldName() 형태의 메서드를 가지며, 최종적으로 객체를 생성하는 build() 메서드를 가집니다.

이제 테스트 데이터 빌더를 사용한 동일한 테스트를 보겠습니다.

@Test
public void testWriteSecureMessage() {
  // 테스트를 위한 설정
  Message message = aMessage()
      .withSignature("1a2b3c4d5e")
      .build();
  User sender = anUser().build();

  // 테스트 실행
  writer.writeSecureMessage(message, sender);

  // 상태 검증
  assertEquals("Sent secure message with signature 1a2b3c4d5e", logger.getInfo());
}

빌더를 사용하면 테스트에 필요한 필드만 지정하면 되므로 객체 생성 코드가 훨씬 간결해집니다. 빌더는 나머지 필드들을 합리적인 기본값으로 채워줍니다. 이제 이 테스트는 메시지의 서명(signature)이 중요하다는 것을 명확하게 보여줍니다.

빌더는 또한 중복을 줄이는 데 도움이 됩니다. 여러 테스트에서 동일하거나 유사한 객체를 생성해야 할 때, 빌더는 생성 로직을 한곳에 모아줍니다. 만약 객체의 생성 방식이 변경되면(예를 들어, 생성자에 새로운 필수 파라미터가 추가될 때), 여러 테스트를 수정하는 대신 빌더만 업데이트하면 됩니다.

좋은 테스트 데이터 빌더는 다음과 같은 특징을 가집니다.

  • 재사용 가능해야 합니다. 빌더는 build()를 여러 번 호출할 수 있어야 하며, 각각 새로운 객체를 반환해야 합니다.
  • 불필요한 정보는 숨겨야 합니다. 빌더는 테스트에 중요하지 않은 필드들에 대해 합리적인 기본값을 제공해야 합니다.
  • 읽기 쉬워야 합니다. 빌더의 메서드 이름은 명확하고 표현력이 풍부해야 합니다.
  • 설정이 쉬워야 합니다. 빌더는 객체를 원하는 상태로 쉽게 만들 수 있도록 유연한 인터페이스를 제공해야 합니다.

테스트 데이터 빌더를 사용함으로써, 우리는 테스트를 더 읽기 쉽고, 유지보수하기 쉬우며, 견고하게 만들 수 있습니다.