////
Search
Duplicate

4. 좋은 단위 테스트의 4대 요소

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는 회귀 방지를 선호하나 단위 테스트는 빠른 피드백을 선호한다.
테스트를 작성할 때는 블랙박스 테스트를 사용하라. 분석할 때는 화이트박스 방법을 사용하라.