자바와 JUnit을 활용한 실용주의 단위 테스트 (5)

아연·2023년 10월 19일

Book

목록 보기
6/8
post-thumbnail

<자바와 JUnit을 활용한 실용주의 단위 테스트> 책과 Junit 5, AssertJ 공식문서를 참고하였습니다.



FIRST 원리

Fast

🔑 역할(책임)을 분리해서 빠른 테스트를 유지하라.

import iloveyouboss.controller.*;
import java.util.*;
import java.util.concurrent.atomic.*;

public class StatCompiler {

   private QuestionController controller = new QuestionController();

   public Map<String, Map<Boolean, AtomicInteger>> responsesByQuestion(
         List<BooleanAnswer> answers) {
      Map<Integer, Map<Boolean, AtomicInteger>> responses = new HashMap<>();
      answers.stream().forEach(answer -> incrementHistogram(responses, answer));
      return convertHistogramIdsToText(responses);
   }

   private Map<String, Map<Boolean, AtomicInteger>> **convertHistogramIdsToText**(
         Map<Integer, Map<Boolean, AtomicInteger>> responses) {
      Map<String, Map<Boolean, AtomicInteger>> textResponses = new HashMap<>();
      responses.keySet().stream().forEach(id -> 
         textResponses.put(**controller.find(id)**.getText(), responses.get(id)));
      return textResponses;
   }
}
public Question find(Integer id) {
		return em().find(Question.class, id);
}

convertHistogramIdsToText() 의 역할

  1. 컨트롤러에서 id에 맞는 질문 찾기
    1. controller.find(id)
  2. 질문과 답변 히스토그램 매핑하기
    1. textResponses.put(질문, 답변 히스토그램)

영속적 저장소(em, 데이터베이스)과 상호 작용(find())하고 있다.

convertHistogramIdsToText() 테스트는 느리다.


그렇다면 [1 번 역할]을 분리하자.
import iloveyouboss.controller.*;
import java.util.*;
import java.util.concurrent.atomic.*;

public class StatCompiler {
   private QuestionController controller = new QuestionController();
   
   public Map<Integer,String> **questionText**(List<BooleanAnswer> answers) {
      Map<Integer,String> questions = new HashMap<>();
      answers.stream().forEach(answer -> {
         if (!questions.containsKey(answer.getQuestionId()))
            questions.put(answer.getQuestionId(), 
               **controller.find(answer.getQuestionId()).getText());**
			});
      return questions;
   }

   public Map<String, Map<Boolean, AtomicInteger>> responsesByQuestion(
         List<BooleanAnswer> answers, Map<Integer,String> questions) {
      Map<Integer, Map<Boolean, AtomicInteger>> responses = new HashMap<>();
      answers.stream().forEach(answer -> incrementHistogram(responses, answer));
      return convertHistogramIdsToText(responses, **questions**);
   }

   private Map<String, Map<Boolean, AtomicInteger>> convertHistogramIdsToText(
         Map<Integer, Map<Boolean, AtomicInteger>> responses, 
         **Map<Integer,String> questions**) {
      Map<String, Map<Boolean, AtomicInteger>> textResponses = new HashMap<>();
      responses.keySet().stream().forEach(id -> 
         textResponses.put(**questions.get(id)**, responses.get(id)));
      return textResponses;
   }
}

1 번 역할 ‘컨트롤러에서 id에 맞는 질문 찾기’ 를 questionText()로 분리했다. 이제 convertHistogramIdsToText()는 메모리상의 해시 맵만 사용하며 느린 영속적 저장소를 조회하지 않는다.

나아연(은)는 빠른 테스트를 유지했다 !


Isolated

🔑 테스트 코드는 어떤 순서나 시간에 관계없도록 유지해라.

좋은 단위 테스트는 다른 단위 테스트에 의존하지 않는다. 각 테스트가 작은 양의 동작에만 집중하면 테스트 코드를 집중적이고 독립적으로 유지하기 쉬워진다.

그래야 테스트가 실패하는 원인을 파악하기 쉽다! 즉, SRP(단일 책임 원칙)에 따라 클래스는 작고 단일한 목적을 가져야 한다.



Repeatable

🔑 좋은 테스트는 반복 가능해야 하며 항상 결과가 동일해야 한다.

반복 가능한 테스트는 실행할 때마다 결과가 같아야 한다. 즉, 상황에 따라 성공 여부가 변경되는 테스트 코드를 만들지 말아야 한다. 반복 가능한 테스트를 만들기 위해서는 직접 통제할 수 없는 외부 환경 요소들과 격리시켜야 한다. 하지만 시스템은 불가피하게 통제할 수 없는 요소와 상호 작용해야 할 것이다. 이 경우 테스트 대상 코드와 외부 환경 요소의 독립성을 유지하는 방법으로 Mock 객체를 사용할 수 있다. ex) 현재 시간



Self-validating

🔑 테스트는 스스로 검증 가능해야 한다.

  • 단언이 없는 테스트는 테스트가 아니다.
  • 테스트에 필요한 모든 설정 단계를 자동화 하자. CI 도구를 사용한 테스트 자동화도 가능하다.


Timely

🔑 단위 테스트로 코드를 검증하는 것을 미루지 말자.

0개의 댓글