TDD의 창시자인 켄트 백 (Kent Beck)은 프로그램을 작성하기 전에 먼저 '테스트를 먼저 하라'고 했습니다. 즉, TDD(Test-Driven Development)란 테스트 코드를 먼저 만들고, 실제 프로덕션 코드를 나중에 만드는 개발 방법을 말합니다.
그림을 보시면, 기존 개발 프로세스가 설계 -> 개발(코드 작성) -> 테스트(코드 작성) 순서였다면 TDD는 두 번째 보이는 그림과 같이 개발(코드 작성)보다는 테스트 코드를 먼저 작성하는 개발 방법입니다.
이 그림은 Red, Green, Refactor라고 불리는 TDD 개발 사이클을 그림으로 나타낸 것인데요. 먼저 Red: 테스트를 실패하고 Green: 트세트를 성공할 수 있게 프로덕션 코드를 구현한 후 Refactor: 프로덕션 코드와 테스트 코드를 리팩토링 하는! 이렇게 TDD는 세 가지 사이클로 이루어져 있습니다.
그렇다면 캔트 백은 왜 TDD를 만들고 저희는 TDD를 사용할까요?
저희가 이제 블로그에서 일반적으로 생각하는 TDD의 장점을 가져왔습니다.
- 변화에 대한 두려움을 줄여준다.(리팩토링이 편하다)
- 디버깅 시간을 줄여준다.
- 동작하는 문서 역할을 한다.
사실 TDD를 함으로써 이러한 장점을 얻을 수 있는 것은 맞지만 제 생각은 조금 다릅니다. 위에 장점은 이제 테스트 코드 장점이고 진정한 TDD의 장점은 따로 있다고 생각하는데요.
먼저 첫 번째 TDD의 장점은 'TDD를 하면 자연스레 테스트 커버리지가 높아진다!'입니다. -> 테스트 커버리지란 시스템 및 소프트웨어에 대해 충분히 테스트가 되었는지를 나타내는 정도이다.
물론 테스트 커버리지가 높다고 해서 무조건 좋은 코드는 아닙니다.
TDD를 사용하면 당연히 테스트 코드를 먼저 작성하니까 자연스레 테스트 커버리지가 높아지겠죠.
누군가는 '나중에 테스트 코드를 작성해도 테스트 커버리지가 높아지는데 뭐가 달라?'라고 물어볼 수 있습니다. 제 경험에 의하면 테스트 코드를 작성하는 것을 잊어버리거나 뒤로 미루다 결국 하지 않았던 경험이 있습니다 이에 대하여 TDD는 이를 방지해 줍니다.
두 번째: 오버 엔지니어링 방지
-> 오버 엔지니어링(Overengineering)은 정교하거나 복잡한 방식으로 제품을 설계하거나 문제에 대한 설루션을 제공하는 행위로, 원래 설계와 동일한 효율성과 효과로 더 간단한 설루션이 존재함을 입증할 수 있다.
TDD를 하면 요구사항에 맞추어 개발 코드를 구현하니 정말 내가 필요한 만큼만 코딩을 할 수 있습니다. 미래의 필요할 것 같은 구현을 지레짐작하여 불필요한 코드를 작성하게 되면 이는 오버 엔지니어링이라고 할 수 있습니다. TDD는 이런 점을 방지해 줍니다.
세 번째 설계에 대한 피드백이 빠르다.
설계를 처음부터 잘 한다면 좋겠지만 잘못 설계한다면 언제 깨달을 수 있을까요?
변경이 일어나거나 사용하기 어려울 때 깨달을 수 있습니다.
이러한 점에서 TDD는 설계에 대한 피드백을 빠르게 해 줍니다.
예를 들어 테스트 코드를 작성하는데 복잡하고 잘못된 설계를 가지고 있으면 새로운 테스트 코드를 작성하기가 점점 더 어려워질 것입니다.
그러면 빠르게 설계를 바꾸면 되겠죠?
TDD가 설계 방법론이라고 소개하는 책도 있습니다. 아래에 이규원 님의 말씀을 가져왔는데요. 만약 TDD가 설계 방법론이라면 우리는 TDD를 통해 좋은 설계를 얻을 수 있어야 할 것입니다.
TDD는 설계 방법론이다?
- TDD는 높은 응집을 유도하지 않는다.
- TDD는 단일 책임 원칙과 인터페이스 분리 원칙 위배에 어떤 신호도 주지 않는다.
- TDD는 인터페이스 일관성을 도출하지 않는다.
- TDD의 리팩토링 단계는 좋은 구조를 안내하거나 좋은 구조를 갖도록 강제하지 않는다.
이는 'TDD가 설계 방법론이 아니다'라는 것을 증명해 주고 있습니다.
물론 TDD가 설계에 전혀 영향을 미치지 않는 것은 아닙니다.
TDD는 강한 결합과 의존성 역전 원칙 위배를 경고하고 불명확한 설계 지점을 발견해 주기도 합니다. 하지만 전적으로 TDD에만 설계를 의존하게 된다면 테스트하기만 좋은, 결국 안 좋은 설계가 만들어집니다.
TDD를 막상 시작하면 적용하면서 실패를 겪게 되는데요.
제가 소개할 것은 TDD의 선구자 중 한 분인 이규원 님의 2018년 okkycon 강연 중 일부를 가져왔습니다.
TDD를 실패하는 사람이 하는 테스트
- 코드가 이루고자 하는 가치나 기능을 테스트하기보다 그 기능을 어떻게 구현하고 있는지를 테스트한다.
- 결국 테스트 케이스들이 구현체와 결합도가 높아진다.
- 구현체들을 리팩토링하면 결합되어 있는 테스트 케이스들이 모두 깨져버린다
출처 : [OKKYCON: 2018] 이규원 - 당신들의 TDD가 실패하는 이유
말로 하면 이해하기 어려울 텐데요 그림으로 표현하자면
화살표를 보시면 이제 테스트들이 구현체 안에 있는 Implementation 내부에 존재하는 구현체들을 테스트하고 있는데
Implemetation을 리팩토링하게 되면
테스트들이 모두 깨지게 됩니다.
결국 저희는 구현체가 아닌 설계 즉, 인터페이스를 테스트해야 됩니다.
이렇게 인터페이스를 테스트하게 되면 내부에 구현되어 있는 구현체를 아무리 리팩토링하더라도 테스트 케이스는 인터페이스를 테스트하기 때문에 테스트 케이스는 깨지지 않습니다.
여기까지 TDD 설명을 마치고 다음은 단위 테스트에 대해 소개하겠습니다.
먼저 단위 테스트를 소개하기 전에 프로그램 전체 테스트를 먼저 살펴보겠습니다.
테스트는 크게 인수, 부합, 기능, 통합, 단위 테스트가 존재하는데 오늘 소개할 단위 테스트를 제외한 나머지 4개의 테스트는 제일 기본적인 단위 테스트로부터 점점 더 범위를 넓혀가며 테스트를 하게 됩니다.
이 중 저희가 볼 단위 테스트는 제일 작은 범위인 코드 단위를 테스트하는데요.
단위 테스트
- 가장 작은 단위의 테스트
- 일반적으로 메서드 레벨
- 검증이 필요한 코드에 대해 테스트 케이스를 작성하는 절차 또는 프로세스
- Unit Testing은 테스트 코드가 목적 코드의 안전성을 입증해 주기 때문에, 테스트 코드 그 자체만으로 주요한 가치가 있음
단위 테스트 목적
- 문제점 발견 -> 단위 테스트는 각 단위가 정확하게 동작하는지 검사하고 문제가 발생 시 어느 부분이 잘못되었는지 재빨리 확인할 수 있게 해줍니다. 따라서 프로그램 안전성이 높아집니다.
- 쉬운 변경 -> 앞의 TDD에서 살짝 말한 부분인데요. 어떻게 코드를 고치더라도 문제점을 금방 파악할 수 있고, 수정된 코드가 정확하게 동작하는지 쉽게 알 수 있게 되므로 변경을 자주 할 수 있습니다.
- 품질 향상 -> 하나의 단위 테스트가 너무 길어지거나 복잡해지는 것은 프로덕션 코드에서 잘못되었다고 생각할 수 있습니다. 하나의 테스트 메서드에서 너무 많은 기능을 수행하기 때문에 이는 리팩토링이 필요하다는 것을 바로 알 수 있습니다.
- 코드의 문서화 -> 일명 샘플 코드라고 하는데요. 예외 상황, 용도, 의존 관계를 한눈에 파악할 수 있습니다. 또한 단위 테스트는 배포되는 코드와 일치하므로 항상 최신 상태로 유지된다는 이점도 있습니다.
마지막으로 좋은 단위 테스트를 만들려면 어떻게 해야 하는지 FIRST 법칙을 보고 마치겠습니다.
F.I.R.S.T 법칙
F - Fast(빠르게)
-> 테스트는 빨라야 합니다. 테스트가 느리면 자주 돌릴 엄두를 못 내겠죠? 그렇게 되면 문제를 빨리 찾아내 고치지 못하고 결국 코드 품질이 망가지게 될 것입니다.
I - Independent(독립적으로)
-> 각 테스트는 서로 의존하면 안 됩니다. 테스트가 서로 의존하게 되면 하나가 실패할 때 도미노처럼 나머지도 쭉 실패할 것입니다. 이렇게 되면 원인을 진단하기 어려워지며 후반 테스트가 찾아낼 결함이 숨겨지겠죠?
R - Repeatable(반복 가능하게)
-> 테스트는 어떤 환경에서도 반복 가능해야 합니다. 실제 환경, QA 환경, 심지어 버스를 타고 집에 가는 길에 즉 네트워크가 연결되어 있지 않은 노트북 환경에서도 실행할 수 있어야 하는데요. 테스트가 돌아가지 않는 환경이 하나라도 있다면 테스트가 실패하는 이유를 둘러댈 변명이 생기고 게다가 환경이 지원되지 않기에 테스트를 수행하지 못하는 상황에 직면합니다.
S - Self-Validating(자가 검증하는)
-> 테스트는 Boolean 값으로 결과를 내야 하는데요. 테스트는 성공 아니면 실패를 반환해야 합니다. 테스트가 이제 스스로 성공과 실패를 가늠하지 않는다면 판단은 보는 사람이 주관적으로 판단하게 되고 그러면 지루한 수작업 평가가 필요하게 되겠죠?
T - Timely(적시에)
-> 테스트는 항상 적시에 작성해야 합니다. 단위 테스트는 테스트를 하려는 실제 코드를 구현하기 직전에 구현해야 합니다. 만약 실제 코드를 구현한 다음 테스트 코드를 구현하게 된다면 실제 코드가 테스트하기 어렵다는 사실을 뒤늦게 발현할지도 모릅니다. 그렇게 되면 저희가 구현한 코드는 다시 짜야겠죠?
참고자료 및 출처
https://m.blog.naver.com/suresofttech/221569611618 - TDD 17H
https://dublin-java.tistory.com/54 - TDD 장점은 무엇일까?
https://soulpark.wordpress.com/2012/09/12/test-driven-development/ - TDD란?
https://www.youtube.com/watch?v=UttzAcbuk5k - [OKKYCON: 2018] 이규원 . 당신들의 TDD가 실패하 는 이유
https://sabarada.tistory.com/68 - 단위 테스트를 하고 계신가요?
https://www.youtube.com/watch?v=gX9PkEibWsM - 이규원이 말하는 현실 세상의 TDD : 안정감을 주는 코드 작성 방법
https://gyuwon.github.io/blog/2019/03/03/tdd-is-not-a-design-methodology.html - 프로그래머 이규원 의 웹사이트
https://web-km.tistory.com/38 - 2018. Okkycon 82/클린코드- 로버트 C. 마틴의 책
https://www.youtube.com/watch?v=3LMmPXoGI9Q - [10분 테코톡] 😼 피카의 TDD와 단위테스트