1장. 단위 테스트의 목표
단위 테스트 이해
단위 테스트의 목표는 소프트웨어의 지속 가능한 성장을 가능하게 하는 것이다.
- 모든 테스트를 작성할 필요는 없다.
- 일부 테스트는 아주 중요하고 품질에 많은 기여를 한다.
- 테스트의 가치와 유지 비용을 모두 고려해야 한다.
- 기반 코드를 리팩터링할 때 테스트도 리팩터링하라.
- 각 코드 변경시 테스트를 실행하라.
- 테스트가 잘못된 경고를 발생시킬 경우 처리하라.
- 기반 코드가 어떻게 동작하고 이해하려고 할 때는 테스트를 읽는 데 시간을 투자하라.
- 제품 코드 vs 테스트 코드
- 코드는 자산이 아니라 책임이다.
- 코드가 더 많아질수록, 소프트웨어 내의 잠재적인 버그에 노출되는 표면적이 넓어지고 유지비가 증가한다.
- 가능한 한 적은 코드로 문제를 해결하는 것이 좋다.
- 테스트도 역시 코드다. 애플리케이션의 정확성을 보장하는 것을 목표로 하는 코드베이스의 일부로 봐야 한다.
- 다른 코드와 마찬가지로 단위 테스트도 버그에 취약하고 유지보수가 필요하다.
코드 커버리지 지표에 대한 이해
가장 많이 사용되는 커버리지 지표로 코드 커버리지(code coverage)가 있으며 테스트 커버리지(test coverage)로도 알려져 있다. 커버리지 지표는 테스트 스위트가 소스 코드를 얼마나 실행하는지를 백분율로 나타낸다.
코드 커버리지(테스트 커버리지) = 제품 코드 라인 수 / 전체 라인 수
코드가 작을수록 커버리지 지표는 더 좋아지는데, 코드를 더 작게 해도 테스트 스위트의 가치나 기반 코드베이스의 유지 보수성이 변경되지 않는다. (변경해서도 안 된다. 커버리지 지표를 보는 가장 좋은 방법은 지표 그 자체로 보는 것이며, 목표로 여겨서는 안 된다.
무엇이 성공적인 테스트 스위트를 만드는가?
테스트 스위트의 품질을 측정하는 믿을만한 방법은 스위트 내 각 테스트를 하나씩 따로 평가하는 것뿐이다. 테스트 스위트가 얼마나 좋은지 자동으로 확인할 수 없다. 개인 판단에 맡겨야 한다.
성공적인 테스트 스위트는 아래와 같은 특성을 가지고 있다.
- 개발 주기에 통합돼 있다.
- 코드베이스에서 가장 중요한 부분만을 대상으로 한다.
- 최소한의 유지비로 최대의 가치를 끌어낸다.
2장. 단위 테스트란 무엇인가
단위 테스트
- 작은 코드 조각(=단위)을 검증하고,
- 빠르게 수행하고,
- 격리된 방식으로 처리하는 자동화된 테스트다.
통합 테스트
통합테스트는 위 기준 중 하나를 충족하지 않는 테스트다. 예를 들어,
- 공유 의존성에 접근하는 테스트 (다른 테스트와 분리해 실행할 수 없으며, 테스트가 순차적으로 실행된다. 또 프로세스 외부 의존성에 접근하기 때문에 테스트가 느려진다.)
- 둘 이상의 동작 단위를 검증할 때의 테스트
- 다른 팀이 개발한 모듈이 둘 이상 있을 때
통합 테스트는 시스템 전체를 검증해 소프트웨어 품질을 기여하는 데 중요한 역할을 한다.
3장. 단위 테스트 구조
AAA 패턴
AAA 패턴은 각 테스트를 준비, 실행, 검증 3 부분으로 나눈다.
- 준비 구절 Arrange
- 테스트 대상 시스템(SUT)과 해당 의존성을 원하는 상태로 만든다.
- 일반적으로 준비 구절이 세 구절 중 가장 크며, 실행과 검증을 합친 만큼 클 수도 있다.
- 그러나 이보다 훨씬 크면, 같은 테스트 클래스 내 비공개 메서드 또는 별도의 팩토리 클래스로 도출하는 것이 좋다.
- 실행 구절 Act
- SUT에서 메셔드를 호출하고 준비된 의존성을 전달하며 (출력이 있으면) 출력 값을 캡처한다.
- 실행 구절은 보통 코드 한 줄이다.
- 실행 구절이 두 줄 이상인 경우 SUT의 공개 API에 문제가 있을 수 있다.
- 검증 구절 Assert
- 결과를 검증한다.
- 결과는 반환 값이나 SUT와 협력자의 최종 상태, SUT가 협력자에 호출한 메셔드 등으로 표시될 수 있다.
4장. 좋은 단위테스트의 4대 요소
1) 회귀 방지
일반적으로 코드를 수정한 후 기능이 의도한 대로 작동하지 않는 경우를 말한다.
회귀 방지 지표를 평가하기 위한 고려 사항
- 테스트 중에 실행되는 코드의 양
- 코드 복잡도
- 코드의 도메인 유의성
2) 리팩터링 내성
테스트를 실패로 바꾸지 않고 기본 애플리케이션 코드를 리팩터링할 수 있는지에 대한 척도를 말한다.
- 리팩터링 내성은 타협할 수 없다."
3) 빠른 피드백
- 테스트 속도가 빠를수록 더 많이 자주 테스트를 실행 가능
- 피드백 루프가 대폭 줄어 버그를 수정하는 비용을 거의 0까지 줄일 수 있음
4) 유지 보수성
- 테스트가 얼마나 이해하기 어려운가
- 테스트를 가능한 작게 만들어라 → 하지만 테스트 코드의 품질도 중요
- 테스트 코드를 일급 시민으로 취급해라
- 테스트가 얼마나 실행하기 어려운가
- 테스트가 외부 종속성으로 작동하면 의존성을 운영하는데 시간을 들여야한다.
"테스트를 탄탄하게 만들려면 뻥으로 통과한 테스트(false positive)를 제거하는 것이 최우선이다."
5장. 목과 테스트 취약성
테스트 대역(test double)이란?
- 테스트를 진행하기 어려운 경우 이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체 (영화 촬영 시 위험한 역할을 대신하는 스턴트 더블에서 비롯되었다.) 테스트 더블은 크게 Dummy, Stub, Mock, Spy 등으로 나눌 수 있다.
Mock vs Stub
- 목
- 외부로 나가는 상호작용을 모방하고 검사하는데 도움이 된다.
- SUT가 상태를 변경하기 위해 의존성을 호출하는 경우
- 명령을 대체하는 테스트 대역
- 스텁
- 내부로 들어오는 상호작용을 모방하는데 도움이 된다.
- SUT가 입력 데이터를 얻기 위한 의존성을 호출하는 경우
- 조회를 대체하는 테스트 대역
식별할 수 있는 동작 vs 구현 세부 사항
- 이상적으로는, 공개 API와 식별할 수 있는 동작이 일치하고, 모든 구현 세부 사항은 클라이언트의 눈에 보이지 않아야 한다. (이런 설계는 잘된 설계에 해당한다.) 그러나, 종종 공개 API가 구현 세부 사항을 노출하기 시작하게 되고, 이렇게 되면 시스템의 구현 세부 사항은 공개 API를 통해 유출되어버린다.
- 모든 구현 세부사항을 비공개로 하면 테스트가 식별할 수 있는 동작을 검증하는 것 외에는 다른 선택지가 없게 된다. 즉, API를 잘 설계하면 단위 테스트도 자동으로 좋아진다.
출처: 단위 테스트 생산성과 품질을 위한 단위 테스트 원칙과 패턴 | 블라디미르 코리코프 저/임준혁 역 | 에이콘출판사
'General' 카테고리의 다른 글
[클린 코드] 4장 주석 (0) | 2023.05.06 |
---|---|
[클린 코드] 3장 함수 (0) | 2023.04.23 |
[클린 코드] 2장 의미있는 이름 (0) | 2023.04.16 |
[클린 코드] 1장 깨끗한 코드 (1) | 2023.04.16 |
[클린 코드] 서문, 0장 들어가면서 (0) | 2023.04.16 |