테스트의 가치
테스트는 실수를 바로 잡아 준다.
- 대부분의 실수는 단위 테스트만으로도 쉽게 잡을 수 있다.
테스트는 실사용에 적합한 설계를 끌어내준다.
- 실패하는 테스트를 작성하고, 그 테스트가 성공할 수 있을 만큼의 코드 작성
- 코드의 설계와 기능을 사용자의 시각에서 바라볼 수 있게 끔 해줌
테스트는 원하는 동작을 명확히 알려주어 군더더기를 없애준다.
- 실패하는 테스트를 거쳐간 코드는 필요한 기능을 모두 담고 있으면서도, 이전 방식으로 작성한 코드보다 더욱 간결해짐
테스트를 작성해서 얻게 되는 가장 큰 수확은 테스트 자체가 아니다. 작성 과정에서 얻는 깨달음이다.
- 신중히 고민해서 만든 100개의 테스트로는 개선 효과가 크게 느껴지겠지만, 이미 3,000개나 갖춰진 상태에서 100개를 추가한다고 해도 변화를 체감하기 어려움
- 테스트가 단순한 검증 수단 이상임을 깨닫고, 사고의 전환을 통해야만 발견할 수 있는 설계 고지를 점령해야 함
생산성에 영향을 주는 요소
테스트 코드는 보통 제품 코드보다 간결하다. (조건문이나 반복문을 사용하는 테스트는 많지 않다.)
그렇다고 테스트 코드를 성의없이 작성(중복도 많고, 쓸데없이 복잡한 테스트)하면 생산성을 저하시킨다. (엉터리로 작성한 코드는 가독성, 안전성, 신뢰성, 실행 속도에 악영향을 줌)
생산성에 직접적인 영향을 주는 피드백 주기와 디버깅 시간이 가장 유력한 야근의 요정이다. (생산성 저하의 끝판왕은 회의라는 이야기도 있다.)
야근의 요정
우리가 바라는 야근의 요정
우리가 마주하는 야근의 요정
버그 수정 비용의 증가
프로그래머가 버그를 만들자마자 즉시 수정한다면 $5를 쓴 것
같은 결함을 프로젝트 전체 빌드 때 발견하면 비용은 $50가 됨
만약 통합 테스트까지 살아남으면 $500로 증가
시스템 테스트에 이르면 $5,000까지 치솟음
- 테스트 실행 속도는 변경사항을 검증하고 확인하기 위해 기다리는 시간
- 가독성이 떨어지면 자연스럽게 분석이 더뎌지고 디버거를 사용해야 할 상황까지 만들 수 있음.
- 테스트 결과의 정확성: 실수로 결함을 만들어도 테스트 스위트가 찾아줄거라고 믿어야 함.
- 신뢰성과 안전성이 연관되어 있음.
- 테스트가 약속한 것을 확실히 잡아내고 몇 번을 수행해도 항시 같은 결과가 나오도록 만들어야 함.
설계 잠재력 곡선
아래와 같은 상황을 가정하자.
- 제품 코드의 가장 대표적인 시나리오에 대한 테스트 코드 완료
- 구조상 가장 치명적인 부분에 대한 테스트 코드 완료
- 리팩토링으로 중복을 제거하고 가독성을 높인 상태
이 상태에서 마지막으로 남은 getter/setter 등의 사소한 테스트는 별로 큰 가치가 없다. 단지 테스트 작성만으로 얻을 수 있는 가치의 한계점에 도달한 상태이다.
이 상태에서는 실수가 반복되는 것을 예방하는, 방어적이고 검증 지향적인 가치가 아닌 더 창조적이고 설계 지향적인 가치를 찾아내야 한다.
테스트로 이룰 수 있는 모든 고지를 점령하려면 다음과 같이 해야 한다.
- 테스트 코드도 제품 코드를 다루듯 하라. 믿고 의지할 수 있을 만큼 철저하게 리팩토링하고 높은 품질을 유지하라
- 테스트를 제품 코드가 목적과 쓰임새에 적합한 구조가 되게끔 이끌어주는 설계 수단으로 활용하라.
프로그래머 대부분이 잘 지키지 못하는 쪽은 첫번째다. (이는 이 책이 커버할 내용)
설계 수단으로써의 테스트 쪽은 다음 책들을 참고.
- [Test Driven] (매닝, 2007)
- [Growing Object-Oriented Software, Guided by Tests] (Addison-Wesley, 2009)
설계 수단으로써의 테스트
전통적으로 테스트는 크게 두 가지 목적을 위한 품질 검증 수단이었다.
- 코드를 작성하는 즉시 정확하게 구현했는지 검사하는 것
- 코드베이스가 커져도 계속 잘 동작하는 지 지속해서 확인하는 것
테스트를 검증 수단으로 활용한다면, 설계 -> 설계대로 코딩 -> 생각대로 잘 구현되었는지 테스트의 순서를 거칠 것이다.
하지만 테스트를 설계 수단으로 이용한다면 설계 -> 코딩 -> 테스트 순서가 아닌 테스트 -> 코딩 -> 설계로 바뀐다. 마지막 설계 단계는 사실 리팩토링 단계로 더 유명하다.
이것이 바로 테스트 선행 프로그래밍 혹은 테스트 주도 개발이라 불리는 개발 방법론이다.
테스트 주도 개발 (Test Driven Development)
테스트 주도 개발(줄여서 TDD)은 간단한 아이디어에서 시작된 프로그래밍 훈련법이다.
실패하는 테스트 없이는 코드를 작성하지 않는다.
테스트를 먼저 작성하면 테스트로 설계한 제품 코드가 만들어지며 다음과 같은 긍정적인 효과를 불러온다.
- 사용 가능한 코드가 만들어진다. 즉 제품 코드의 설계와 API가 활용 시나리오에 적합한 모습으로 거듭난다.
- 코드가 가벼워진다. 실제로 활용할 시나리오에서 요구하는 기능만을 담게 된다.
첫째로, 다른 모든 컴포넌트가 갖춰졌는지와 상관없이 여러분의 손에는 항시 명확한 사용 시나리오가 딸린 설계만이 쥐어져 있게 된다. 더구나, 그 시나리오는 자동화된 단위 테스트라는 구체적이고 실행 가능한 형태로 구현되어 있다.
둘째, 테스트를 통과할 만큼만 코딩한다는 규칙을 잘 따라주면 설계를 간결하면서도 목적에 딱 맞도록 유지할 수 있다. 시나리오를 갖추지 못한 코드가 단 한줄도 없으므로 버릴게 하나도 없다. (우발적 복잡성은 코드 품질을 떨어뜨리는 가장 악명 높은 적이자 개발자 생산성을 갉아 먹는 주 요인이다. 우발적 복잡성이란 쓸데 없이 복잡한 것을 말한다. 요구 조건을 그대로 만족하면서도 더 단순한 설계로 대체할 수 있다는 뜻이다.)
결함이나 빠진 기능을 명시하는 테스트, 테스트를 통과할 만큼의 코드만 작성한다는 규칙, 그리고 간결한 설계를 지향하는 철저한 리팩토링은 우발적 복잡성을 크게 줄여준다. (물론 만병 통치약이 될 수는 없다. 최종 결과는 결국 프로그래머의 설계 감각과 경험에 크게 좌우되기 때문이다.)
참고 서적:
[Test Driven Development: By Example] (Kent Beck, Addison-Wesley, 2002)
번역서 [테스트 주도 개발] (김창준/강규영 역, 인사이트, 2005)
행위 주도 개발 (Behavior Driven Development)
TDD를 하지 않는 이유 중 가장 흔한 것이 “테스팅할 시간이 없어요”와 “테스트하기에 너무 복잡하고 힘든 코드”라는 것이다. 테스트 우선 프로그래밍의 또 다른 장애물은 테스트 우선이라는 개념 그 자체다. 우리 대부분은 테스팅을 감촉적(tactile) 행위, 추상적이기보다는 구체적인 행위로 본다. 우리는 경험적으로 존재하지 않는 어떤 것을 테스트하는 것이 가능하지 않다고 생각한다. 이 개념적인 프레임이 머릿속에 이미 자리잡은 일부 개발자들에게는 ‘테스트 우선(testing first)’이라는 아이디어가 바보 같은 일로 여겨진다.
그러나 테스트를 작성하고 어떻게 테스트할 것인가 하는 관점이 아닌, 행위(behavior)에 대해 생각해 본다면 어떨까? 여기서 말하는 행위란 애플리케이션이 어떻게 동작’해야’ 하는가, 즉 본질적으로 그것의 명세를 말하는 것이다.