티스토리 뷰

반응형

이번에는 여러 개발 서적들 및 실무 경험 그리고 시행 착오 등을 겪으면서 얻은 테스트 주도 개발 방법에 대해 소개해보고자 합니다.

이번 포스팅에서는 먼저 단위 테스트와 중요성 그리고 단위 테스트를 바탕으로 하는 TDD는 어떻게 프로그래밍하는 것인지에 대해 이론적으로 소개해보고자 합니다. 그리고 다음 포스팅들에서 실제로 테스트에 주도되는 개발을 해볼 것인데, 많은 분들에게 도움이 되기를 바라겠습니다:) 

 

 

 

1. 단위 테스트의 중요성과 좋은 단위 테스트의 특징


[ 단위 테스트(Unit Test)를 작성해야 하는 이유 ]

단위 테스트를 작성해야 하는 이유는 정말 너무 많다. 그 중에서 몇 가지 핵심적인 이유들을 작성하면 다음과 같다.

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

테스트 코드를 작성하면 우리가 개발한 코드들에 대해 수시로 빠르게 검증을 받을 수 있으며, 기능을 수정하거나 리팩토링을 할 때에도 검증을 받으므로 안정성을 확보할 수 있다는 장점이 있다.

하지만 그것보다 큰 장점으로 개발 및 테스팅에 대한 시간과 비용을 절감할 수 있다는 점에 주목해야 한다.

우리는 개발이 끝난 뒤에 버그가 있는지 확인하기 위해 애플리케이션 서버를 실행하고, (통합) 테스트를 진행해야 한다. 단위 테스트를 작성하지 않은 코드들은 테스트를 작성하지 않은 코드들 보다 버그가 잠재되어 있을 확률이 높은데, 문제는 서버를 실행하고 통합 테스트를 하는 비용이 너무 크다는 것이다. 그 이유는 통합 테스트를 위해서는 캐시, 데이터베이스 등 외부 컴포넌트들과 연결 등 부가적인 시간이 필요하기 때문이다.

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

 

 

[ 단위 테스트(Unit Test)를 작성해야 하는 이유 ]

그렇다고 테스트를 무작정 작성하는게 좋은 것은 아니다. 좋은 테스트를 작성해야 그 이점을 누릴 수 있는데, 좋은 테스트의 특징은 FIRST라는 5가지 규칙을 따라야 한다.

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

(위의 내용들은 CleanCode 책에서 참고한 내용들입니다.)

 

즉, 위의 내용을 압축하여 정리하면 좋은 테스트는 빠르게 독립적으로 어느 환경에서든 실행이 가능하고 검증할 수 있어야 한다는 것이다. 만약 테스트를 실행하는 비용이 크다면 그럴 바에는 통합테스트를 하는게 더 나을 수도 있다.

그리고 우리는 위의 5가지 규칙 중에서 Timely에 주목해야 할 필요가 있다. 테스트 코드를 작성하는 시점에 대한 이야기인데, CleanCode에서는 테스트 코드를 실제 코드를 구현하기 직전에 구현하라고 설명하고 있다. 즉, 테스트 코드를 먼저 작성하라는 것이다. 우리는 왜 테스트 코드를 먼저 작성해야 하고, 어떻게 테스트 코드를 먼저 작성할 수 있는지에 대해 알아야 한다.

(단위 테스트와 관련해서는 다른 포스팅에서 다루어보았습니다. 이와 관련해서 부족하신 분들은 이 글을 참고해주세요!)

 

 

 

 

2. TDD(테스트 주도 개발), 테스트 코드를 먼저 작성해야 하는 이유와 방법 및 순서


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

테스트 코드를 먼저 작성하는 개발 방법론은 테스트 주도 개발(Test-Driven Development, TDD)로 많이 불린다. 우리는 프로덕션 코드 보다 테스트 코드를 먼저 작성해야 하는데, 그 이유는 다음과 같다.

  • 깔끔한 코드를 작성할 수 있다.
  • 장기적으로 개발 비용을 절감할 수 있다.
  • 개발이 끝나면 테스트 코드를 작성하는 것은 매우 귀찮다. 실패 케이스면 더욱 그렇다.

