•
4장에서 다루는 내용은 다음과 같다.
◦
좋은 단위 테스트의 관점 간 차이점 모색
◦
이상적인 테스트 정의
◦
테스트 피라미드 이해
◦
블랙박스 테스트 및 화이트박스 테스트 사용
•
앞서 우리는 좋은 단위 테스트 스위트의 특성을 살펴봤다. 좀 더 구체화시켜서 이번에는 좋은 단위 테스트의 특성들을 이해해보자.
1. 좋은 단위 테스트의 4대 요소 자세히 살펴보기
•
좋은 단위 테스트에는 다음 네 가지 특성이 있다.
◦
회귀 방지
◦
리팩토링 내성
◦
빠른 피드백
◦
유지 보수성
1. 회귀 방지
•
좋은 단위 테스트는 회귀 방지를 수행해내야 한다. 회귀란 코드를 수정한 후 기능이 의도한대로 동작하지 않는 것이다.
•
회귀 방지 지표에 대한 점수를 확인하려면 다음 사항을 확인하면 된다.
◦
테스트 중에 실행되는 코드의 양
▪
테스트 시 실행되는 코드의 양이 많을수록 회귀를 잡아낼 가능성이 높아진다.
▪
그렇지만 이 회귀가 정말로 버그인지를 확인하는 작업도 중요하다.
◦
코드 복잡도
▪
복잡한 비즈니스 로직을 나타내는 코드가 보일러플레이트 코드보다 훨씬 더 중요하다.
•
여기서 보일러플레이트 코드는 큰 수정없이 공통으로 사용되는 코드를 의미한다.
▪
비즈니스의 중요한 기능에서 발생한 버그가 가장 큰 피해를 입히기 때문이다.
◦
코드의 도메인 유의성
▪
단순한 코드를 테스트하는 것은 의미가 없다. 비즈니스 로직도 거의 없으며 짧기까지 하다.
▪
단순한 코드를 다루는 테스트는 실수할 여지가 많지 않아 오류가 발생할 염려가 적다.
2. 리팩토링 내성
•
좋은 단위 테스트의 두 번째 특성은 리팩토링 내성이다.
◦
리팩토링 내성이란 제품 코드의 리팩토링에 대해 테스트 스위트가 얼마나 견뎌낼 수 있냐를 의미한다.
◦
리팩토링은 하나의 단위로 인식되는 동작 단위를 변경하지 않고 기존 코드를 변경하는 것을 의미한다.
•
다음 예시를 통해 확인해보자.
◦
리팩토링을 하고 테스트를 수행했더니 테스트가 실패했다. 기능은 정상적인데 말이다.
◦
이러한 상황을 거짓 양성이라고 한다. 거짓 양성은 허위 경보다. 실제로 기능이 의도한 대로 작동하지만 테스트는 실패를 나타내는 결과다.
테스트 실패 | 테스트 성공 | |
비정상 코드 | 참 양성(True Positive) | 거짓 음성(False Negative) |
정상 코드 | 거짓 양성(False Positive) | 참 음성(True Negative) |
▪
문제가 없는 코드가 테스트에 실패할 때 거짓 양성이라고 한다.
•
거짓 양성이 발생한다는 것은 제품 코드의 리팩토링에 테스트 스위트가 영향을 받았다는 것이고 테스트가 테스트 대상 코드와 강하게 결합되어있음을 의미한다.
•
거짓 양성은 당연히 문제가 된다. 단위 테스트의 목적은 생산성을 유지할 수 있도록 지원하는 것인데, 좋은 단위 테스트는 다음 두 가지 장점을 제공한다.
◦
기존 기능이 고장났을 때, 조기 경보기의 역할을 수행한다. 이를 통해 배포가 된 후 문제를 알게 되지 않아도 된다.
◦
코드 변경이 오류로 발생하지 않을 것이라는 확신을 제공한다. 이러한 확신이 없으면 리팩토링은 어려워지고 코드가 나빠질 가능성이 높아진다.
•
거짓 양성은 이 두가지 장점을 모두 방해한다.
◦
테스트가 타당한 이유없이 실패하면 문제가 외부에 있을 것이라 치부해버리고 점점 테스트 코드의 경고에 무감각해진다.
◦
더 이상 리팩토링의 안전망으로 여겨지지 않게 된다. 신뢰가 부족해지면 리팩토링은 줄어들게 된다.
3. 무엇이 거짓 양성의 원인인가?
•
테스트와 테스트 대상 시스템의 구현 세부 사항의 결합도가 높을수록 거짓 양성이 더 많이 생긴다.
•
거짓 양성이 생길 가능성을 줄이는 방법은 해당 구현에서 테스트를 분리시키는 것뿐이다.
•
테스트는 클라이언트의 관점에서 테스트 대상 시스템을 검증해야 하고 클라이언트에게 의미 있는 결과만 제공해야 한다.
4. 구현 세부 사항 대신 최종 결과를 목표로 하기
•
거짓 양성을 만들지 않고 리팩토링 내성을 높이는 방법은 테스트 대상 시스템의 구현과 테스트 간의 결합도를 낮추는 것 뿐이다.
•
즉, 코드와 테스트 사이를 가능한 멀리 떨어뜨리고 최종 결과만을 바라보게 하는 것이다.
2. 첫 번째 특성과 두 번째 특성 간의 본질적인 관계
1. 테스트 정확도 극대화
•
테스트가 통과하고 대상 기능이 의도한대로 동작하는 상황은 올바른 추론이다. 테스트는 시스템의 상태를 올바르게 유추했다.
◦
이를 참 음성이라고 한다.
•
마찬가지로 기능이 고장나서 테스트가 실패하는 것도 올바른 추론이다. 기능이 제대로 동작하지 않았을때, 실패할 것으로 예상하기 때문이다.
◦
이는 참 양성이다.
•
그러나 테스트에서 오류가 발생하지 않으면 문제가 발생한다. 이는 거짓 음성이다. 즉 실패해야하는 데 테스트가 성공하는 것이다.
•
거짓 음성을 피하는 데 도움을 주는 좋은 단위 테스트의 특성은 회귀 방지다. 회귀 방지는 거짓 음성의 수를 최소화하는 데 도움이 된다.
•
반면에 기능은 올바르게 설계되었으나 테스트는 실패하는 대칭적인 상황이 있다. 이는 거짓 양성 즉 허위 경보다. 거짓 양성을 피하는 데는 리팩토링 내성이 도움을 준다.
•
거짓 음성과 거짓 양성이 낮을 수록 테스트의 정확도가 높다.
•
테스트 정확도를 향상시키는 방법으론 두가지가 있다.
◦
첫 번째는 오류를 더 잘 찾아내는 테스트로 개선하는 것이다.
◦
두 번째는 허위 경보를 발생시키지 않는 테스트로 개선하는 것이다.
2. 거짓 양성과 거짓 음성의 중요성: 역학 관계
•
단기적으로는 거짓 양성도 거짓 음성만큼 나쁘지 않다.
•
초기에는 리팩토링이 중요하지 않기 때문에 거짓 양성이 소외된다. 하지만 프로젝트가 점차 진행됨에 따라 리팩토링이 발생하므로 리팩토링 내성이 중요해진다.
•
이 과정에서 계속된 허위 경보가 울린다면 이 경보를 신경쓰지 않고 진행해 문제의 여지를 남겨두거나 이 경보를 두려워해 아무것도 하지 않거나가 될 것이다.
•
거짓 양성으로부터 코드를 보호하는 것이 중요하다해도 이를 신경쓰는 프로그래머는 적다.
◦
후반까지 가는 프로젝트가 훨씬 적기 때문이며 대부분의 프로젝트는 작고, 커지기 전에 끝나기 때문이다.
•
중대형 프로젝트에서 작업을 수행한다면 거짓 음성과 거짓 양성에 대해 똑같이 주의를 기울여야 한다.
3. 세 번째 요소와 네 번째 요소: 빠른 피드백과 유지 보수성
•
빠른 피드백은 단위 테스트의 필수 속성이다.
◦
테스트가 빠르게 실행되면 코드에 문제가 생기자마자 버그에 대해 경고를 해주므로 버그를 수정하는 비용을 줄일 수 있다.
•
유지 보수성은 유지비를 평가한다. 이 지표는 다음 두 가지 주요 요소로 구성된다.
◦
테스트의 가독성이 좋은가
▪
테스트는 코드 라인이 적을수록 가독성이 좋다. 작은 테스트는 변경도 용이하다. 테스트 코드의 품질은 제품 코드만큼 중요하다. 테스트 코드도 일급 시민이다.
◦
테스트의 외부 의존도가 높은가
▪
테스트가 프로세스 외부 의존성으로 동작한다면 데이터베이스 서버를 재부팅하고 네트워크도 신경써야하는 등 비용이 발생한다.
4. 이상적인 테스트를 찾아서
•
좋은 단위 테스트의 4가지 특성을 곱하면 테스트의 가치가 결정된다.
◦
곱하니까 하나라도 0이면 0이다..
•
테스트 코드를 포함하는 모든 코드는 책임이다. 최소의 기준치에 대해 높은 임계치를 설정하고 이 임계치를 충족하는 테스트만 테스트 스위트에 남겨라.
•
소수의 가치 있는 테스트는 다수의 평범한 테스트보다 프로젝트의 생산성을 유지하는데 훨씬 더 효과적이다.
1. 이상적인 테스트를 만들 수 있는가?
•
이상적인 테스트는 존재하지 않는다. 회귀 방지, 리팩토링 내성, 빠른 피드백은 상호 배타적이기 때문이다. 셋 중 하나를 희생해야 나머지가 최대가 될 수 있다.
2. 극단적인 사례 1: E2E 테스트
•
첫 번째 예는 E2E 테스트다.
•
E2E 테스트는 많은 코드를 테스트하므로 회귀 방지를 훌륭하게 달성한다. 거짓 양성에는 면역이라 리팩토링 내성도 우수하다.
•
하지만 E2E 테스트는 느리다. E2E 테스트에만 의존하는 시스템은 빠른 피드백을 받기가 어렵다.
•
따라서 E2E 테스트만으로는 코드를 다루기 힘들다.
3. 극단적인 사례 2: 간단한 테스트
•
간단한 테스트는 매우 바르게 실행되고 빠른 피드백을 제공한다. 또한 거짓 양성을 생길 가능성이 낮기 때문에 리팩토링 내성도 우수하다.
•
그러나 기반 코드에 실수할 여지가 많지 않기 때문에 간단한 테스트는 오류를 나타내지 않을 것이다. 즉 회귀 방지의 역할을 제대로 수행해내지 못한다.
4. 극단적인 사례 3: 깨지기 쉬운 테스트
•
마찬가지로 실행이 빠르고 회귀 방지도 훌륭하게 수행해내지만 거짓 양성이 많은 테스트를 작성하게되는 것은 매우 쉽다.
•
이러한 테스트를 깨지기 쉬운 테스트라고 한다. 이는 리팩토링을 견디지 못하고 해당 기능이 고장이 나던 정상이던 관계없이 실패로 바꾼다.
5. 이상적인 테스트를 찾아서: 결론
•
좋은 단위 테스트의 4가지 특성 중 회귀 방지, 리팩토링 내성, 빠른 피드백은 상호 배타적이다. 따라서 이 모두를 챙기기는 불가능하다.
•
실제로는 리팩토링 내성이 제일 중요하다. 따라서 리팩토링 내성을 최대한 많이 갖는 것을 목표로 하고 회귀 방지와 빠른 피드백 사이의 선택으로 절충이 귀결된다.
•
리팩토링 내성이 중요한 이유는 테스트가 이 특성을 가지고 있는지 여부가 이진 선택이기 때문이다. 갖고 있거나 없거나 둘 중 하나다.
◦
따라서 리팩토링 내성을 0.5 정도로 인정할 수는 없다. 우리는 회귀 방지와 빠른 피드백 중에 절충을 해야한다.
5. 대중적인 테스트 자동화 개념 살펴보기
1. 테스트 피라미드 분해
•
테스트 피라미드란 테스트 스위트에서 테스트 유형 간의 일정한 비율을 일컫는 개념이다.
•
테스트 피라미드는 세 유형의 테스트가 있는 피라미드로 표현한다. 면적이 넓을수록 해당 테스트가 많다.
◦
층의 높이는 이러한 테스트가 클라이언트의 동작을 얼마나 유사하게 흉내내는지 나타내는 척도다.
◦
E2E 테스트가 가장 위에 있고, 이는 사용자 경험에 가장 가깝게 흉내 내는 것을 의미한다.
•
피라미드 내 테스트 유형에 따라 빠른 피드백과 회귀 방지 사이에서 선택한다.
◦
피라미드 상단의 테스트는 회귀 방지에 유리한 반면, 하단은 빠른 피드백을 강조한다.
•
모든 테스트는 거짓 양성을 가능한 적게 하는 것을 목표로 해야 한다. 심지어 제품 코드를 직접 작업할 때도 그래야 한다.
•
테스트 유형 간의 정확한 비율은 일반적으로 피라미드 형태를 유지해야 한다.
•
즉 E2E 테스트가 가장 적고, 단위 테스트가 가장 많으며, 통합 테스트는 중간 어딘가에 있어야 한다.
◦
E2E 테스트가 적어야 하는 이유는 곱셈 규칙에 있다.
◦
E2E 테스트는 빠른 피드백 지표에서 매우 낮은 점수를 받는다. 또 유지 보수성이 결여돼 있는데, 이는 관련 프로세스 외부 의존성을 유지하는 데 노력을 더 들여야 하기 때문이다.
◦
따라서 E2E 테스트는 가장 중요한 기능에 적용할 때와 단위 테스트나 통합 테스트와 동일한 수준으로 보호할 때만 적용된다.
◦
E2E 테스트를 최소한으로 필요한 가치의 임계치를 넘어서 다른 용도로 사용하면 안 된다. 보통 단위 테스트가 더 안정적이므로 더 많이 있다.
•
테스트 피라미드에는 예외가 있다.
◦
예를 들어 모든 애플리케이션이 복잡도가 거의 없는 CRUD 작업이라면 테스트 피라미드는 단위 테스트와 통합 테스트의 수가 같고 엔드 투 엔드 테스트가 없는 직사각형처럼 보일 것이다.
◦
테스트 피라미드의 또 다른 예외는 프로젝트 외부 의존성 하나만 연결하는 API다.
2. 블랙박스 테스트와 화이트박스 테스트 간의 선택
•
블랙박스 테스트는 시스템의 내부 구조를 몰라도 검사할 수 있는 소프트웨어 테스트 방법이다.
◦
일반적으로 명세와 요구 사항, 즉 애플리케이션이 어떻게 해야 하는지가 아니라 무엇을 해야 하는지를 중심으로 구축된다.
•
화이트박스 테스트는 정반대다. 애플리케이션의 내부 작업을 검증하는 테스트 방식이다.
◦
테스트는 요구 사항이나 명세가 아닌 소스 코드에서 파생된다.
•
일반적으로 화이트박스 테스트가 더 철저한 편이다. 소스 코드를 분석하면 외부 명세에만 의존할 때 놓칠 수 있는 많은 오류를 발견할 수 있다.
•
반면에 화이트박스 테스트는 테스트 대상 코드의 특정 구현과 결합돼 있기 때문에 깨지기 쉽다.
•
이러한 테스트는 거짓 양성을 많이 내고 리팩터링 내성 지표가 부족하다. 블랙박스 테스트는 이와 정반대의 장단점을 제공한다.
회귀 방지 | 리팩터링 내성 | |
화이트박스 테스트 | 좋음 | 나쁨 |
블랙박스 테스트 | 나쁨 | 좋음 |
•
리팩터링 내성은 타협할 수 없으므로 화이트박스 테스트 대신 블랙박스 테스트를 기본으로 선택하라.
◦
모든 테스트가 시스템을 블랙박스로 보게 만들고 문제 영역에 의미 있는 동작을 확인하라.
•
테스트를 통해 비즈니스 요구 사항으로 거슬러 올라갈 수 없다면, 이는 테스트가 깨지기 쉬움을 나타낸다.
◦
이 테스트를 재구성하거나 삭제하라. 기존 테스트 스위트로 두지 말라. 유일한 예외는 알고리즘 복잡도가 높은 유틸리티 코드를 다루는 경우다.
•
테스트를 작성할 때는 블랙박스 테스트가 바람직하지만, 테스트를 분석할 때는 화이트박스 방법을 사용할 수 있다.
◦
코드 커버리지 도구를 사용해서 어떤 코드 분기를 실행하지 않았는지 확인한 다음 코드 내부 구조에 대해 전혀 모르는 것처럼 테스트하라.
•
이러한 화이트박스 방법과 블랙박스 방법의 조합이 가장 효과적이다.
요약
•
좋은 단위 테스트에는 단위 테스트, 통합 테스트, E2E 테스트 등 자동화된 테스트를 분석하는 데 사용할 수 있는 네 가지 기본 특성이 있다.
◦
회귀 방지, 리팩토링 내성, 빠른 피드백, 유지 보수성
◦
회귀 방지는 테스트가 얼마나 버그의 존재를 잘 나타내는지에 대한 척도다. 테스트가 코드를 많이 실행할수록 테스트에서 버그가 드러날 확률이 높아진다.
◦
리팩토링 내성은 테스트가 거짓 양성을 내지 않고 애플리케이션 코드 리팩토링을 유지할 수 있는 정도를 의미한다.
▪
거짓 양성은 허위 경보다. 즉 테스트는 실패했지만 기능은 의도한대로 동작된다. 이는 테스트 스위트에 치명적인 영향을 줄 수 있다.
•
허위 경보에 익숙해지고 주의를 기울이지 않게 되므로 코드 문제에 대응하려는 능력과 의지가 희석된다.
•
테스트를 신뢰할 수 있는 안전망으로 인식하지 않게 되기 때문에 테스트 스위트에 대한 신뢰를 잃게 된다.
▪
거짓 양성은 테스트 대상 시스템의 내부 구현과 테스트 간의 강결합의 결과다. 결합도를 낮추려면 구현이 아닌 결과에 의존해야 한다.
▪
거짓 양성은 프로젝트 초기엔 리팩토링이 적어 그 중요도가 낮으나 프로젝트가 커질수록 더 중요해진다.
◦
회귀 방지와 리팩토링 내성은 테스트 정확도에 기여한다. 테스트는 가능한 적은 거짓 음성과 거짓 양성을 가질 수록 정확하다.
◦
빠른 피드백은 테스트가 얼마나 빨리 실행되는지에 대한 척도다.
◦
유지 보수성은 두 가지 요소로 구성된다.
▪
테스트 이해 난이도, 테스트는 작을수록 읽기 쉽다.
▪
테스트 실행 난이도, 테스트에 관련된 프로세스 외부 의존성은 적을수록 쉽게 실행할 수 있다.
•
테스트의 가치 추정치는 네 특성의 곱이다. 따라서 하나라도 0이면 가치도 0이다.
◦
회귀 방지, 리팩토링 내성, 빠른 피드백은 상호 배타적이기 때문에 하나는 희생시켜야 한다.
◦
리팩토링 내성은 희생시킬 수 없다. 이는 이진 선택, 즉 가지고 있거나 없거나이기 때문이다. 따라서 회귀 방지와 빠른 피드백의 절충으로 귀결된다.
•
테스트 피라미드는 단위 테스트, 통합 테스트, E2E 테스트의 일정한 비율을 일컫는다. E2E가 가장 적고 통합, 단위 순이다.
•
피라미드에서는 테스트 유형마다 빠른 피드백과 회귀 방지 사이에서 선택을 한다. E2E는 회귀 방지를 선호하나 단위 테스트는 빠른 피드백을 선호한다.
•
테스트를 작성할 때는 블랙박스 테스트를 사용하라. 분석할 때는 화이트박스 방법을 사용하라.