eumjo_o
Unit Testing 4장 본문
4장 좋은 단위 테스트의 4대 요소
가치있는 테스트를 어떻게 식별할 수 있을까??
4.1 좋은 단위 테스트의 4대 요소 자세히 살펴보기
- 회귀 방지
- 리펙터링 내성
- 빠른 피드백
- 유지 보수성
4.1.1 첫번째 요소: 회귀 방지
💡 회귀: 코드를 수정한 후 기능이 의도한 대로 작동하지 않는 경우 회귀방지: 테스트가 얼마나 **버그(회귀)**의 존재를 잘 나타내는지에 대한 척도
- 코드는 자산이 아니라 책임!!!
테스트에서 버그(회귀)가 드러날 확률이 높아지는 원인 3가지
- 테스트 중에 실행되는 코드의 양
- 코드 복잡도
- 코드의 도메인 유의성
4.1.2 두 번째 요소: 리펙터링 내성
- 리펙터링을 통해 코드의 비기능적 특징을 개선하는 것으로 가독성을 높이고 복잡도를 낮추는 것
💡 거짓 양성(false positive) 실제로 기능이 의도한 대로 작동하지만 테스트는 실패를 나타내는 것
- 허위 경보에 익숙해지고 주의를 기울이지 않기 때문에 코드 문제에 대응하려는 능력과 의지가 희석된다.
- 테스트를 신뢰할 수 있는 안전망으로 인식하는 것이 줄어들고 테스트 스위트에 대한 신뢰를 잃게 된다.
- 거짓 양성은 프로젝트 초기에 부정적인 영향을 미치지는 않지만 프로젝트가 성장함에 따라 점점 더 중요해진다. 즉 거짓 음성(알려지지 않은 버그)만큼 중요하다.
💡 리팩터링 내성 테스트가 거짓 양성을 내지 않고(테스트를 빨간색으로 바꾸지 않고) !! 애플리케이션 코드 리펙터링을 유지할 수 있는 정도를 의미
4.1.3 무엇이 거짓 양성의 원인인가?
- 거짓 양성의 수는 테스트 대상 시스템의 내부 구현 세부 사항과 테스트 간의 강결합의 결과.
- 결합도를 낮추려면 테스트는 SUT가 수행한 단계가 아니라 SUT가 만든 최종 결과를 검증해야 한다.
- 거짓 양성이 생길 가능성을 줄이는 방법은 해당 구현 세부사항에서 테스트를 분리하는 것 뿐이다.
똑같이 적용할 수 있는 다른 구현을 고려하지 않고 특정 구현만 예상해서 알고리즘을 검사한다.
4.1.4 구현 세부 사항 대신 최종 결과를 목표로 하기
- 리팩터링 내성을 높이는 방법은 SUT의 구현 세부사항과 테스트 간의 결합도를 낮추는 것.
좋은 테스트: “최종 결과가 올바른가?” 좋지 않은 테스트: “이 모든 단계가 올바른가?”
4.2 첫 번째 특성과 두 번째 특성 간의 본질적인 관계
- 회귀방지와 리펙터링 내성은 테스트 정확도에 기여한다. 테스트는 가능한 한 적은 소음(거짓 양성)으로 강한 신호(버그를 찾을 수 있음. 회귀 방지 영역)를 발생시키기 때문에 정확하다.
4.2.1 테스트 정확도 극대화
오류 유형 표 작동 고장
테스트 통과 | 올바른 추론 (참 음성) | 2종 오류(거짓 음성) ⇒ 회귀 방지 |
테스트 실패 | 1종 오류(거짓 양성) ⇒ 리펙터링 내성 | 올바른 추론 (참 양성) |
- 정확도 지표
- 테스트가 버그 있음을 얼마나 잘 나타내는가 (== 거짓 음성(회귀 방지 영역) 제외)
- 테스트가 버그 없음을 얼마나 잘 나타내는가 (== 거짓 양성 (리펙터링 내성 영역) 제외)
테스트 정확도 = 신호(발견된 버그 수) / 소음 (허위 경보 발생 수)
- 테스트의 정확도를 높이는 방법
- 분자, 신호를 증가시키는 것 , 회귀를 더 잘 찾아내는 테스트로 개선하는 것
- 분모, 소음을 줄이는 것, 허위 경보를 발생시키지 않는 테스트로 개선하는 것
- 가능한 한 소음(허위 경보)이 적은 강한 신호(버그를 찾을 수 있음)를 생성할 때,
4.2.2 거짓 양성과 거짓 음성의 중요성: 역학 관계
- 거짓 양성(허위 경보)은 초기에 그다지 부정적인 영향을 미치지 않는다.
- 초기에는 리팩터링이 바로 중요하지 않고, 시간이 흐를 수록 코드베이스는 복잡해지고 체계적이지 않게 된다.
- 그러나 프로젝트에 거짓 음성(알려지지 않은 버그)이 중요한 만큼 거짓 양성도 점점 더 중요해진다.
- 코드 베이스가 나빠질수록 새로운 기능에 드는 비용이 커지기 때문에 리팩터링이 점점 필요해지고, 이에 따라 테스트에서 리팩터링 내성도 중요해진다.
4.3 세 번째 요소와 네 번째 요소: 빠른 피드백과 유지 보수성
- 빠른 피드백
- 테스트 속도가 빠를수록 테스트 스위트에서 더 많은 테스트를 수행할 수 있고 더 자주 실행할 수 있다.
- 테스트가 빠르게 실행되면 코드에 결함이 생기자마자 버그에 대해 경고하고 버그를 수정하는 비용을 줄일 수 있다.
- 유지 보수성
- 테스트가 얼마나 이해하기 어려운가
- 테스트는 코드 라인이 적을수록 더 읽기 쉽다. 하지만 인위적으로 압축하지는 말아야 한다.
- 테스트 코드의 품질은 제품 코드만큼 중요하고, 테스트를 작성할 때 절차를 생략하지 말아야 한다.
- 테스트 코드를 일급 시민(first-class citizen)으로 취급하라
- 테스트가 얼마나 실행하기 어려운가
- 테스트에 관련된 프로세스 외부 의존성은 적을수록 쉽게 운영할 수 있다.
- 테스트가 프로세스 외부 종속성으로 작동하면, 데이터베이스 서버를 재부팅하고 네트워크 연결 문제를 해결하는 등 의존성을 상시 운영하는 시간을 들여야 한다.
- 테스트가 얼마나 이해하기 어려운가
4.4 이상적인 테스트를 찾아서
테스트의 가치 = 회귀 방지 X 리펙터링 내성 X 빠른 피드백 X 유지 보수성
- 어떠한 특성이라도 0이 되면 전체가 0이 된다.
- 가치가 있으려면 테스트는 네 가지 범주 모두에서 점수를 내야 한다.
- 가치 추정치는 [0..1] * [0..1] * [0..1] * [0..1]
- 테스트 코드를 포함한 모든 코드는 책임(liability)이고, 최소한으로 필요한 가치로 임계치를 상당히 높게 설정하고, 이 임계치를 충족하는 테스트만 테스트 스위트에 남긴다.
4.4.1 이상적인 테스트를 만들 수 있는가?
- 이상적인 테스트는 각 속성마다 모두 1을 갖는 것
- 회귀 방지, 리팩터링 내성, 빠른 피드백은 모두 상호 배타적이기 때문에, 세 가지 특성 모두 최대로 하는 것은 불가능하고, 셋 중 하나를 희생해야 나머지 둘을 최대로 할 수 있다.
4.4.2 극단적인 사례1: 엔드 투 엔드 테스트
- 많은 코드를 테스트하므로 회귀 방지를 훌륭하게 해낸다.
- 거짓 양성에 면역이 돼 리팩터링 내성도 우수하다.
- 하지만, 엔드 투 엔드 테스트에만 의존하는 모든 시스템은 피드백을 빨리 얻기가 어렵고, 느리다.
4.4.3 극단적인 사례2: 간단한 테스트
- 간단한 테스트는 매우 빠르게 실행되고 빠른 피드백을 제공한다.
- 거짓 양성이 생길 가능성이 상당히 낮기 때문에 리펙터링 내성도 우수하다.
- 간단한 테스트는 우수한 리팩터링 내성과 빠른 피드백을 제공하지만 회귀 방지가 없다.
4.4.4 극단적인 사례3: 깨지기 쉬운 테스트
- 깨지기 쉬운 테스트(Britte test)
- 실행이 빠르고 회귀를 잡을 가능성이 높지만 거짓 양성이 많은 테스트를 작성하기가 매우 쉽다.
- 깨지기 쉬운 테스트는 빠르게 실행되고 회귀 방지를 훌륭히 해내지만, 리팩터링 내성은 거의 없다.
4.4.5 이상적인 테스트를 찾아서: 결론
- 좋은 단위 테스트의 처음 세 가지 특성(회귀방지, 리팩터링 내성, 빠른 피드백)은 상호배타적
- 세 가지 특성 중 두 가지를 극대화하는 테스트를 만들기는 쉽지만 나머지 특성 한가지를 희생해야 하고, 세 가지 특성 모두 완벽한 점수를 얻어서 이상적인 테스트를 만드는 것은 불가능하다.
- 엔드 투 엔드 테스트는 관련된 모든 의존성을 설정해야 하므로 계속 운영하려면 추가적인 노력이 더 들고, 유지비 측면에서 더 비싼 경향이 있다.
- 최상의 테스트는 유지 보수성과 리팩터링 내성을 최대로 갖기 때문에 항상 이 두 특성을 최대화하도록 노력해야 한다.
- 회귀 방지, 리팩터링 내성, 빠른 피드백의 상호 배타성 때문에 세 가지 특성 모두를 양보할 만큼 서로 조금씩 인정하는 것이 최선의 전략이다.
- 테스트가 얼마나 버그를 잘 찾아내는지(회귀방지)와 얼마나 빠른지(빠른 피드백) 사이의 선택으로 절충이 귀결된다.
- 테스트 스위트를 탄탄하게 만들려면 테스트의 불안정성(거짓 양성)을 제거하는 것이 최우선 과제
💡 CAP 정리
일관성(consistency) - 모든 읽기가 가장 최근의 쓰기 또는 오류를 수신하는 것을 의미
가용성(availability) - 모든 요청 (시스템 내 전체 노드 중단은 제외하고) 응답을 수신하는 것을 의미
분할 내성(partition tolerance) - 네트워크 분할(네트워크 노드 간 연결 끊김)에도 시스템이 계속 작동함을 의미
일관성과 가용성 간의 절충
4.5 대중적인 테스트 자동화 개념 살펴보기
- 테스트 피라미드와 화이트박스 테스트 대 블랙박스 테스트
- 테스트를 작성할 때는 블랙박스 테스트 방법
- 테스트를 분석할 때는 화이트박스 방법
4.5.1 테스트 피라미드 분해
테스트 스위트에서 테스트 유형 (단위 테스트, 통합 테스트, 엔드 투 엔드 테스트) 간의 일정한 비율을 일컫는 개념
- 엔드 투 엔드 테스트 < 통합 테스트 < 단위 테스트
4.5.2 블랙박스 테스트와 화이트박스 테스트 간의 선택
💡 블랙박스 테스트 (Black-box testing) - 시스템의 내부 구조를 몰라도 시스템의 기능을 검사할 수 있는 소프트웨어 테스트 방법.
💡 화이트박스 테스트(White-box testing) - 애플리케이션의 내부 작업을 검증하는 테스트 방식. 테스트는 요구 사항이나 명세가 아닌 소스 코드에서 파생된다.
회귀 방지 리팩터링 내성
화이트박스 테스트 | 좋은 | 나쁨 |
블랙박스 테스트 | 나쁨 | 좋음 |
- 검증문을 작성할 때, 제품 코드에 의존하지 말아야 한다.
- 테스트에서 별도의 리터럴과 상수집합을 사용하고, 필요시 리터럴과 상수를 복제해야 한다.
- 테스트는 제품 코드와 독립적으로 검사점을 제공해야 하고, 그렇지 않을 시 동어반복 테스트( 아무것도 검증하지 않고, 무의미한 검증문만 있는 테스트)를 만들 위험이 있다.