테스트란?
- TDD 관점에서 테스트는 요구사항에 대한 명세.
- 주어진 상황에서 객체(혹은 서버)에게 요청을 보냈을 때 기대하는 결과 혹은 과정에 대한 명세.
- 기대하는 결과를 명세: 특정 Input에 특정 Output 약속 ex) 평균값 메소드는 (1,2,5) → (3)를 테스트
- 기대하는 행동 명세: A메소드 호출은 B,C,D를 2번씩 호출하는 것을 약속
💡 테스트는 요구사항을 구현한 동작에 대한 검증 아닌가?
테스트를 작성하는 동기의 차이라고 생각합니다. 구현한 코드가 원하는 대로 동작하고 다른 코드의 변화에 의해 테스트가 작성된 코드가 영향을 받지 않는 것에 테스트의 목적을 둔다면, 테스트는 요구사항을 구현한 동작에 대한 검증으로 바라볼 수 있습니다.
반면, 요구사항을 구현하기 전, 사용자가 서비스에게 바라는 것이 무엇(Happy case)인지, 바라지 않는 것(Unhappy case, 흔히 예외로 인해 발생한다)은 무엇인지 미리 정하고 시작하는 것에 테스트의 주 목적을 둔 것이 TDD 관점에서의 테스트죠. TDD 관점에서 테스트를 작성하는 동기는 문제를 명확히 정의하는 것에 있습니다.
TDD는 무조건 좋은가?
요구 사항 명세의 목적인 TDD는 좋다고 생각합니다. 하지만 상황에 따라 TDD를 적용하는 방향성은 유연할 필요가 있습니다. 그렇지 않으면 TDD를 잘못 사용하게 되어 ‘TDD는 실무에 쓰기 적합하지 않다’라는 아쉬운 결론을 내릴 수 있기 때문이죠. 개발을 하다 보면 크게 두가지 상황이 발생합니다. <Outside→Inside> 방향으로 개발하는 경우와 <Inside→Outside> 방향으로 개발하는 경우죠. <Outside→Inside> 경우는 사용자와 가까운 레이어(컨트롤러, API 등) 부터 도메인 모델 순으로 TDD를 진행하는 경우고 <Inside→Outside>는 그 반대 경우입니다. 만약 독자께서 'TDD는 실무에 쓰기 적합하지 않다'라고 말한다면, 두 방식 중 한가지로만 TDD를 적용했을 가능성이 높습니다. 각 상황에서 TDD를 사용했을 때 특징과 장단점에 대해 알아보도록 하죠.
<Outside→Inside> 방식
- 상위 레벨 테스트 부터 작성하는 방식
- 테스트 더블을 사용하여 협력 객체(가짜 객체)의 예상 결과 정의(결과 상태 혹은 메소드 호출 여부 등)
- ✅ 테스트를 작성하며, 테스트 대상이 협력해야 하는 객체를 정의하게 됨.
- 다음 사이클로 테스트 더블로 미리 정의한 협력 객체의 테스트를 작성
- 장점
- TDD를 처음 접하는 사람이 접근하기 쉬운 방식
- 도메인에 대한 이해도가 높지 않아도 테스트 작성 가능
- 단점
- 협력 객체의 구체적인 동작 방식을 알아야 하기에, 협력 객체에 의존적인 테스트가 작성됨.
<Inside→Outside> 방식
- 도메인 모델 부터 상세히 설계
- 실제 객체(ex. 도메인 모델)를 사용하여 테스트 진행
- 의존하는 협력 객체가 실제로 존재해야 테스트 진행 가능
- 장점
- 협력 객체의 구체적인 동작 방식(인터페이스)을 알지 않아도 됨. 실체 객체이기 때문.
- 단점
- 설계를 먼저하기에 TDD 사이클을 지속하기가 어려움
*테스트 더블: 실제 객체를 대신해서 테스팅에서 사용하는 모든 기법
TDD 방향 <Outside→In> or <Inside→Out> 무엇이 좋은가?
두 방식 모두 장단점이 있기에 하나 만이 정답은 없다고 생각합니다. 경험적으로 비추어보았을 때는 즉 Top-Down 방향으로 요구 사항 명세를 시작(인수 테스트 작성)하여 전체적인 방향을 잡고, 그 다음은 도메인 레이어 부터 즉 Bottom-Up으로 TDD를 하는 것이 좋은 방법 중 하나였습니다.
장점
- 문제 정의를 명확히 할 수 있어 도메인에 빠져 필요하지 않은 기능을 만들 일을 줄었습니다.
- 컨트롤러와 서비스 테스트가 아닌 도메인 설계를 먼저 시작하기에 요구 사항 변화에 유연하게 대처할 수 있는 코드(도메인)을 작성할 수 있게됐습니다.
🥊 나만의 TDD 사이클 단계
전체 사이클
인수 테스트 -> (선택)서비스 테스트 -> 도메인 테스트
개별 사이클
1. 문제 정의 후, 요구 사항 분석
- 📋 문제 정의: 필요한 기능을 한 문장으로 요약하기
- 📋 요구 사항 분석: given/when/then 프레임 사용
2. 테스트 조건(해피 케이스) 주석으로 정의
- 예외 케이스 생각나면 그때 그때 정리
3. 테스트 작성
4. 테스트 통과하는 로직 작성
- (유연) TDD 사이클 대로라면 1~3 반복 후 최소 단위 모듈에서 4번을 시작해야함.
- ✅ 비즈니스적인 의미 & 테스트 유용성을 고려하여 특정 레이어의 모듈은 1~3 skip
- '내가 아는 것' -> '모르는 것' 방향으로 진행.
- ❗ 내가 아는 것: 객체의 책임과 역할 등
- ❓ 내가 모르는 것: 구현 방식 등
5. 리팩토링 진행
- 우선순위: (1)재사용성, (2)가독성
- 가독성
- 💥 기능을 '명세'하는 역할을 하는가
- 💥 읽는 사람을 고려하여 작성했는가(ex. 기획자, 개발자 등)
6. 예외 케이스 고려
- 1~5번 반복
- 🌟 예외 상황
- request 데이터 누락(인증 정보)
- 논리적 오류 check(비즈니스 규칙에 근거)
- 시스템 오류 check(서버 down, memory 이슈 등)
7. 성능 고려
- 1~6번 반복
- ⏰ 데이터를 효율적으로 읽는가
- ⏰ 데이터를 효율적으로 쓰는가
ps. '5. 시스템 오류 check'와 '7. 성능 고려'는 개발 경험이 쌓임에 따라 1번에서 진행되도 함.
결론
TDD는 문제를 풀기 전에 문제를 명확히 정의(요구 사항 명세)하는 것에 주 목적을 둔 개발론입니다. 팀의 입장에서 TDD를 통해 개발한다면, 요구 사항 명세를 하고 시작하기에 문서화를 따로 할 필요가 없어서 동료 개발자 혹은 기획자(인수 테스트의 경우)와 협업하기에 용이합니다. 개인의 입장에서 TDD를 통해 개발한다면, 문제 정의를 명확히하여 방향성을 잡고 설계를 시작할 수 있어서 전체적인 개발 리소스를 효율적으로 다룰 수 있습니다.
테스트는 상위 레이어에서 하위 레이어 방향으로 만 진행할 필요는 없습니다. 경험적으로 비추어 봤을 때 요구 사항 명세를 위해 최상위 레이어(인수 테스트) 작성을 먼저 한 후, 도메인 레이어를 상세하게 설계하며 TDD를 진행하는 것을 권장합니다.
'TIL > 개발 칼럼' 카테고리의 다른 글
#7 타행 이체 개선기, 비관락 획득 후 외부 API 호출 최소화 (0) | 2023.05.24 |
---|---|
Resume 링크 (0) | 2023.04.23 |
#6 타행 이체 기능 성능 개선기, 속도(gap lock으로 인한 insert 병목 해결) (1) | 2023.04.20 |
#5 타행 이체 기능 성능 개선기, 속도(이체 입금 요청 API 처리 전략) (2) | 2023.04.08 |
#4 타행 이체 기능 성능 개선기, 속도(인프라 개선) (0) | 2023.04.08 |