테스트 주도 개발로 배우는 객체 지향 설계와 실천 - 1장 테스트 주도 개발의 핵심은 무엇인가?
26 Mar 2021Intro
초서-독서 한 내용을 그대로 적는 곳이기 때문에 책을 읽지 않은 분들이 보기에 맥락이 애매할 수 있습니다.
초서 : 책을 읽는데 그치는 것이 아닌 손을 이용해 책의 중요한 내용을 옮겨 적음으로써 능동적으로 책의 내용을 수용하고 판단하여 새로운 지식을 재창조하는 과정. 메타인지 학습법
Index
- 들어가면서
- 1부 서론
- 1장 테스트 주도 개발의 핵심은 무엇인가? «
- 2장 객체를 활용한 테스트 주도 개발
- 3장 도구 소개
- 2부 테스트 주도 개발 과정
- 4장 테스트 주도 주기 시작
- 5장 테스트 주도 개발 주기의 유지
- 6장 객체 지향 스타일
- 7장 객체 지향 설계의 달성
- 8장 서드 파티 코드를 기반으로 한 개발
- 3부 동작하는 예제
- 9장 경매 스나이퍼 개발 의뢰
- 10장 동작하는 골격
- 11장 첫 테스트 통과하기
- 12장 입찰 준비
- 13장 스나이퍼가 입찰하다
- 14장 스나이퍼가 경매에서 낙찰하다
- 15장 실제 사용자 인터페이스를 향해
- 16장 여러 품목에 대한 스나이핑
- 17장 Main 분석
- 18장 세부 사항 처리
- 19장 실패 처리
- 4부 지속 가능한 테스트 주도 개발
- 20장 테스트에 귀 기울이기
- 21장 테스트 가독성
- 22장 복잡한 테스트 데이터 만들기
- 23장 테스트 진단
- 24장 테스트 유연성
- 5부 고급 주제
- 25장 영속성 테스트
- 26장 단위 테스트와 스레드
- 27장 비동기 코드 테스트
1부 서론
1장 테스트 주도 개발의 핵심은 무엇인가?
학습 과정으로서의 소프트 개발
- 프로젝트에는 미처 예상하지 못한 요소가 있게 마련이다.
- 갖가지 중요한 구성 요소가 조합된 시스템은 너무나도 복잡해서 개인이 해당 시스템의 모든 가능성을 이해하기는 어렵다.
- ‘불확실한 변화를 예측하려면 경험이 늘어남에 따라 불확실성을 해결하는 데 도움이 될 프로세스가 필요하다.’
피드백은 가장 기본적인 도구다
- 경험에 의거한 피드백
- 팀에는 반복적인 확동 주기가 필요하다.
- 각 주기마다 양과 질에 관한 피드백을 받는다.
- 배포는 현실에서 자신이 내린 가정을 검사할 기회이며, 배포하지 않고는 피드백이 완전해지지 않는다.
- ‘중첩된 고리형 시스템’ 으로 피드백 주기를 적용하라
- 짝 프로그래밍, 단위 테스트, 인수 테스트, 일별 회의, 반복 주기, 출시 등…
- 중첩된 각 고리마다 팀의 산출물이 경험에 의거한 피드백으로 드러나 팀에서는 오류나 오해를 발견하고 수정함.
- 고리는 서로를 강화함 (안쪽에서 뭔가 모순되는 것이 바깥쪽에서 포착됨)
- 안쪽 고리는 ‘기술적 세부 사항’ 에 집중 (단위 코드의 역할, 시스템 나머지 부분과의 통합 여부)
- 바깥쪽 고리는 ‘조직과 팀’ 에 집중
- 피드백을 일찍 받을수록 좋다
- 점진적이고 반복적인 개발
- 점진적인 개발에서는 모든 계층과 구성 요소를 구축한 다음 그것들을 마지막에 통합하는 대신 시스템을 ‘기능별’ 로 구축.
- 각 기능은 시스템 전 구간에 이르는 ‘조각’ 으로 구현됨.
- 시스템은 언제나 통합된 상태이며 배포할 준비가 되어 있음.
- 반복적인 개발은 계속해서 충분한 상태에 이를 때까지 ‘피드백에 응답’ 해 ‘기능 구현’ 을 다듬는다.
변화를 돕는 실천법
- 시스템 규모를 점진적으로 키우고, 늘 일어나는 예상치 못한 변화에 대처하기 위해 필요한 두 가지
- 회귀 오류를 잡아줄 꾸준한 테스트 작성, 테스트 자동화
- 단순한 코드 유지 (리팩터링)
- 코드를 작성하기 전에 테스트를 작성한다.
- 테스트를 ‘설계 활동’ 으로 바꾼다.
- 테스트를 사용해 ‘코드에서 하고 싶은 바’ 에 관한 생각을 명확하게 한다.
(‘물리적인 설계’, ‘논리적인 설계’의 분리) - 깔끔하고 모듈화된 코드가 생성됨.
- 빠른 품질 피드백
” 코드 변경에 대한 ‘자신감’ 을 주는 자동화된 회귀 테스트라는 안전망을 구축할 수 있다. “
테스트 주도 개발 간단 정리
TDD 핵심 주기
- 테스트 작성
- 동작하는 코드 작성
- 리팩터링
TDD 혜택
- 다음 작업에 대한 인수 조건이 명확해짐. (자신이 테스트 코드를 작성하니까 당연함..)
- 느슨한 구성 요소로 단계별 테스트 가능.
(격리된 상태 -> 결합된 상태 -> 좀 더 결합된 상태 -> … 점점 더 높은 수준으로) - 실행 가능한 설명.
- 완전한 회귀 스위트가 늘어남
cf) 리펙터링은 작은 규모의 개선 사항을 찾아내는 식으로 진행되는 ‘미시적 기법’이다.
경험상 리팩터링의 ‘여러 작은 단계’ 를 엄격하고 지속적으로 적용해야만 ‘커다란 구조적 개선’ 으로 이어질 수 있다.
주의) 리펙터링은 재설계와 같은 활동이 아니다.
좀 더 큰 그림
- 기존 ‘레거시’ 에 바로 단위 테스트 작성하여 TDD를 시작하고 싶은 ‘유혹’ 에 빠질 수 있다.
(없는 것 보단 낫다고 함) - 단위 테스트만 있는 프로젝트는 TDD 프로세스가 주는 혜택을 놓칠 수 있다.
- 아무 데서도 호출하지 않는다. (테스트 자동화 부재?)
- 시스템의 나머지 부분과 통합할 수 없다.
- 그렇다면 코드 작성을 어디서 부터 시작할까?
실패하는 테스트
- 어떤 기능을 구현할 때 인수 테스트를 작성하는 것으로 시작.
- cf) 인수 테스트 : 만들고자 하는 기능을 시험하는 테스트
- 인수 테스트를 사용해 작성하려는 코드가 실제로 필요한지 가늠한다. (직접 관련된 코드만 작성)
- 인수 테스트 하에서 단위 수준의 테스트, 구현, 리팩터링 주기를 따라 기능을 개발.
- 인수 테스트는 통과 시간이 길다. 아래와 같이 구분한다.
- ‘현재 작업 중인 인수 테스트’(빌드에 아직 포함 되지 않는)
- ‘작업을 마친 인수 테스트’(빌드에 포함되며 반드시 통과해야 하는)
- 실패하는 단위 테스트는 소스 저장소에 절대 커밋해서는 안 된다.
전 구간 테스트
- 인수 테스트에서는 시스템 내부 코드를 가능한 한 직접 호출하지 말고 ‘시스템 전 구간’ 을 시험해야 한다.
- 외부 유입 시스템 하고만 상호작용 한다.
(시스템의 전체적인 작동 방식에는 시스템 외부 환경과의 상호 작용을 포함해야 한다) - 단지 외부에서 유래한 시스템과 상호 작용하는 것이라면 ‘경계 간’테스트라고 부르는 편이 더 낫다.
- 전 구간 테스트는 시스템과 해당 시스템을 구축하고 배포하는 ‘프로세스를 모두 시험’ 하는 방식으로 진행된다.
전 구간 빌드 주기 자동화 (CI,CD)
- 자동화 주기
- 누군가 소스 저장소에 코드 체크인 (수동)
- 최신 버전을 체크아웃해서 코드를 컴파일 (이하 자동)
- 단위 테스트 실행
- 시스템에 통합, 패키지
- 운영 환경 수준으로 배포
- 외부 접근 지점을 통한 시스템 시험
- 소프트웨어의 생애 동안 반복적으로 이루어짐.
- 실제보다 좀 더 어려운 출시 주기가 만들어 질수도 있으므로 전체적인 기술과 조직적인 환경을 이해해야 함.
테스트의 수준
- 인수 테스트
- ‘전체 시스템이 동작하는가 ?’
- 전 구간의 기능이 제대로 동작함을 보증한다.
- 관련 기술 또는 조직 문화에 따라 차이가 있음.
- 통합 테스트
- ‘변경할 수 없는 코드(외부)를 대상으로 코드가 동작하는가 ?’
- 서드 파티 코드를 대상으로 만든 추상화가 기대한 대로 동작하는지 확인.
- 영속화 매퍼같은 공용 프레임워크나 조직 내 다른 팀에서 개발한 라이브러리 등..
- (큰 프로젝트의 경우) 느린 인수 테스트에 비해 좀 더 빠른 피드백을 얻기 위해 통합 테스트가 필요.
- (작은 프로젝트의 경우) 인수 테스트가 통합 테스트의 역할을 할 수도 있음.
- 관련 기술 또는 조직 문화에 따라 차이가 있음.
- 단위 테스트
- ‘객체가 제대로 동작하는가 ? 객체를 이용하기가 편리한가 ?’
- (개인)프로그래밍 스타일에 따라 달라짐.
- 모든 시스템에 공통으로 적용됨.
외부 품질과 내부 품질
- 외부 품질
- 시스템이 고객과 사용자의 요구를 얼마나 잘 충족하는가
- 기능, 신뢰성, 가용성, 응답성 등
- 내부 품질
- 시스템이 개발자와 관리자의 요구를 얼마나 잘 충족하는가
- 이해하기 쉬운가, 변경하기 쉬운가 등 (코드 가독성, 느슨한 설계 등)
- 내부 품질을 유지하기 위해 시스템 동작 방식을 ‘안전’ 하고 ‘예상 가능한 상태’ 로 ‘바꿀 수 있게’ 만들어야 한다.
- 그렇게 해야만 ‘변경’ 으로 인해 ‘큰 규모의 재작업을 해야 할 위험’ 을 최소화할 수 있음.
- 전 구간 테스트로 시스템 외부 품질을 알 수 있다.
- 전 구간 테스트를 작성하면 팀 전체가 도메인을 얼마나 잘 이해하는지 알 수 있다.
- 단위 테스트로 코드를 얼마나 잘 작성했는지 알 수 있다. (전 구간 테스트로는 알 수 없음)
단위 테스트
- 철저한 단위 테스트는 내부 품질을 개선하는데 도움이 된다.
- 단위를 테스트하려면 ‘테스트 픽스처’ 에서 해당 단위를 시스템 바깥에서 실행할 수 있게 ‘구조화’ 해야 하기 때문.
- 객체에 대한 단위 테스트 하려면
- 객체를 생성하고 해당 객체의 의존성을 제공하며, 객체와 상호 작용하고, 예상대로 동작하는지 검사.
- 클래스가 대체할 수 있는 명시적인 의존성(=느슨한 결합)과 명확한 책임(=높은 응집력)을 지녀야 한다.
- 설계를 잘못하면 (클래스가 멀리 떨어져 있는 시스템의 일부와 긴밀하게 결합 | 암시적인 의존성 | 불분명한 책임이 많음 등)
‘단위 테스트를 작성하거나 이해하기 어려워짐’ - 테스트에 귀 기울이기
- ‘테스트 하기 싫다’ ↓
- ‘왜? 하기 싫을까’ (작성하기 어려운 이유 조사) ↓
- 도메인 재설계(설계 방식이 틀리진 않았을까) | 코드 구조 개선(리팩터링)
테스트 픽스처 란
- cf) 테스트 픽스처란 : SUT(System Under Test)를 실행하기 위해 필요한 모든 것.
- 픽스처는 테스트에 필요한 자원 생성, 테스트 가능한 상태로 세팅. (픽스처 설치 단계에 해당)
- 픽스처는 테스트 ‘선조건’ 을 의미, 이를 구현한 클래스는 Test Case Class 이다.
- 1회용 신선한 픽스처
- 테스트가 실행될 때마다 새로운 픽스처 생성.
(다른 테스트와 의존 관계가 없는 완벽한 독립성 보장, 대신 느림)
- 테스트가 실행될 때마다 새로운 픽스처 생성.
- 지속되는 픽스처
- 테스트 대상 컴포넌트가 ‘어떤 상태를 유지 시키는 매커니즘을 가진 것’과 강하게 결합되어 있을 때 지속되는 픽스처가 될 수 있다.
- 테스트 수행마다 해체 코드를 실행하여 ‘지속되는 신선한 픽스처’로 변경 가능
- 픽스처를 지속되게 만드는 요소
- 데이터 베이스 사용
- 클래스 변수에 데이터 저장
- 다른 테스트에서 사용되는 1회용 픽스처를 수행 후에도 Test Case Class 에서 갖고 있는 경우
- 공유 픽스처
- 테스트 실행 속도를 향상시키기 위해 사용.
많은 테스트가 하나의 픽스처를 공유하여 재사용. - 오래된 픽스처는 어질러지는 부수효과 발생, 이로인해 반복 안되는 테스트, 테스트 실행 전쟁 등 문제 발생.
테스트 실행 전쟁 : 각 테스트에서 동시에 같은 픽스처 자원 접근, 테스트가 무작위로 실패하게 되는 문제.
- 테스트 실행 속도를 향상시키기 위해 사용.
- 1회용 신선한 픽스처
- 들어가면서
- 1부 서론
- 1장 테스트 주도 개발의 핵심은 무엇인가? «
- 2장 객체를 활용한 테스트 주도 개발
- 3장 도구 소개
- 2부 테스트 주도 개발 과정
- 4장 테스트 주도 주기 시작
- 5장 테스트 주도 개발 주기의 유지
- 6장 객체 지향 스타일
- 7장 객체 지향 설계의 달성
- 8장 서드 파티 코드를 기반으로 한 개발
- 3부 동작하는 예제
- 9장 경매 스나이퍼 개발 의뢰
- 10장 동작하는 골격
- 11장 첫 테스트 통과하기
- 12장 입찰 준비
- 13장 스나이퍼가 입찰하다
- 14장 스나이퍼가 경매에서 낙찰하다
- 15장 실제 사용자 인터페이스를 향해
- 16장 여러 품목에 대한 스나이핑
- 17장 Main 분석
- 18장 세부 사항 처리
- 19장 실패 처리
- 4부 지속 가능한 테스트 주도 개발
- 20장 테스트에 귀 기울이기
- 21장 테스트 가독성
- 22장 복잡한 테스트 데이터 만들기
- 23장 테스트 진단
- 24장 테스트 유연성
- 5부 고급 주제
- 25장 영속성 테스트
- 26장 단위 테스트와 스레드
- 27장 비동기 코드 테스트