개요
좋은 테스트 스위트는 단순히 많은 테스트를 보유한 것이 아니라, 실제로 가치 있는 테스트를 담고 있어야 한다. 좋은 테스트는 개발 주기에 통합되어야 하며, 코드베이스의 중요한 부분을 다루고, 유지비가 낮고, 최대한의 가치를 끌어낼 수 있어야 한다.
좋은 단위 테스트의 네 가지 핵심 요소
회귀 방지
회귀(regression)란 기존에 잘 동작하던 기능이 이후의 코드 변경으로 인해 오류를 일으키는 현상이다.- 테스트는 가능한 많은 코드를 실행함으로써 회귀 가능성을 줄여야 한다.
- 회귀 방지 지표가 높을수록 테스트가 버그를 잘 검출해낸다.
리팩터링 내성
- 애플리케이션 코드를 리팩터링해도 테스트가 실패하지 않아야 한다.
- 테스트가 구현 세부사항에 의존하지 않고 최종 결과를 검증하면 리팩터링 내성이 향상된다.
거짓 양성(false positive)은 테스트가 실패했지만 실제로는 기능이 정상 작동하는 경우이며, 테스트 신뢰도를 저하시킨다.- 테스트와 SUT 간 결합도를 줄이는 것이 핵심이다.
오류 유형표
| 오류 유형표 | 작동 | 고장 |
|---|---|---|
| 테스트 통과 | 참 음성 (정상) | 거짓 음성 (2종 오류, 회귀 누락) |
| 테스트 실패 | 거짓 양성 (1종 오류) | 참 양성 (정상 오류 감지) |
빠른 피드백
- 테스트가 빠르게 실행될수록 더 자주 실행할 수 있고 개발 속도도 향상된다.
- 빠른 피드백은 단위 테스트에서 특히 중요하다.
유지 보수성
- 테스트가 얼마나 이해하기 쉬운지, 실행하기 쉬운지를 평가한다.
- 작고 명확한 테스트일수록 유지 관리가 용이하다.
- 외부 의존성이 적을수록 테스트를 실행하기 쉽다.
상호 관계 및 테스트 가치
- 네 가지 특성 중 하나라도 점수가
0이면 전체 테스트의 가치는0이다. - 회귀 방지, 리팩터링 내성, 빠른 피드백은 서로 상충한다. 모든 특성에서 최고 점수를 얻는 것은 불가능하다.
- 그러나
리팩터링 내성은 타협할 수 없는 속성이다. 이는 테스트 유지 관리와 신뢰성의 기반이 된다.
테스트 피라미드와 속성 우선순위
단위 테스트: 빠른 피드백에 초점을 맞춘다.통합 테스트: 균형 잡힌 피드백과 회귀 방지를 제공한다.엔드 투 엔드 테스트: 회귀 방지를 중시한다.
테스트 분석과 작성 방식
- 테스트를 작성할 때는
블랙박스 테스트방식(결과 위주)을 적용한다. - 테스트를 분석할 때는
화이트박스 테스트방식(구현 내부 확인)을 활용해 품질을 평가한다.
정리
좋은 테스트는 다음 조건을 충족해야 한다.
- 회귀를 잘 방지하고
- 리팩터링에도 견디며
- 빠르게 피드백을 제공하고
- 유지 관리가 쉬워야 한다
이 네 가지 요소는 함께 곱해져 테스트의 전체적인 가치를 구성하므로, 하나라도 놓치면 테스트의 효용은 급격히 떨어진다.
용어 정의
회귀(regression)
이전에 정상적으로 작동하던 기능이 새로운 코드 변경으로 인해 다시 오류를 발생시키는 현상이다. 주로 리팩터링, 기능 추가, 라이브러리 업데이트 등에서 발생한다. 테스트를 통해 회귀를 조기에 감지할 수 있다.
리팩터링 내성(refactoring resistance)
코드의 동작을 바꾸지 않고 구조만 변경했을 때 테스트가 실패하지 않는 성질이다. 이는 테스트가 내부 구현이 아니라 외부 동작에만 의존해야 함을 의미한다. 리팩터링 내성이 높을수록 테스트의 유지 관리 비용이 낮아진다.
거짓 양성(false positive)
테스트가 실패했지만 실제로는 애플리케이션 동작에 문제가 없는 경우를 의미한다. 테스트 신뢰도를 저하시켜 개발자가 테스트 결과를 무시하게 만들 수 있다. 일반적으로 테스트와 코드의 구현 세부사항이 과도하게 결합되었을 때 발생한다.
거짓 음성(false negative)
테스트는 통과했지만 실제로는 코드에 버그가 존재하는 상태를 의미한다. 이는 회귀를 놓치는 결과로 이어질 수 있다. 테스트의 회귀 방지력이 낮다는 신호이다.
블랙박스 테스트
시스템의 내부 구조나 구현을 고려하지 않고 입력과 출력만으로 검증하는 테스트 방식이다. 사용자 관점에서 테스트를 설계하며 기능 요구 사항 충족 여부를 확인하는 데 유리하다.
화이트박스 테스트
코드의 내부 구조, 논리, 흐름 등을 분석하여 테스트 케이스를 설계하는 방식이다. 조건 분기, 루프 등을 기준으로 테스트 범위를 결정할 수 있다. 테스트 커버리지를 높이는 데 적합하다.