1. 단위 테스트의 목표

Bard·2022년 3월 12일
28

Unit Testing

목록 보기
1/3
post-thumbnail

이 책에서 다루는 내용은 어떤 단위 테스트 기술이 좋은지를 구별하는 데 도움이 될 것이다.

  • 테스트에 대한 비용 편익 분석 (cost-benefit analysis)
  • 공통적인 안티 패턴을 피하는 방법

1.1 단위 테스트 현황

단위 테스트를 작성해야 하는가?

단위테스트는 이젠 대부분의 회사에서 필수로 간주될 정도로 당연한 것이다.

단위 테스트를 적용해야 하는 지는 더 이상의 논쟁거리가 아니다. 한번 작성하고 버리는 프로젝트가 아니라면 단위 테스트는 늘 적용해야 한다.

논쟁은 다음과 같이 바뀌었다.

좋은 단위 테스트를 작성하는 것은 어떤 의미인가?

좋은 테스트와 좋지 않은 테스트의 차이는 취향이나 개인적인 선호도의 문제가 아니라 현재 작업중인 중대한 프로젝트의 성패를 가르는 문제다.

이 책에서는 이상적인 단위 테스트에 대해 정확하고 과학적인 정의를 다룬다. 이 정의가 실제 사례에서 어떻게 적용되는 지 살펴본다.

1.2 단위 테스트의 목표

흔히 단위 테스트를 작성하는 것이 더 나은 설계로 이어진다고 한다.

이는 사실이다. 그러나 단위 테스트의 주목표는 아니다. 더 나은 설계는 단지 좋은 부수 효과일 뿐이다.

코드를 단위 테스트하기 어렵다는 것은 비교적 높은 정확도로 저품질 코드를 가려낸다. 코드를 단위테스트하기 어렵다면 코드 개선이 반드시 필요하다는 것이다.대표적인 예로 강한 결합 (tight coupling)이 있을 것이다.

단위 테스트의 목표는 소프트웨어 프로젝트의 지속 가능한 성장을 가능하게 하는 것이다. 지속 가능하다는 것이 핵심이다.

테스트가 없는 코드는 처음에는 발목을 잡을 것이 없으므로 빨리 시작할 수 있다. 그러나 시간이 지나면서 점점 더 많은 시간을 들여야 처음에 보여준 것과 같은 정도의 진척을 낼 수 있다. 결국 개발 속도가 현저히 느려진다.

개발속도가 빠르게 감소하는 현상을 소프트웨어 엔트로피라고도 한다.

소프트웨어에서 엔트로피는 품질을 떨어뜨리는 코드 형태로 나타난다. 코드베이스에서 무언가를 변경할 때마다 엔트로피는 증가한다.

지속적인 정리와 리팩터링 등과 같은 적절한 관리를 하지 않고 방치하면 시스템이 점점 더 복잡해지고 무질서해진다.

테스트는 이러한 경향을 뒤집는 역할을 한다.

테스트는 안전망 역할을 하며, 대부분의 회귀에 대한 보험을 제공한다.

여기서 한가지 단점이 있는데, 이러한 테스트는 초반에 노력이 필요하다는 것이다.

그러나 장기적으로 보면 그 비용을 메울 수 있다.

1.2.1 좋은 테스트와 좋지 않은 테스트를 가르는 요인

일부 테스트는 아주 중요하고 소프트웨어 품질에 매우 많은 기여를 한다. 그 밖에 다른 테스트는 그렇지 않다.

프로젝트에 테스트를 더 많이 실행하더라도 단위 테스트의 목표를 달성할 수는 없다. 테스트의 가치와 유지 비용을 모두 고려해야 한다.

여기서 비용은 다음과 같은 활동에 드는 시간에 따라 결정된다.

  • 기반 코드를 리팩터링할 떄, 테스트도 리팩터링하라.
  • 각 코드 변경시 테스트를 실행하라.
  • 테스트가 잘못된 경고를 발생시킬 경우 처리하라.
  • 기반 코드가 어떻게 동작하는지 이해하려고 할 때는 테스트를 읽는 데 시간을 투자하라.

