본문 바로가기
Develop/Java

[Java] 쉽다 쉬워! 전래동화를 통해 알아보는 단위 테스트

by 제이._ 2023. 2. 16.

오늘은 단위 테스트에 대해 알아보겠습니다.


단위 테스트란?

단위 테스트는 영어로 Unit Test입니다.

 

단위 테스트는 모듈 혹은 애플리케이션 안에 있는 개별적인 코드 단위가 의도한 대로 작동하는지 확인하는 행위입니다.

 

 

"뭔 말인가요? 더 쉽게 설명해 주세요!"

==> 하나의 애플리케이션에는 한 개 이상의 작동하는 기능들로 구성되어 있습니다.

단위 테스트란, 이런 기능들을 '개별적으로' 테스트함을 의미합니다.

 

 

 

 

"아~ 그러면 단위 테스트는, 기능들이 개별적으로 작동하는지 테스트하는 행위이군요."

맞습니다! 이렇게 생각하셨다면 단위 테스트의 뜻을 파악하신 것입니다👍

 


 

그렇다면 단위 테스트는 왜 사용할까요? 다음 예시를 통해 알아보겠습니다.

 

애니메이션을 바탕으로 프로그램 구현을 하는 '홍보와 졸부' 회사에 신입 개발자 제이가 들어왔습니다.

회사에 들어왔는데, 이오가 제이에게 새로운 프로그램 제작을 맡겼습니다.

 

이오 : [토끼와 거북이 경주 게임]을 다음과 같은 흐름으로 만들어주세요!

1. 토끼와 거북이의 이름은 각각 'R', 'T'로 시작됩니다.

2. 이동 시도할 회수를 받아주세요.
==> n 번의 라운드 동안 경주를 하게 됩니다.
==> "거리 설정은, 토끼가 빠르니깐.. 이동할 때마다 3씩 증가시켜주고, 거북이는 느리니깐 1씩 증가시켜주세요!"

3. 매 라운드마다 현재 상태를 출력해 주세요.

4. 우승자를 출력해 주세요.

 

이오가 위와 같은 프로그램을 만들어 달라고 요청을 했습니다!

 

이오의 요구사항에 맞게 프로그램을 만들었는데 잘 작동이 되는지 확인을 해볼까요?

(예상 결과 : Turtle 2, Rabbit 6 / Rabbit win)

 

 

실행을 시켜서 결과를 확인해 보면 위에 사진과 같이 프로그램이 잘 작동되는 것을 확인할 수 있습니다.

 

이오의 요구사항처럼 토끼는 이동 시마다 +3씩 증가하고, 거북이는 +1씩 증가했습니다.

 

 

 

 

프로그램을 다 만들었는데, 갑자기 우르가 새로운 요구사항을 들고 왔습니다.

 

우르 : 제이, 거북이가 너무 불쌍해요!
현재 거북이는 이동 시마다 +1씩 증가하지만, +2씩 증가하게 바꿔주세요.

 

 

우르의 요구사항에 맞게 다시 리팩토링을 진행했으니, 작동이 잘 되는지 확인해 봐야겠죠?

다시 한번 프로그램을 작동 시켜보겠습니다.

 

 

이런! 오도가 신입 개발자 제이를 방해하려고 결과를 보려면 1~100까지 입력하게 바꿔놨습니다!

 

 

 

 

마지막으로 코코닥이 새로운 요구사항을 들고 왔습니다.

코코닥 : 제이, 나는 평등한 게 좋으니 거북이와 토끼가 모두 움직일 때마다 +3씩 움직이게 만들어주세요!

마지막으로 코코닥의 요구 사항대로 리팩토링을 진행하고, 작동을 확인해 봤습니다.

 

 

제이는 눈물을 머금고 1~100까지의 숫자를 입력하고 리팩토링 한 결과를 확인했습니다.

 

 

신입 개발자 제이는 또 다른 요구사항에 대비를 해야 했지만, 오도의 방해로 걱정을 했습니다.

이를 타파하기 위해서 결국 밤 잠을 설치면서 열심히 구글링을 하며 '단위 테스트'를 공부하게 됩니다!

 

제이는 공부를 마치고, 코코닥의 요구사항에 맞게 한 번 이동 시마다 토끼와 거북이의 거리는 +3씩 증가하는 기능을 테스트하려고 합니다.

 

public class PlayersTest {

