TDD 개념과 전략 및 과정

jinhan han·2023년 12월 25일
0

TDD(Test Driven Development)의 정의와 필요성

정의 : 테스트가 코드 개발을 주도하는 작성 방법론

목적 :

전체적인 테스트를 위한 개발을 단위별 서비스 유형별로 분화하여 개발하고 이에 맞게 전체적인 개발을 시행

TDD특성 :  

  • 반복 테스트를 주기적으로 할 수 있는 구조로 개발을 이루어 가는 패턴
  • 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가 가능
  • 코드에 대한 피드백+오류+ 빠르게 받는거 가능
  • 짧은 개발 주기의 반복에 의존하는 개발 프로세스이며 이는 분리 개발을 하는 트렌드에 알맞는 개발 방법론

TDD의 기본 원칙은 "실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다"라고 합니다.
이 원칙을 통해 개발된 코드는 모두 테스트가 검증된 상태라고 할 수 있습니다.

TDD가 사용되어야 하는 이유
코드의 양이 어마어마하다면, 어떻게 테스트를 해야할지 감도 안잡히고 결국에는
테스트를 안해버릴수도 있습니다.

TDD는 아예 테스트를 먼저 만들고 테스트를 성공하는 코드를 만들어 내기 때문에 테스트를 꼼꼼히 만들 수 있습니다.

코드를 만들기 전 머릿속에서만 복잡하게 생각했던 코드 설계를 테스트 코드를 통해 용이하게 설계할 수 있습니다.

장점

객체 지향적인 코드 생산
TDD는 코드의 재사용 보장을 명시하므로 TDD를 통한 소프트웨어 개발 시 기능별 철저한 모듈화가 이뤄집니다.
이는 종속성과 의존성이 낮은 모듈로 조합된 소프트웨어 개발을 가능하게 하며 필요에 따라 모듈을 추가하거나 제거해도 소프트웨어 전체 구조에 영향을 미치치 않습니다.

추가 구현의 용의함
개발이 완료된 소프트웨어에 어떤 기능을 추가할 때 가장 우려되는 점은 해당 기능이 기존 코드에 어떤 영향을 미칠지 알지 못합니다.
하지만 TDD의 경우 자동화된 유닛 테스팅을 전제하므로 테스트 기간을 획기적으로 단축시킬 수 있다.

높은 코드 안정성
짧은 주기의 테스트 코드 개발-리팩토링 단계를 거치며 끊임 없이 보완하고 철저한 모듈화를 통해 종속성과 의존성이 낮은 모듈로 조합된 소프트웨어 개발을 가능하게 하여 코드의 안정성을 높일 수 있다.

재설계 시간의 단축
TDD는 설계 단계에서 테스트 코드를 먼저 작성하기 때문에 무엇을 해야하는 지 정의하고 생각할 수 있어 완성도 높은 설계로 이어집니다.이는 개발 진행 중 소프트웨어의 전반적인 설계가 변경되는 일을 방지할 수 있습니다.

디버깅 시간의 단축
TDD의 경우 자동화 된 유닛 테스팅을 전제하므로 특정 버그를 쉽게 찾아낼 수 있습니다.

단점

기존 방식에 적응 되어 버려 개발 방식을 바꾸기 어려움
이미 기존 코드 중심 개발으로 오랜 시간 개발을 해온 개발자들은 기존과 다른 방식인 TDD로 개발 방식을 바꾸긴 어려울 것입니다.
하지만 오히려 개발을 별로 해보진 않은 신입 개발자들은 적용하기 쉬울 것 입니다.

생산성 저하
한 번 개발할 코드를 처음부터 2개의 코드를 짜야하고 중간중간 테스트를 하면서 고쳐나가야하기 때문에 일반적인 개발 방식보다 개발 시간이 늘어날 수 밖에 없습니다.
TDD 개발 방식은 일반적인 개발 방식에 비해 시간이 대략 10 ~ 30% 정도 늘어난다고 합니다.
SI 프로젝트에서는 소프트웨어 품질보다는 납기일 준수가 중요해 TDD 방식을 잘 사용하지 않습니다.

또 단순 개발이나 소규모 프로젝트의 경우 개발기간이 짧아 TDD를 적용하게 되었을 때 Ctrl + C, Ctrl + V를 통한 뻔하고 중복된 테스트 코드를 작성했을 때는 비효율적일 수 있습니다.

구성 :

[단위 테스트(Unit test) : 독립적으로 진행되는 가장 작은 단위의 테스트 (하나의 기능 또는 메소드)]

  • 부분만 독립적으로 테스트하기 때문에 어떤 코드를 리팩토링하여도 빠르게 문제 여부를 확인 가능
  • 테스팅에 대한 시간과 비용이 절감 가능
  • 새로운 기능 추가 시에 수시로 빠르게 테스트 가능
  • 리팩토링 시에 안정성을 확보 가능
  • 코드에 대한 문서화 가능
  • 테스트 코드를 수시로 빠르게 돌리면서 문제 파악 가능

단위 테스트의 given/when/then 패턴: 단위 테스트를 3가지 단계로 나누어 처리하는 패턴

  • given(준비): 어떠한 데이터가 준비되었을 때
  • when(실행): 어떠한 함수를 실행하면
  • then(검증): 어떠한 결과가 나와야 한다.
  • verify : 메서드가 호출된 횟수, 타임아웃 시간 체크를 검사할 때 사용 (부가적)

