소프트웨어 개발은 학습의 과정이다
- 흥미로운 프로젝트(== 가장 큰 이익을 줄 만한 프로젝트)에는
예상치 못한 요소
가 상당히 많다 - 갖가지 중요한 구성 요소가 조합된 시스템은
너무나 복잡
해서, 개인이 해당 시스템의 모든 가능성을이해하기는 어렵다
- 프로젝트에 관련된 모두는 프로젝트가 진행되면서
배우는 것
이 있어야 한다- 프로젝트에서 무엇을
달성해야 하는지
? 잘못 이해
하고 있는 바를 식별해결
하고자협업
해야 한다
- 프로젝트에서 무엇을
피드백은 가장 기본적인 도구다
팀애서 취할 수 있는 가장 좋은 접근법은 경험에 의거한 피드백을 이용해 시스템과 그 용도에 관해 배운 다음, 이렇게 배운 바를 다시 시스템에 적용하는 것이다
그러므로 이러한 피드백을 얻을 수 있는, 반복적인 활동 주기가 필요하다
- 저자는 모든 개발에
초에서 월 단위에 이르는 중첩된 고리형 시스템
의 피드백 주기를 적용했다- 짝 프로그래밍, 단위 테스트, 인수테스트, 일별 회의, 반복 주기, 출시 등을 사용하여 고리를 구성한다
- 하나의 고리마다 산출물이 나오고, 이 산출물이 피드백으로 드러나게 된다
- 팀에서는 빠르게 오해를 발견하고 수정할 수 있다
- 이렇듯, 중첩된 피드백 고리는 서로를 강화한다
안쪽 고리는 기술적 세부 사항
에 좀 더 집중한다- 단위 코드의 역할과 시스템 나머지 부분과의 통합 여부
바깥쪽 고리는 조직과 팀
에 좀 더 집중한다- 사용자의 요구를 충족하는지, 팀이 효과적으로 운영되고 있는지
- 프로젝트의 어떠한 측면에 대해서도 피드백을 일찍 받을수록 좋다
TDD가 왜 중요할까?
시스템 규모를 믿을 수 있는 방식으로 키우고, 늘 일어나는 예상치 못한 변화에 대처하고 싶다면 두가지 기술적인 토대가 필요하다
- 테스트 자동화
- 시스템 규모와 상관없이 수동테스트를 자주 하는것은 비실용적이다
- 회기 오류를 잡아줄 꾸준한 테스트가 필요하다
- 기존 기능을 망가뜨리지 않고 새 기능을 추가할 수 있다
- 꾸준한 리팩토링
- 개발자들은 코드를 작성하는 것보다 코드를 읽는데 훨씬 더 시간을 많이 보낸다
- 그러므로 코드를 가능한 한 단순하게 유지해야 한다
- 단순함에는 노력이 많이 들어간다
- 설계 개선하고 단순화하며, 중복을 제거하며, 코드가 명확하게 자신의 역할을 표현하게끔 코드를 사용할때마다 꾸준히 리팩토링 해야한다
하지만 여기서 문제는, 대부분의 개발자들이 테스트 작성을 업무
로 보지 않으며, 따분하다고 여기기 까지 한다는 것이다.
테스트 주도 개발은 이러한 상황을 근본적으로 뒤집는다.
작업을 완료한 후 작업 결과를 검증하려고 테스트를 작성하는 것이 아니라(Test Last),
코드를 작성하기 전에 테스트를 먼저 작성한다.
즉, 테스트 작성 자체가 설계 활동이 되는 것이다.
- 테스트를 먼저 작성함으로써 코드에서 하고 싶은바에 대한 생각을 명확하게 하고,
- 테스트를 먼저 작성하려는 노력으로 설계 아이디어의 품질에 대한 피드백도 빠르게 얻을 수 있게된다
- 코드를 테스트하기 쉽게 만들면 좀 더 깔끔하고 모듈화된 코드가 만들어지기 때문이다
TDD 간단 정리
TDD 작성 순서는 아래와 같다
다들 잘 아는 Red-Green-Refactor
cycle이다.
- 테스트를 작성한다
- 프로덕션 코드가 없으므로 당연히 실패한다(Red)
- 해당 테스트가 동작하게(Green) 만든다
- 테스트는 건드리지 않고 프로덕션 코드를 리팩토링 한다
- 이를 반복한다
테스트를 먼저 작성함으로써 얻는 이점은 아래와 같다
- 다음 작업에 대한 인수 조건이 명확해진다
- 작업이 끝나는 시점을 스스로 알아내야 하기 때문이다
- 느슨하게 결합된 구성 요소를 작성할 수 있게 된다
- 격리된 상태에서, 더 높은 수준으로, 모두 결합된 상태로 구성 요소를 손쉽게 테스트 할 수 있다
- 코드가 하는 일에 대한 설명이 더해진다
- 테스트 코드는 일종의 스펙 문서 역할도 한다
- 완전한 회귀 스위트가 늘어난다
사실상 2,3,4 번은 Test First Development 보단 단순히 테스트 작성에서 얻을 수 있는 이점들이다.
하지만 언급했다시피, 테스트를 나중에 작성하는 방식은 힘들고, 현실적으로 위의 이점들을 다 가져가지 못할 가능성이 크다.
그리고 테스트를 실행하면 얻는 이점은 아래와 같다
- 컨텍스트를 선명하게 인지하는 동안 오류를 탐지한다(?)
- 언제 작업이 충분히 완료되었는지 알게된다
- ‘금도긋’ 하듯 과도한 최적화를 하거나 불필요한 기능을 더하지 않게 된다
TDD의 황금률(?)
실패하는 테스트 없이는 새 기능을 작성하지 말라
좀 더 큰 그림
어플리케이션 내에 있는 클래스들을 대상으로 단위 테스트를 작성하는 것으로 TDD를 시작해보고 싶을 수 있으나, 결과적으로 단위 테스트만 있는 프로젝트는 TDD 프로세스가 주는 아주 중요한 혜텍을 놓치는 셈이 된다.
좀 더 큰 그림을 봐야한다.
(물론 단위테스트가 없는 프로젝트 보다는 백배 천배 낫다)
기존의 TDD 라이프 사이클에서 앞단에 인수 테스트(Acceptance test) 작성
이라는 부분이 추가 되었다.
(인수테스트-단위테스트-통합테스트-전-구간-테스트)
여기서는 전 구간 테스트를 사용하여 인수 테스트를 수행한다
그리고 TDD의 황금률에, 이 인수 테스트까지 같이해서 코드 작성을 시작한다.
- 실패하는 인수 테스트를 작성한다
- 이 테스트가 실패한다는 말은 아직까지 해당 기능을 구현하지 않았다는 것을 보여준다
- 단위 테스트들을 작성해나가며 기능을 완성해나간다
- 전 구간 테스트이기 때문에, 많은 단위 테스트들을 포함한다
- TDD cycle을 사용하여 단위 기능들을 개발해나간다
- 인수테스트가 통과하면, 작업이 끝난다
- 이때서야 시스템이 배포될 수 있다
외부 품질과 내부 품질
- 외부 품질
- 시스템이 고객과 사용자의 요구를 얼마나 잘 충족하는가(기능, 신뢰성, 가용성, 응답성 등)이다
- 보통 계약의 일부라, 이를 이해하지 못할 사람은 없다
- 전 구간 테스트를 사용한다
- 전 구간 테스트로는 코드를 얼마나 잘 작성했는지는 알 수 없다
- 내부 품질
- 시스템이 개발자와 관리자의 요구를 얼마나 잘 충족하는가(이해하기 쉬운가, 변경하기 쉬운가) 이다
- 외부 품질과 똑같이 중요하지만, 달성하기는 많이 어렵다
- 단위 테스트를 사용한다
- 단위 테스트로는 시스템이 전체적으로 동작하는지를 충분히 확신할 수 없다
높은 내부 품질은 어떻게 달성할 수 있을까?
높은 응집과 낮은 결합
을 유지해야한다- 코드의 동작 방식을 얼마나 쉽게 바꿀 수 있는지를 설명하는 척도이기 때문이다
- 한 요소의 변경이 다른 요소의 변경에 영향을 미친다면, 그 두 요소는
결합
되어 있는 것이다.- 이를 최대한 낮게 유지해야 한다
응집도
는 해당 요소의 책임이 의미있는 단위를 형성하는지 나타내는 척도이다- 날짜와 URL을 파싱하는 기능이 같이 들어간 클래스는 응집도가 낮다고 표현할 수 있다
- 그리고, 두 가지 기능을 다 잘할 가능성이 낮다
- 객체에 대한 단위테스트를 많이 해야한다
- 객체를 생성하고, 객체의 의존성을 제공하며, 객체와 상호 작용하고, 예상대로 동작하는지 검사할 필요가 있다
- 설계를 잘못하면 단위 테스트를 작성하거나 이해하기 어렵다
- 예를 들면 클래스가 멀리 떨어져있는 시스템의 일부와 긴밀하게 결합되어 있거나, 암시적인 의존성이 있거나, 불분명한 책임이 너무 많을때 등이 있다
결과적으로 높은 응집과 낮은 결합
을 가진 객체가 많아야 높은 내부 품질을 유지할 수 있게 되는데, 테스트 작성이 이를 측정하는 기준점이 될 수 있다.
TDD는 테스트를 먼저 작성함으로써, 이러한 설계에 관한 즉각적인 피드백을 바로 얻을 수 있다는 큰 장점이 있다.