선생님, 개발을 잘하고 싶어요.

단위 테스트: 생산성과 품질을 위한 단위 테스트 원칙과 패턴 - 4장 - 좋은 단위 테스트의 4대 요소 본문

일상/책 리뷰

단위 테스트: 생산성과 품질을 위한 단위 테스트 원칙과 패턴 - 4장 - 좋은 단위 테스트의 4대 요소

알고싶은 승민 2022. 7. 31. 11:38

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

  • 다루는 내용
    • 좋은 단위 테스트의 관점 간 차이점 모색
    • 이상적인 테스트 정의
    • 테스트 피라미드 이해
    • 블랙박스 테스트 및 화이트박스 테스트 사용
  • 가치 있는 테스트를 작성하기 위해서는 가치 있는 테스트를 식별할 줄 알아야 한다. 이 장에선 그 방법을 알아본다.

4.1 좋은 단위 테스트의 4대 요소 자세히 살펴보기

  • 총 네 가지 특성이 있다.
    • 회귀 방지
    • 리팩터링 내성
    • 빠른 피드백
    • 유지 보수성

4.1.1 첫 번째 요소: 회귀 방지

  • 회귀: 코드를 수정한 후 기능이 의도한 대로 작동하지 않는 경우(소프트웨어 버그)
  • 회귀 방지 차원에서 가치있는 테스트란 다음 사항을 고려해야 한다.
    • 테스트 중에 실행되는 코드의 양 (많을 수록 회귀 방지 가능성 ⤴️)
    • 코드 복잡도 (단순한 코드는 실수할 여지가 많지 않아 회귀 오류가 발생하지 않으니 필요 없다)
    • 코드의 도메인 유의성 (비즈니스에 중요한 기능에 발생한 버그의 비용이 가장 크기 때문에 이를 회귀 방지하면 가치가 높다)

4.1.2 두 번째 요소: 리팩터링 내성

  • 리팩터링: 식별할 수 있는 동작을 수정하지 않고 기존 코드를 변경하는 것
  • 리팩터링 이후 기능에 버그는 없었지만 테스트가 빨간색인 경우 → 거짓 양성 발생 (false positive)
  • 거짓 양성: 허위 경보, 기능은 동작하지만 테스트는 실패를 나타냄
  • 리팩터링 내성 차원에서 고려 사항은 다음과 같다
    • 거짓 양성의 수 (적을 수록 좋다)
  • 거짓 양성이 많아지면 유지가능한 테스트를 방해한다.
    • 테스트가 타당한 이유 없이 실패하면, 테스트 실패를 무시하게 된다. (양치기 소년)
    • 테스트 스위트에 대한 신뢰가 떨어진다. 결국 리팩터링이 줄어든다.

4.1.3 무엇이 거짓 양성의 원인인가?

  • 테스트 대상 시스템의 구현 세부 사항이 많이 결합할수록 허위 경보 ⤴️
  • 구현 세부 사항에서 테스트를 분리할 것
  • 테스트는 최종 사용자 관점에서 SUT를 검증하고, 최종 사용자에게 의미 있는 결과만 확인해야 한다.
  • 식별 가능한 동작에 영향 없이 구현 세부 사항을 변경하는데, 그 때마다 빨간색으로 변한다면? 테스트가 구현 세부 사항에 관계돼 있는 것이다.
  • 🌟 SUT의 구현 세부 사항과 결합된 테스트는 리팩터링 내성이 없다.

4.1.4 구현 세부 사항 대신 최종 결과를 목표로 하기

  • 코드의 내부 작업과 테스트 사이를 가능한 멀리 떨어뜨리고 최종 결과를 목표로 하기

4.2 첫 번째 특성과 두 번째 특성 간의 본질적인 관계

4.2.1 테스트 정확도 극대화

  • 기능은 작동하는데 테스트는 실패할 경우 - 1종 오류 - 거짓 양성 (false positive)
    • 리팩터링 내성 극대화하기
  • 기능은 고장났는데 테스트는 통과할 경우 - 2종 오류 - 거짓 음성 (false negative)
    • 회귀 방지 극대화하기
  • 테스트 정확도는 (신호 / 소음)으로 표현할 수 있다.
    • 신호 → 발견된 버그 수
    • 소음 → 허위 경보 발생 수
  • 🌟 이런 관점에서 테스트의 정확도를 높이려면?
    • 회귀를 더 잘 찾아내는 테스트로 개선 (분자 증가)
    • 허위 경보를 발생시키지 않는 테스트로 개선 (분모 감소)

4.2.2 거짓 양성과 거짓 음성의 중요성: 역학 관계

  • 단기적으로는 거짓 양성도 거짓 음성만큼 나쁘지 않다.
  • 거짓 양성은 리팩터링이 필요한 시점, 즉 프로젝트가 충분히 성숙한 이후부터 매우 중요해진다.
  • 비용을 줄이기 위해서 정기적으로 리팩터링 해야한다.
  • 🤔 작고, 오래가지 않을 프로젝트는 회귀 방지가 더 중요하다는 것으로 들린다?

4.3 세 번째 요소와 네 번째 요소: 빠른 피드백과 유지 보수성

  • 빠른 피드백은 단위 테스트의 필수 덕목
    • 느린 테스트는 피드백을 느리게 하고, 잠재적으로 버그를 뒤늦게 눈에 띄게 해 버그 수정 비용 증가함
  • 유지 보수성 지표는 테스트 유지비를 평가
    • 테스트가 얼마나 이해하기 어려운가?
      • 테스트 코드 라인이 적을수록 읽기 쉽다.
      • 테스트 코드를 일급 시민으로 취급하라.
    • 테스트가 얼마나 실행하기 어려운가?
      • 외부 족속성으로 작동하나?

