단위 테스트와 TDD(Test-Driven Development)

박상민·2024년 3월 3일
2

개념 정리!

목록 보기
7/18
post-thumbnail

⭐️ 단위 테스트의 중요성과 좋은 단위 테스트의 특징

단위 테스트(Unit Test)를 작성해야 하는 이유
단위 테스트를 작성해야 하는 이유는 정말 많다.

  • 코드를 수정하거나 기능을 추가할 때 수시로 빠르게 검증
  • 리팩토링 시에 안정성을 확보
  • 개발 및 테스팅에 대한 시간과 비용 절감

큰 장점은 개발 및 테스팅에 대한 시간과 비용을 절감할 수 있다는 것이다. 테스트 코드를 작성하지 않았다면 여러 개의 버그가 잠재되어 있을 확률이 있고, 모든 버그들을 수정하고 테스트를 반복하는 비용은 기하급수적으로 늘어나게 된다. 그러므로 개발 및 테스팅에 대한 비용을 줄이기 윟 단위 테스트를 작성해야 한다.

좋은 테스트란?
테스트를 무작정 작성하라는 것이 아니다. 좋은 테스트를 작성해야 한다.
좋은 테스트의 특징은 'FIRST'라는 5가지 규칙을 따라야 한다.

  • Fast: 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 한다.
  • Independent: 각각의 테스트는 독립적이며 서로 의존해서는 안된다.
  • Repeatable: 어느 환경에서도 반복 가능해야 한다.
  • Self-Validating: 테스트는 성공 또는 실패로 bool 값으로 결과를 내어 자체적으로 검증되어야 한다.
  • Timely: 테스트는 적시에 즉, 테스트하려는 실제 코드를 구현하기 직전에 구현해야 한다.

정리하면 좋은 테스트란 빠르고 독립적으로 어느 환경에서도 실행이 가능하고 검증할 수 있어야 한다.
위 5가지 규칙 중 Timely에 집중하자. 테스트 코드를 작성하는 시점에 대한 이야기인데 테스트 코드를 실제 코드를 구현하기 직전에 구현하라고 설명한다.

📌 TDD(테스트 주도 개발)

테스트 코드를 먼저 작성하는 방법을 테스트 주도 개발(Test_Driven Development, TDD)라고 한다.
그렇다면 왜 프로덕션 코드보다 테스트 코드를 먼저 작성해야 할까?

테스트 코드를 먼저 작성해야 하는 이유

  • 깔끔한 코드 작성 가능
  • 장기적으로 개발 비용 절감
  • 개발이 끝나면 테스트 코드를 작성하는 것이 귀찮다. (특히 실패 케이스)

TDD의 궁극적은 목표는 작동하는 깔끔한 코드를 작성하는 것이다. TDD의 단계에는 리팩토링 단계가 있는데 이 과정을 거치면 중복된 코드들은 제거되고, 복잡한 코드들은 깔끔하게 정리된다.

테스크 코드를 작성하는 과정 중에서도 실패 테스트부터 작성해야 한다.
즉, 순차적으로 실패하는 테스트를 먼저 작성하고 테스트가 실패할 경우에만 새로운 코드를 작성해야 한다. 그리고 중복된 코드가 있으면 제거해야 한다.

TDD 방법 및 순서

  1. 실패하는 작은 단위 테스트를 작성한다. (처음에는 컴파일조차 되지 않을 수 있다.)
  2. 빨리 테스트를 통과하기 위해 프로덕션 코드를 작성한다. 이를 위해 정답이 아닌 가짜 구현 등을 작성할 수도 있다.
  3. 그 다음의 테스트 코드를 작성한다. 실패 테스트가 없을 경우에만 성공 테스트를 작성한다.
  4. 새로운 테스트를 통과하기 위해 프로덕션 코드를 추가 또는 수정한다.
  5. 1~4 단계를 반복하여 실패/성공의 모든 테스트 케이스를 작성한다.
  6. 개발된 코드들에 대해 모든 중복을 제거하며 리팩토링한다.

위의 과정을 따라 진행하면 자연스레 프로덕션 코드보다 테스트 코드를 먼저 작성하게 될 것이다. 물론 이론적으로만 해당 내용을 이해하기는 어렵다.
이를 Spring과 같은 프레임워크에 적용하는 것은 다른 영역이다.

