전체적인 테스트를 위한 개발을 단위별 서비스 유형별로 분화하여 개발하고 이에 맞게 전체적인 개발을 시행
TDD의 기본 원칙은 "실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다"라고 합니다.
이 원칙을 통해 개발된 코드는 모두 테스트가 검증된 상태라고 할 수 있습니다.
TDD가 사용되어야 하는 이유
코드의 양이 어마어마하다면, 어떻게 테스트를 해야할지 감도 안잡히고 결국에는
테스트를 안해버릴수도 있습니다.
TDD는 아예 테스트를 먼저 만들고 테스트를 성공하는 코드를 만들어 내기 때문에 테스트를 꼼꼼히 만들 수 있습니다.
코드를 만들기 전 머릿속에서만 복잡하게 생각했던 코드 설계를 테스트 코드를 통해 용이하게 설계할 수 있습니다.
객체 지향적인 코드 생산
TDD는 코드의 재사용 보장을 명시하므로 TDD를 통한 소프트웨어 개발 시 기능별 철저한 모듈화가 이뤄집니다.
이는 종속성과 의존성이 낮은 모듈로 조합된 소프트웨어 개발을 가능하게 하며 필요에 따라 모듈을 추가하거나 제거해도 소프트웨어 전체 구조에 영향을 미치치 않습니다.
추가 구현의 용의함
개발이 완료된 소프트웨어에 어떤 기능을 추가할 때 가장 우려되는 점은 해당 기능이 기존 코드에 어떤 영향을 미칠지 알지 못합니다.
하지만 TDD의 경우 자동화된 유닛 테스팅을 전제하므로 테스트 기간을 획기적으로 단축시킬 수 있다.
높은 코드 안정성
짧은 주기의 테스트 코드 개발-리팩토링 단계를 거치며 끊임 없이 보완하고 철저한 모듈화를 통해 종속성과 의존성이 낮은 모듈로 조합된 소프트웨어 개발을 가능하게 하여 코드의 안정성을 높일 수 있다.
재설계 시간의 단축
TDD는 설계 단계에서 테스트 코드를 먼저 작성하기 때문에 무엇을 해야하는 지 정의하고 생각할 수 있어 완성도 높은 설계로 이어집니다.이는 개발 진행 중 소프트웨어의 전반적인 설계가 변경되는 일을 방지할 수 있습니다.
디버깅 시간의 단축
TDD의 경우 자동화 된 유닛 테스팅을 전제하므로 특정 버그를 쉽게 찾아낼 수 있습니다.
기존 방식에 적응 되어 버려 개발 방식을 바꾸기 어려움
이미 기존 코드 중심 개발으로 오랜 시간 개발을 해온 개발자들은 기존과 다른 방식인 TDD로 개발 방식을 바꾸긴 어려울 것입니다.
하지만 오히려 개발을 별로 해보진 않은 신입 개발자들은 적용하기 쉬울 것 입니다.
생산성 저하
한 번 개발할 코드를 처음부터 2개의 코드를 짜야하고 중간중간 테스트를 하면서 고쳐나가야하기 때문에 일반적인 개발 방식보다 개발 시간이 늘어날 수 밖에 없습니다.
TDD 개발 방식은 일반적인 개발 방식에 비해 시간이 대략 10 ~ 30% 정도 늘어난다고 합니다.
SI 프로젝트에서는 소프트웨어 품질보다는 납기일 준수가 중요해 TDD 방식을 잘 사용하지 않습니다.
또 단순 개발이나 소규모 프로젝트의 경우 개발기간이 짧아 TDD를 적용하게 되었을 때 Ctrl + C, Ctrl + V를 통한 뻔하고 중복된 테스트 코드를 작성했을 때는 비효율적일 수 있습니다.
단위 테스트의 given/when/then 패턴: 단위 테스트를 3가지 단계로 나누어 처리하는 패턴
Mock : Unit Test를 할 모듈과 메시지를 주고받는 객체를 대신할 가짜 객체
Stub : 실제 코드나 아직 준비되지 못한 코드를 호출하여 수행할 때 호출된 요청에 대해 미리 준비해둔 결과를 제공하는 테스트 메커니즘
점진적 모듈 통합 방법 : 하향식 기법
하향식(top-down) 기법은 시스템을 구성하는 모듈의 계층 구조에서 맨 상위의 모듈부터 시작하여 점차 하위 모듈 방향으로 통합하는 방법
점진적 모듈 통합 방법 : 상향식 기법
상향식(bottom-up) 기법은 하향식 기법과는 반대로 가장 말단에 있는 최하위 모듈부터 테스트를 시작
코드 구현 -> 서버 실행 -> 수동 입력 -> 실행 -> 에러 -> 에러 분석(시간소요) -> 해결 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. 테스트 결과의 일관성
2. 동일한 결과를 보장하는 테스트
3. 포괄적인 테스트