Framework/Spring

[TIL][Test] TDD 파헤쳐보기 (개념, 장/단점, 흐름)

쉬지마 이굥진 2024. 4. 1. 23:20

테스트 코드를 본격적으로 쓰기 전에 테스트의 종류와 필요성에 대해 정리했던 시간을 가졌었다. 

 

2024.03.22 - [Framework/Spring] - [TIL][Spring] 단위 테스트/통합 테스트/인수 테스트, 테스트 코드의 필요성

 

[TIL][Spring] 단위 테스트/통합 테스트/인수 테스트, 테스트 코드의 필요성

테스트 코드를 프로젝트에 적용하기에 앞서, 테스트에 대해 공부하고 회고해보는 시간을 가지고 포스팅한다. 사실 여태 테스트 코드를 작성하지 않고 Postman 으로만 API 기능 테스트를 진행해왔

developer-jinnie.tistory.com

오늘 포스팅에선 TDD (테스트 주도 개발)가 뭔지 그 개념을 자세히 공부해보도록 하자!!

 

📌 TDD란?

(Test-Driven-Development); 테스트 주도 개발

전통적인 개발 방식과 다르게 실패하는 테스트 코드로 시작해서 리팩토링으로 종료되는 일련의 스프린트방식을 여러 번 거쳐서, 요구사항을 충족하는 코드를 점진적으로 완성 시켜가는 개발 방법론을 말한다.

 

FAIL, PASS, REFACTOR 이 세 단계를 요구사항 마다 거치면 된다. 

 

 

📌TDD 개발의 장점 

사실 위의 단계를 매번 요구사항마다 거쳐야 한다니 번거로울 것 같기도 하다. 테스트코드를 먼저 쓰고 개발을 진행하다보니 전체 개발 시간이 늘어날 것 같기도 하고. 이럼에도 테스트 주도 개발을 하시는 분들은 왜 이 방식을 선택하시는 걸까?

  1. 디버깅 시간 단축
    모듈 별로 테스트를 자동화 할 수 있는 코드가 없다면 오류가 발생하는 기능의 오류를 찾기 위해서 모든 레벨의 코드를 확인 해야한다. 하지만 TDD의 경우, 모든 모듈의 유닛 테스트 코드가 있기 때문에 손 쉽게 오류를 찾을 수 있다.
  2. 작성한 코드가 가지는 불안정성을 개선하여 생산성을 높일 수 있음
    TDD를 사용하면, 코드가 내 손을 떠나(배포 → 실행된 단계) 사용자에게 도달하기 전에 문제가 없는지 먼저 진단 받을 수 있다. 그렇기 때문에 코드가 지닌 불안정성과 불확실성을 지속적으로 해소해준다. 
  3. 재설계 시간 단축
    개발자는 요구사항을 제대로 이해하지 않고는 구현을 시작할 수 없다. 테스트 시나리오를 작성하려면 먼저 기능에 대한 포괄적인 이해가 필요하기 때문에 개발자가 지금 무엇을 해야하는지 분명히 이해하고 개발을 시작하게 된다. 
    또한 테스트 시나리오를 작성하면서 다양한 예외사항 에 대해서도 생각해 볼 수 있기 때문에 이는 완성도 높은 설계로 이어질 수 있다. 그렇게 되면 Clean 하게 코드를 짤 수 있다.
  4. 추가 구현이 용이
    비즈니스에서의 변화는 항상 있을 수 있다. 새롭게 추가되는 요구사항을 적용하기 위해서 기존 코드의 변경이 발생하고 이로 인해 이전에 진행했던 모든 테스트 케이스와 새롭게 추가된 테스트 케이스 모두를 확인해야 하는데, TDD의 경우 자동화된 유닛 테스팅을 전제하므로 테스트 기간을 획기적으로 단축 시킬 수 있다.
  5. 각 테스트가 요구사항을 직접 식별하므로, 코드와 요구사항이 긴밀하게 연결됨
    복잡한 기능 정의서와 같은 문서를 볼 필요 없이 (기능 정의서가 100% 최신화 돼 있을 거란 보장도 없다) 테스트 코드와 함께 있는 Test Case 를 통해서 메서드에 대한 기능 정의를 이해할 수 있다.

 

이러한 장점들이 있는 반면, 실패되는 테스트 코드를 1차원적으로 수정하면서 시작하기 때문에 시간이 많이 소요된다는 단점도 있다.

그렇기 때문에 무조건 ! TDD를 적용하는 것은 옳지 않을 것이다. 

 

 

📌TDD 흐름

출처 : https://www.icterra.com/tdd-is-not-about-testing-but-the-design/

TDD는 어떻게 진행될까?

포스팅 상단에서 언급했던 FAIL, PASS, REFACTOR 세 가지 단계에 대해 알아보자.

  1. [Fail] 작은 단위의 Test Case 요구사항을 만족하지만, 정상적으로 실행이 되지 않는 코드를 작성한다.
    - 여기서 실패란 컴파일 오류를 뜻한다. 
  2. [Pass] 정상 동작을 하면서 Test Case를 통과하는 테스트 코드로 수정 후, 테스트 통과 여부를 확인한다.
  3. [Refactor] 테스트를 통과한 뒤에는 개선할 코드가 있으면 리팩토링한다. 리팩토링을 수행한 뒤에는 다시 테스트를 실행해서 기존 기능이 망가지지 않았는지 확인한다.

이 3가지 과정을 반복하면서 점진적으로 기능을 완성해 나가는 것, 이것이 전형적인 TDD 의 흐름이다.

 

 

📌테스트 코드 작성 시 주의점

  1. 테스트 클래스의 이름 잘 (?) 짓기
    단순하게 클래스를 만들 수도 있겠지만, 테스트 코드가 API 정의서를 대체 할 수도 있다고 상단에 명시했다. 
    그러므로 테스트 클래스의 이름을 지을 땐 코드를 읽는 독자의 이해력을 돕기 위해서 올바른 의미를 전달할 수 있는 이름으로 네이밍하는 것이 더 좋다. 
  2. 첫 번째 Test Case를 선택할 때, 구현하기 가장 쉬운 상황이나 가장 예외적인 상황 선택하기
    TDD에서 첫 번째 테스트는 매우 중요하다. TDD는 점진적으로 기능을 확장해 가는 방식으로 개발을 하는데, 첫 번째 테스트부터 모든 기능을 구현 해야하는 Test Case를 선택한다면 이후 진행 과정이 순탄하게 흘러가지 않을 수 있기 때문이다.
    따라서 첫 번째 테스트 케이스를 선택할 땐 구현하기 쉬운 상황 (가장 빠르고 쉽게 개발할 수 있는 상황)이나 가장 예외적인 상황을 선택해서 하자.  
  3. 짧은 시간 (30분 이내) 동안 집중해서 개발 할 수 있는 범위까지만 개발하기
    TDD의 핵심은 "가장 쉽고 빠르게" 테스트 할 수 있는 항목을 먼저 선택하고 테스트를 통과하기 위해서 "가장 쉬운 방법"을 선택하는 것이다. 그리고 이러한 일련의 과정을 여러 번 반복해서 점진적으로 기능을 확장해서 모든 요구사항을 만족하는 메서드를 완성시키는 것이다. 

 

이렇게 TDD의 개념과 장/단점, 흐름에 대해서 알아봤다. 다음 포스팅에선 실무에서 쓰이는 단위 테스트 도구 JUnit5에 대해서 활용 방법과 개념을 작성해보겠다.