    @Test
    @DisplayName("코코닥의 요구사항에 맞게, 토끼와 거북이는 한 번 이동시마다 +3씩 거리가 증가한다!")
    void test_moveAll() {
        // given
        int defaultDistance = 0;
        int expectedDistance = 3;

        Player rabbit = new Player("Rabbit", defaultDistance);
        Player turtle = new Player("Turtle", defaultDistance);
        List<Player> participants = List.of(rabbit, turtle);
        Players racingPlayers = new Players(participants);

        // when
        racingPlayers.moveAll();

        // then
        assertThat(rabbit.getDistance()).isEqualTo(expectedDistance);
        assertThat(turtle.getDistance()).isEqualTo(expectedDistance);
    }
}

 

위와 같은 단위 테스트 작성을 통해서, 제이는 오도의 방해를 피해 리팩토링 한 코드의 결과를 확인할 수 있었습니다.

(단위 테스트 작성 방법은 아래에 기술하겠습니다.)

 

 

여기서 단위 테스트를 사용하면서 '신입 개발자 제이'는 어떤 결과를 얻었을까요?

  • 1 ~ 100까지의 숫자를 입력하지 않아도 결과를 확인할 수 있었다.
  • 추후에 새로운 요구사항으로 인해 리팩토링을 진행해도 결과를 바로 확인할 수 있다.

 

 

이렇게 두 가지 결과를 얻었습니다.

다시 이 내용을 조금 더 보편적인 말로 정리하자면 다음과 같습니다.

 

  • 1 ~ 100까지의 숫자를 입력하지 않아도 결과를 확인할 수 있었다.
    • ==> 기능의 일부를 빠르게 검증할 수 있다.
  • 추후에 새로운 요구사항으로 인해 리팩토링을 진행해도 결과를 바로 확인할 수 있다.
    • ==> 서비스 작동에 오류가 있을 시 코드의 문제점을 빠르게 확인할 수 있다.
    • ==> 단위 테스트를 통해 리팩토링 및 기능 검증을 빠르게 진행할 수 있다.

 


 

자신감이 찬 개발자 제이는, 훗날 들어올 후임 개발자들을 위해 몇 가지 단위 테스트 꿀팁을 남겼습니다.

 

  1. 테스트 코드를 작성하기 위해서 [Given - When - Then] 패턴을 사용하면 편리하다!
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;


public class PlayersTest {

    @Test
    @DisplayName("코코닥의 요구사항에 맞게, 토끼와 거북이는 한 번 이동시마다 +3씩 거리가 증가한다!")
    void test_moveAll() {
        // given (테스트를 위해 준비된 데이터 및 환경) : 준비
        int defaultDistance = 0;
        int expectedDistance = 3; // 기능 작동 후 예상하는 이동 거리

        Player rabbit = new Player("Rabbit", defaultDistance);
        Player turtle = new Player("Turtle", defaultDistance);
        List<Player> participants = List.of(rabbit, turtle);
        Players racingPlayers = new Players(participants);

        // when (테스트 대상에게 메서드를 실행한다. == 기능을 동작한다.) : 실행
        racingPlayers.moveAll();
        
        // then (기능 동작 후, 의도한 결과가 나왔는지 검증한다.) : 검증
        // 검증 1. 토끼의 이동 후 거리는 expectedDistance 변수의 값과 동일하다.
        assertThat(rabbit.getDistance()).isEqualTo(expectedDistance);

        // 검증 1. 거북이의 이동 후 거리는 expectedDistance 변수의 값과 동일하다.
        assertThat(turtle.getDistance()).isEqualTo(expectedDistance);
    }
}

 

예외를 찾는 경우에 given에 주어지는 값에 따라 테스트의 결과가 달라질 수 있습니다.

 

이런 경우, given에 따라서 테스트를 n 개를 만드는 것보다는, @ParameterizedTest 어노테이션을 이용해서 다양한 값을 넣어서 여러 예외를 잡을 수 있습니다.

 

 

 

 

3. 좋은 테스트를 위해선 FIRST 규칙을 따르자!

 

  • Fast
    • 좋은 단위 테스트는 내부 코드만 테스트를 진행하여 빠른 동작을 해야 한다.
  • Independent
    • 좋은 단위 테스트는 테스트하고자 하는 '단위 기능에 집중'해야 한다.
  • Repeatable
    • 좋은 단위 테스트는 반복 수행을 하더라도 같은 결과가 나와야 한다.
  • Self-Validating
    • 좋은 단위 테스트는 실패 혹은 성공을 해야 한다.
    • 또한 사람이 콘솔 출력같이 수동적으로 검증하는 것이 아닌 테스트 자체적으로 결과가 나와야 한다.
  • Timely
    • 좋은 단위 테스트는 제때 미루지 않고 작성해야 한다.