4.4 이상적인 테스트를 찾아서

  • 좋은 단위 테스트의 4대 특성
    • 회귀 방지
    • 리팩터링 내성
    • 빠른 피드백
    • 유지 보수성
  • 🌟 이 네 가지 특성을 곱하여 테스트의 가치를 결정하자.
  • 어떤 특성이라도 0이라면, 그 테스트 가치는 0이다.
  • 🌟 소수의 매우 가치 있는 테스트는 다수의 평범한 테스트보다 프로젝트가 계속 성장하는 데 훨씬 더 효과적이다.

4.4.1 이상적인 테스트를 만들 수 있는가?

  • 다만… 회귀 방지, 리팩터링 내성, 빠른 피드백은 상호 배타적이다. 모두를 최대로 하는 건 불가능하다.

4.4.2 극단적인 사례 1: 엔드 투 엔드 테스트

  • 많은 코드를 테스트하므로 회귀 방지 훌륭
  • 거짓 양성에 면역돼 리팩터링 내성 우수
  • 하지만 느린 속도로 인해 단위 테스트 가치 0

4.4.3 극단적인 사례 2: 간단한 테스트

  • 코드가 간단하니 거짓 양성이 생길 여지가 낮으니 리팩터링 내성 우수
  • 매우 빠르게 실행되므로 빠른 피드백 우수
  • 하지만 기반 코드에 실수할 여지가 많지 않기 때문에 회귀 자체를 나타내지 않을 코드, 즉 회귀 방지가 없다.

4.4.4 극단적인 사례 3: 깨지기 쉬운 테스트

  • 내부 구현을 그대로 빼다박은 테스트
  • 회귀 방지? 가능하다
  • 빠른 실행 당연히 가능
  • 하지만, 내부 구현 사항을 바꾸는 것으로 테스트는 깨진다. 리팩터링 내성이 좋지 않다.

4.4.5 이상적인 테스트를 찾아서: 결론

  • 모두 완벽한 점수를 가진 이상적 테스트 만드는 것은 불가능하다.
  • 🌟 리팩터링 내성은 포기할 수 없다. 따라서 회귀 방지와 빠른 피드백 사이에서 적절히 타협해야 한다.
    • 리팩터링 내성은 좋거나 나쁘거나 둘 중 하나이기 때문이다.
  • 🌟 테스트 스위트를 탄탄하게 만들기 위해, 테스트의 불안정성(거짓 양성)을 제거하는 것이 최우선이다.

4.5 대중적인 테스트 자동화 개념 살펴보기

4.5.1 테스트 피라미드 분해

  • 단위 테스트: 회귀 방지는 어렵지만 빠른 피드백 가능하다. → 실행 속도 강조
  • 엔드 투 엔드 테스트 (통합 테스트): 회귀 방지는 유리하지만 피드백이 느리다. → 회귀 방지 강조
  • 종종 피라미드 형태로 표현된다. 단위 테스트가 많고 통합 테스트가 그에 비해 적은 형태
  • 🌟 어떤 계층도 리팩터링 내성은 포기하지 않는다.
  • 단순한 CRUD의 경우, 단위 테스트의 빠른 실행은 사실상 중요하지 않다. 반면 통합 테스트는 다른 하위 시스템과 잘 통합되는 지 확인하므로 중요하다. (이 경우, 피라미드 형태가 유지될 필요가 없다)

4.5.2 블랙박스 테스트와 화이트박스 테스트 간의 선택

  • 블랙박스 테스트: 시스템 내부 구조를 몰라도 시스템 기능 검사할 수 있는 테스트 방법
    • 애플리케이션이 무엇을 해야 하는지를 중심으로 구축
  • 화이트박스 테스트: 내부 작업을 검증, 소스 코드에서 파생
  • 화이트박스 테스트는 테스트 대상 코드의 특정 구현과 결합돼 깨지기 쉽다.
  • 🌟 리팩터링 내성은 타협할 수 없으므로, 블랙박스 테스트를 기본으로 선택하자.
  • 항상 예외가 있으니 … 복잡도가 높은 유틸리티 코드를 다루는 경우는 깨지기 쉽지만 테스트 스위트로 두자

4장 후기

좋은 테스트를 작성하고 싶어서 온라인 리소스를 찾아보거나 간단한 케이스 훈련을 하다보면 참 이해 안되는 부분이 많았다. 단순한 CRUD를 테스트 해서 단위 테스트의 코드와 통합 테스트의 코드가 완전 중복 처럼 보이는 경우도 있었다.

하지만 무엇이 좋은 테스트인지 모르는 입장에서 그런 불편한 테스트가 안좋은 명확한 이유를 제시하기 어려웠다. 이번 장에서는 나에게 무엇이 좋은 테스트고 나쁜 테스트인지 토론할 수 있는 기본 베이스를 깔아주었다고 생각한다.

리팩터링 내성의 측면에서 보면 설명되는 게 참 많은 것 같다. 단순히 구현을 검증하는 테스트의 불편함을 더 예리하게 파악해서 토론할 수 있을 것 같다.

또한 직접 프로젝트 테스트 코드를 작성할 때 과연 내가 작성하는 이 코드가 관습적으로, 그래야 한다길래 작성하는 것이 아닌 직접 가치를 평가하고 테스트 할지 말지 나만의 논리를 구축하는데 도움을 줄 것 같다.

명쾌하다 !

Comments