이런 높은 유지보수 비용으로 인해 테스트의 순 가치가 0에 가깝거나 심지어 0보다 작은 테스트를 만들기 쉽다.

지속 가능한 프로젝트 성장을 위해서는 고품질 테스트에만 집중해야 한다.

1.3 테스트 스위트 품질 측정을 위한 커버리지 지표

커버리지 지표는 테스트 스위트가 소스 코드를 얼마나 실행하는지를 백분율로 나타낸다.

커버리지 지표는 중요한 피드백을 주더라도 테스트 스위트의 품질을 효과적으로 측정하는 데 사용될 수 없다.

코드를 단위 테스트하는 것과 같은 상황이다. 즉, 커버리지 지표는 괜찮은 부정 지표지만 좋지 않은 긍정 지표다.

1.3.1 코드 커버리지 지표에 대한 이해

가장 많이 사용되는 커버리지 지표로 코드 커버리지가 있으며, 테스트 커버리지로도 알려져 있다.

이 지표는 하나 이상의 테스트로 실행된 라인 수와 제품 코드베이스의 전체 라인 수의 비율을 나타낸다.

코드 커버리지 = 테스트 스위트가 실행한 코드 라인 수 / 전체 라인 수

ex) 4/5 = 80%

public static bool IsStringLong(string input)
{                         //테스트가 다루는 영역
  if (input.length > 5)   //테스트가 다루는 영역
    return true;          
  return false;           //테스트가 다루는 영역
}                         //테스트가 다루는 영역

public void Test()
{
  bool result = IsStringLong("abc");
  Assert.Equal(false, result);
}

ex) 3/3 = 100%

public static bool IsStringLong(string input)
{                           //테스트가 다루는 영역
  return input.length > 5;  //테스트가 다루는 영역
}                           //테스트가 다루는 영역

public void Test()
{
  bool result = IsStringLong("abc");
  Assert.Equal(false, result);
}

1.3.2 분기 커버리지 지표에 대한 이해

분기 커버리지는 코드 커버리지의 단점을 극복하는 데 도움이 되므로 코드 커버리지보다 더 정확한 결과를 제공한다. 분기 커버리지 지표는 if문과 switch문과 같은 제어 구조에 중점을 둔다.

분기 커버리지 = 통과 분기 / 전체 분기 수

ex) 1/2 = 50%

public static bool IsStringLong(string input)
{                           
  return input.length > 5;  //참, 거짓 중 거짓만 검사함.
}                           

public void Test()
{
  bool result = IsStringLong("abc");
  Assert.Equal(false, result);
}

1.3.3 커버리지 지표에 관한 문제점

분기 커버리지로 코드 커버리지보다 더 나은 결과를 얻을 수 있지만, 테스트 스위트의 품질을 결정하는 데 어떤 커버리지 지표도 믿을 수 없는 이유는 다음과 같다.

  • 테스트 대상 시스템의 모든 가능한 결과를 검증한다고 보장할 수 없다.
  • 외부 라이브러리의 코드 경로를 고려할 수 있는 커버리지 지표는 없다.

다음은 검증이 없는 테스트를 보여주는 다소 극단적인 예제다.

public void Test()
{
  bool result1 = IsStringLong("abc");
  bool result2 = IsStringLong("abcdef");
}

이 테스트에서는 코드 커버리지와 분기 커버리지가 둘 다 100%이다. 그러나 아무것도 검증하지 않기 때문에 전혀 쓸모가 없다.

만약 각각의 코드에 대해 철저히 검증한다고 하면 신뢰할 수 있을까?

아니다. 두 번째 문제는 모든 커버리지 지표가 테스트 대상 시스템이 메서드를 호출할 때 외부 라이브러리가 통과하는 코드 경로를 고려하지 않는다는 것이다.

이는 커버리지 지표가 외부 라이브러리의 코드 경로를 고려해야 한다는 것이 아니라, 해당 지표로는 단위 테스트가 얼마나 좋은지 나쁜지를 판단할 수 없다는 것을 보여준다. 커버리지 지표로 테스트가 철저한지 또는 테스트가 충분한지 알 수는 없다.