TDD(Test-Driven Development, 테스트 주도 개발) 접근 방법

  • 가짜로 구현하기: 최대한 빨리 테스트를 통과하기 위해 정답이 아닌 가짜 정답을 구현하는 방법
  • 삼각측량법: 값이 다른 여러 테스트를 작성하고, 이를 일반화하여 정답을 구현하는 방법
  • 명백하게 구현하기: 정답을 바로 구현하는 방법

✔︎ 가짜로 구현하기

실패하는 테스트를 가장 빠르게 구현하는 방법은 아무 값이나 반환하도록 하는 것이다. 그리고 테스트가 통과하면 단계적으로 상수를 변수를 사용하도록 변형한다. 예를 들어 다음과 같은 곱하기 테스트를 작성하였다고 하자.

@Test
public void 곱하기테스트() {
    // given
    
    // when
    final int result = multiply(2, 3);
    
    // then
    assertThat(result).isEqualTo(6);
}

해당 테스트를 가장 빠르게 통과하는 방법은 6을 반환하는 것이다.

public int multiply(final int num1, final int num2) {
    return 6;
}

이렇게 변수를 사용하지 않고 상수를 반환하며, 답이 아닌 방법으로 가짜 구현하여 최대한 빨리 테스트를 통과하는 것이 가짜 구현 방법이다.

장점

  • 심리학적: 빨간 막대와 초록 막대 상태는 완전히 다르다. 막대가 초록색이라면 어느 위치인지 알고 거기부터 리팩토링해 갈 수 있다.
  • 범위 조절: 하나의 구체적인 예에서 일반화를 함으로써, 불필요한 고민으로 혼동되는 일을 예방할 수 있다.

✔︎ 삼각측량법

삼각측량법은 테스트 주도로 추상화된 과정을 일반화하는 과정이다. 삼각측량법은 테스트 예시가 2개 이상일 때에만 추상화를 해야 한다.

@Test
public void 곱하기테스트() {
    // given
    
    // when
    final int result1 = multiply(2, 3);
    final int result2 = multiply(4, 7);
    
    // then
    assertThat(result1).isEqualTo(6);
    assertThat(result2).isEqualTo(28);
}

✔︎ 명백하게 구현하기

명백하게 구현하는 방법은 가짜 구현이나 삼각측량법을 사용하지 않고 바로 정답을 구현하는 방법이다.
예를 들어서 지금까지 예시로 사용했던 곱하기와 같은 문제는 간단하므로 바로 구현해도 괜찮다.

public int multiply(final int num1, final int num2) {
    return num1 * num2;
}

가짜로 구현하기, 삼각측량법은 매우 작은 발걸음이다. 그러므로 무엇을 작성해야 할지 알고, 그걸 빠르게 할 수 있다면 구현하는 것이 좋다.

📌 Spring에서의 TDD(Test_Driven Development, 테스트 주도 개발) 프로그래밍 방법

  • Repository -> Service -> Controller 순서로 개발을 진행한다.
  • Repository 계층의 테스트는 H2와 같은 인메모리 데이터베이스 기반의 통합 테스트로 진행한다.
  • Service 계층의 테스트는 Mockito를 사용해 Repository 계층을 Mock하여 진행한다.
  • Controller 계층의 테스트는 SpringTest의 MockMvc를 사용해 진행한다.

Repository 계층은 다른 계층에 대한 의존성이 거의 없기 때문에 먼저 작성하기가 편리하다. Service 계층은 Repository 계층에 의존하고 Controller 계층은 Service 계층에 의존하기 때문에 Respository -> Service -> Controller 순으로 개발하는 것이 편하다.

물론 Controller 부터 개발하는 것이 편하다면 Controller 부터 개발해도 된다.

이론을 숙지했다고 해서 실제로 TDD를 적용하는 것은 어려울 것이다. 개발자는 코드를 보면서 이해하는 것이 가장 빠르기에 큰 도움이 됐던 글을 소개한다.
https://mangkyu.tistory.com/183


출처
[TDD] 단위 테스트와 TDD(테스트 주도 개발) 프로그래밍 방법 소개

profile
스프링 백엔드를 공부중인 대학생입니다!

0개의 댓글