Mock : Unit Test를 할 모듈과 메시지를 주고받는 객체를 대신할 가짜 객체
Stub : 실제 코드나 아직 준비되지 못한 코드를 호출하여 수행할 때 호출된 요청에 대해 미리 준비해둔 결과를 제공하는 테스트 메커니즘

[통합 테스트(Integration Test) : 모듈을 통합하는 과정에서 모듈 간의 호환성을 확인하기 위해 수행되는 테스트]

  • 컴포넌트 상호작용: 예상대로 통신하고 데이터를 공유하는지 확인
  • API 및 서비스 유효성 검사: 외부 API 혹은 타사 컴포넌트가 애플리케이션 컨텍스트 내부에서 올바르게 상호작용하는지 확인
  • 데이터 일관성: 여러 컴포넌트, 외부 서비스가 시스템 전체에서 데이터를 일관적으로 처리하는지 확인
  • 기능 흐름: 전반적인 기능 흐름이 End-To-End 에서 의도한대로 작동하는지 확인

점진적 모듈 통합 방법 : 하향식 기법
하향식(top-down) 기법은 시스템을 구성하는 모듈의 계층 구조에서 맨 상위의 모듈부터 시작하여 점차 하위 모듈 방향으로 통합하는 방법

점진적 모듈 통합 방법 : 상향식 기법
상향식(bottom-up) 기법은 하향식 기법과는 반대로 가장 말단에 있는 최하위 모듈부터 테스트를 시작

TDD 과정 :

코드 구현 -> 서버 실행 -> 수동 입력 -> 실행 -> 에러 -> 에러 분석(시간소요) -> 해결 or 버그(처음으로)

Test code -> Repository -> Service -> Controller 순서로 개발을 진행한다.
Repository 계층의 테스트는 H2와 같은 인메모리 데이터베이스 기반의 통합 테스트로 진행한다.
Controller 계층의 테스트는 SpringTest의 MockMvc를 사용해 진행한다.
다른 객체 대신에 가짜 객체(Mock Object)를 주입하여 어떤 결과를 반환하라고 정해진 답변을 준비한다.

좋은 테스트의 특징 :

FIRST
Fast: 목표한 기능을 빠르게 검증할 수 있어야 합니다.
Independent: 테스트의 단위는 다른 테스트와 독립적이어야 합니다. 순수하게 목표한 기능만 테스트합니다.
Repeatable: 반복적으로 테스트를 수행해도 동일한 결과를 얻을 수 있어야 합니다.
Self-validating: 스스로 검증할 수 있어야 합니다.
Timely: 실시간으로 검증할 수 있어야 합니다.

Right-BICEP
Right: 결과가 올바른지 판단할 수 있어야 합니다.
Boundary: 모든 경계(Boundary) 조건이 일치해야 합니다.
Inverse: 역(Inverse) 관계를 확인할 수 있어야 합니다.
Cross-check: 다른 수단을 사용하여 결과를 교차 확인(Cross-check) 할 수 있어야 합니다.
Error condition: 에러 조건(Error condition)을 강제로 만들 수 있어야 합니다.
Performance: 성능(Performance) 특성이 한도 내에 있어야 합니다.

  • 실제 코드가 변경되면 테스트 코드 역시 변경이 필요할 수 있는데, 이러한 이유로 우리는 테스트 코드 역시 가독성있게 작성할 필요가 있다.

    	1개의 테스트 함수에 대해 assert를 최소화
    	1개의 테스트 함수는 1가지 개념 만을 테스트

좋은 테스트 코드를 작성하기 위한 방법
1. 테스트 결과의 일관성

  • DB에 데이터를 INSERT하는 코드를 생각해 봅시다. 테스트를 수행하고 나서는 DB의 테이블 데이터를 모두 삭제시켜줘야합니다.
  • 만일 테스트 시, 중복된 정보가 INSERT되게 된다면 예외가 발생할 것입니다. 즉, 성공해야 마땅한 테스트가 실패할 수도 있게 됩니다.
    따라서, 테스트의 결과는 항상 동일하도록 구성해야합니다.

2. 동일한 결과를 보장하는 테스트

  • 단위 테스트는 코드가 바뀌지 않는다면 매번 실행할 때마다 동일한 테스트 결과를 얻을 수 있어야합니다.
  • DB의 데이터를 테스트하기 위해서는 전에 어떤 테스트 작업이 이루어졌는지 확실히 알지 못하므로 테스트 수행이 끝난 뒤 DB의 데이터를 지우는 것보다는
    테스트를 시작하기 전에 테스트 실행에 문제가 되지않는 상태를 만들어주는 것이 더 좋은 방법이라고 할 수 있습니다.
  • 1번과 2번을 통하여 수행되고 있는 테스트는 외부 환경에 영향을 받지 말아야한다는 것을 알 수 있습니다.

3. 포괄적인 테스트

  • 테스트를 안만드는 것도 문제이지만, 성의 없이 테스트를 만드는 바람에 문제있는 코드를 테스트가 성공하도록 만드는 것은 더욱더 문제가 됩니다.
  • 한 가지 결과만 검증한다거나, 오류가 나는 데이터의 테스트를 하지 않는 것은 위험할 수 있습니다.
profile
개발자+분석가+BusinessStrategist

0개의 댓글