1.3.4 특정 커버리지 숫자를 목표로 하기

테스트 스위트 품질을 결정하기에 커버리지 지표만으로는 충분하지 않다는 것을 깨달았을 것이다.

특정 커버리지 숫자를 목표로 삼기 시작하면 위험 영역으로 이어질 수 있다.

커버리지 지표를 보는 가장 좋은 방법은 지표 그 자체로 보는 것이며, 목표로 여겨서는 안 된다.

시스템의 핵심 부분은 커버리지를 높게 두는 것이 좋다. 하지만 이 높은 수준을 요구 사항으로 삼는 것은 좋지 않다. 그 차이는 미미하지만 매우 중요하다.

1.4 무엇이 성공적인 테스트 스위트를 만드는가?

전체적으로 어떻게 테스트 스위트를 성공할 수 있는지 더 넓게 살펴보자.
성공적인 테스트 스위트는 다음과 같은 특성을 갖고 있다.

  • 개발 주기에 통합돼 있다.
  • 코드베이스에서 가장 중요한 부분만을 대상으로 한다.
  • 최소한의 유지비로 최대의 가치를 끌어낸다.

1.4.1 개발 주기에 통합돼 있음

자동화된 테스트를 할 수 있는 방법은 끊임없이 하는 것 뿐이다. 모든 테스트는 개발 주기에 통합돼야 한다. 이상적으로 코드가 변경될 때마다 아무리 작은 것이라도 실행해야 한다.

1.4.2 코드베이스에서 가장 중요한 부분만을 대상으로 함

시스템의 가장 중요한 부분에 단위 테스트 노력을 기울이고, 다른 부분은 간략하게 또는 간접적으로 검증하는 것이 좋다.

대부분의 애플리케이션에 가장 중요한 부분은 비즈니스 로직(도메인 모델)이 있는 부분이다. 비즈니스 로직 테스트가 시간 투자 대비 최고의 수익을 낼 수 있다.

다른 모든 부분은 세 가지로 나눌 수 있다.

  • 인프라 코드
  • 데이터베이스나 서드파티 시스템과 같은 외부 서비스 및 종속성
  • 모든 것을 하나로 묶는 코드

이 중 일부는 단위 테스트를 철저히 해야 할 수 있다. 예를 들어 인프라 코드에 복잡하고 중요한 알고리즘이 있을 수 있으므로, 테스트를 많이 하는 것이 좋다. 그러나 일반적으로 도메인 모델에 관심을 더 많이 갖는 것이 옳다.

1.4.3 최소 유지비로 최대 가치를 끌어냄

단위 테스트에서 가장 어려운 부분은 최소 유지비로 최대 가치를 달성하는 것이다. 이것이 이 책에서 말하려는 핵심이다.

테스트를 빌드 시스템에 통합하는 것만으로는 충분하지 않으며, 도메인 모델에 높은 테스트 커버리지를 유지하는 것도 충분하지 않다.

또한 가치가 유지비를 상회하는 테스트만 스위트에 유지하는 것이 중요하다.

이 속성은 두 가지로 나눌 수 있다.

  • 가치 있는 테스트 (더 나아가, 가치가 낮은 테스트) 식별하기
  • 가치 있는 테스트 작성하기

1.5 이 책을 통해 배우는 것

이 책을 통해 스위트 내의 모든 테스트를 분석하는 데 사용할 수 있는 기준들을 설명한다. 이 기준들이 기초다.

그러고 나서 새로운 관점에서 많은 테스트를 볼 수 있으며, 어떤 것이 프로젝트에 기여하고 어떤 것을 리팩터링해야 하거나 완전히 제거해야 하는 지 알 수 있을 것이다.

profile
The Wandering Caretaker

3개의 댓글

comment-user-thumbnail
2022년 3월 12일

ㄷ ㄷ

답글 달기
comment-user-thumbnail
2022년 3월 15일

와 너무 좋은 글이에요~ 커버리지 마스터 할 수 있을거같아요

1개의 답글