테스트 주도 개발(TDD, Test-Driven Development)의 궁극적인 목표는 작동하는 깔끔한 코드를 작성하는 것이다. TDD의 개발 단계에는 리팩토링이 있는데, 이 리팩토링 과정을 거치면서 중복된 코드들은 제거되고, 복잡한 코드들은 깔끔하게 정리하게 된다.

또한 테스트를 처음 작성할 때에는 귀찮고 개발을 느리게 한다는 느낌을 받을 수 있지만, 장기적으로 보면 반드시 개발 비용을 아껴줄 것이다.

마지막으로는 정말 현실적인 이야기인데, 프로덕션 코드를 먼저 작성하였다면 이후에 테스트 코드를 작성하는 과정은 너무 귀찮다. 왜냐하면 테스트 코드는 성공 케이스 뿐만 아니라 실패 케이스까지 작성해야 하기 때문에 작성해야 할 테스트의 갯수는 해당 함수에서 발생가능한 모든 경우들인 N이며, 이미 개발이 완료되었기에 끝났다는 심리적 요인 때문에 테스트를 작성하는 것이 꺼려지기 때문이다. (물론 그렇지 않은 사람도 있을 수 있지만, 저의 경우는 매우 그렇습니다.) 그렇기 때문에 특별한 경우가 아니라면 테스트 코드를 먼저 작성하는 것이 좋다.

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

하지만 이러한 얘기들로는 정확히 이해하기가 힘드니 테스트 주도 개발, TDD의 프로그래밍은 어떻게 진행되는지 개발 순서에 대해 알아보도록 하자.

 

 

[ TDD(Test-Driven Development, 테스트 주도 개발) 프로그래밍 방법 및 순서 ]

TDD 개발 방법론의 프로그래밍 순서는 매우 단순하다.

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

 

위의 과정을 따라 개발을 진행하면 자연스럽게 프로덕션 코드보다 테스트 코드를 먼저 작성하게 될 것이다. 물론 이론적으로만 해당 내용을 이해하기는 어렵고, 나의 경우도 그러하였다. 또한 이를 Spring과 같은 프레임워크에 적용하는 것은 다른 영역이고, 이러한 이유로 ATDD(Application TDD)라고 불린다. 그러므로 Spring에서는 어떻게 TDD를 적용할 수 있는지 알아보도록 하자.

 

 

[ 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;
}


이렇게 변수를 사용하지 않고 상수를 반환하며, 답이 아닌 방법으로 가짜 구현하여 최대한 빨리 테스트를 통과하는 것이 가짜 구현 방법이다. 가짜 구현으로 연습을 많이 해두면, 복잡한 코드일 경우에 단계를 잘게 쪼개서 TDD로 문제없이 개발하는 능력을 갖출 수 있다. 가짜 구현으로 개발하면 다음과 같은 2가지 효과를 얻을 수 있다.

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

 

 

삼각 측량

삼각 측량은 테스트 주도로 추상화된 과정을 일반화하는 과정이다. 삼각 측량 방법은 테스트 예시가 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를 사용해 진행한다.

 

다른 예제나 강의들을 보면 Controller -> Service -> Repository 순서로 TDD 개발을 하는 경우도 많이 있다. 하지만 개인적인 경험을 바탕으로 Repository부터 계층을 작성해야 TDD의 flow가 매끄럽게 진행이 되는 것 같다. 왜냐하면 Repository 계층은 다른 계층이 필요 없기 때문에 먼저 작성하기가 편리하며, Service 계층은 Repository에 의존하기 때문에 Repository가 있으면 편리하며 흐름이 끊기지 않고, Controller 계층은 마찬가지로 Service계층에 의존하기 때문이다.

반대로 Controller부터 개발을 진행하면 Controller를 작성하다가 Service를 작성하고 그러다가 Repository 까지 내려가서 흐름이 끊길 수 있기 때문이다. (Controller부터 하는게 편하다면 Controller부터 개발해도 전혀 문제가 없다.)

 

 

하지만 이러한 내용을 숙지했다고 하더라도 실제로 TDD를 적용하는 것은 쉽지 않다. 그러므로 요구사항이 주어진 과제를 테스트에 의해 주도되는 방법으로 실제 개발을 진행해보고자 한다. 단순히 TDD로 개발을 하는 것 뿐만 아니라 실무에 적용할만한 알짜 지식들 또는 팁 등도 작성해보고자 한다.

많은 사람들에게 도움이 되기를 바라면서 주어진 과제를 분석하고, TDD 기반으로 개발해보도록 하자.

(만약 단위 테스트에 대한 개념이나 Java에서 단위 테스트의 작성 또는 Spring에서의 단위 테스트 작성 및 Mockito 사용법에 대한 공부가 부족하다면 여기를 참고해주세요!)

 

 

 

 

위에서 작성한 내용들은 클린코드나 이펙티브 자바, 테스트 주도 개발 By Example 등의 책과 실무 경험 등을 종합하여 개인적인 생각을 정리한 글입니다. 옳고 그른 얘기들 및 부족한 점 충분히 있을 수 있는데, 피드백이나 커멘트 등은 언제나 환영합니다:)

코드는 깃허브에 공개되어 있습니다! 코드를 확인하시려면 여기를 참고해주세요:)

 

 

관련 포스팅

  1. 단위 테스트와 TDD(테스트 주도 개발) 프로그래밍 방법 소개 - (1/5)
  2. TDD 연습문제 소개와 요구사항 분석 및 SpringBoot 프로젝트 설정 - (2/5)
  3. TDD로 멤버십 등록 API 구현 예제 - (3/5)
  4. TDD로 멤버십 전체/상세 조회 API 구현 예제 - (4/5)
  5. TDD로 멤버십 삭제 및 포인트 적립 API 구현 예제 - (5/5)

 

 

 

 

반응형
댓글
댓글쓰기 폼
  • 꽃니미 관련포스팅이 안나옵니다ㅠ 2021.08.17 15:29
  • 망나니개발자 안녕하세요 꽃니미님! 우선 어제 작성한 글에 빨리 찾아주셔서 감사합니다:)
    해당 내용들은 다 작성하였는데, 현재 티스토리에 맞춰서 포맷 수정하고 있습니다! 최대한 빨리 포스팅 마무리해서 글들 보이도록 하겠습니다!
    감사합니다:)
    2021.08.17 18:15 신고
  • 비밀댓글입니다 2021.08.18 19:25
  • 비밀댓글입니다 2021.08.18 19:31
  • 비밀댓글입니다 2021.08.18 19:33
  • 망나니개발자 아핫 그렇군요 감사합니닷:)
    내용 빨리 올리겠습니닷! 보시고 피드백이나 의견 주실 부분 많이 남겨주세용!
    2021.08.18 19:39 신고
  • 비밀댓글입니다 2021.08.18 19:40
  • 비밀댓글입니다 2021.08.18 19:55
  • 비밀댓글입니다 2021.08.18 20:03
  • 망나니개발자 크... 저는 취업준비할 때 보지도 않았던 책들을 보시고 너무 대단하시네요!
    맞습니다.. 아무래도 책을 읽고 내용을 적고 또 정리를 하고 시간이 적게 걸리는 일은 아닌 것 같아요ㅜㅜ 그래도 그 만큼 깊이 알게되는 또 다른 장점이 있는 것 같아요! 그래서 저도 꾸준히 포스팅하고 있습니닷ㅎㅎ
    꾸준히 알고리즘 준비하고, 이렇게 책들 읽어가면서 공부하시면 충분히 원하는 회사에 가실 수 있을 것 같아요!
    누구나 준비하고 성장하는 시간은 필요하니까요~ 너무 낙담하지 마세요ㅎㅎ 열심히 공부한게 당장의 취업은 아니여도 언젠가 분명 도움이 될껍니다:)
    2021.08.18 20:16 신고
  • 비밀댓글입니다 2021.08.18 20:21
  • 망나니개발자 앞으로도 꾸준히 열심히 하시고, 원하시는 좋은 회사에 입사하셔서 좋은 소식 들려주세욤!!
    기다리고 있겠습니닷:)
    2021.08.18 20:26 신고
  • 비밀댓글입니다 2021.09.05 19:00
  • 비밀댓글입니다 2021.09.05 21:15
반응형
공지사항
Total
1,765,578
Today
459
Yesterday
4,771
TAG more
«   2021/12   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
글 보관함