티스토리 뷰

테스트

Unit Testing - 1장

perseverance 2024. 8. 12. 12:33

1. 단위 테스트 현황

단위 테스트를 적용해야 하는지는 더 이상 논쟁거리가 아니다. 그냥 쓰고 버리는 프로젝트가 아니면, 단위 테스트는 늘 적용해야 한다.

논쟁은 단위 테스트를 작성해야하는가? 에서 좋은 단위 테스트를 작성하는 것은 어떤 의미인가로 바뀜

2. 단위 테스트의 목표

코드베이스(code base)에 대해 단위 테스트 작성이 필요하면 일반적으로 더 나은 설계로 이어진다. 하지만 단위 테스트의 주목표는 아니다.

그럼 주 목표는?

⇒ 소프트웨어 프로젝트의 지속 가능한 성장을 가능하게 하는 것

테스트가 없는 프로젝트의 경우 시작은 유리하지만, 이내 진척이 없을 정도로 느려진다.

테스트가 없다면?

코드베이스에서 무언가를 변경할 때마다 무질서도(엔트로피)는 증가한다. 하나의 버그를 수정하면 더 많은 버그를 양산하고, 소프트웨어의 한 부분을 수정하면 다른 부분들이 고장 난다. 즉 도미노 현상과 같다. 결국 코드베이스를 신뢰할 수 없게 된다.

테스트가 있다면?

테스트는 안전망 역할을 하며, 대부분의 회귀에 대한 보험을 제공하는 도구라 할 수 있다. 테스트는 기능을 도입하거나 새로운 요구 사항에 더 잘 맞게 리팩터링한 후에도 기존 기능이 잘 작동하는지 확인하는데 도움이 된다.

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

단위 테스트가 프로젝트 성장에 도움이 되는 것은 맞지만, 테스트를 작성하는 것만으로는 충분하지 않다. 잘못 작성한 테스트는 여전히 같은 결과를 낳는다.

모든 테스트를 작성할 필요가 없다. 잘못된 경고가 발생하고, 유지 보수가 어렵고 느리다. 프로젝트에 도움이 되는지 여부를 명확하게 파악하지 않고 단위 테스트를 작성하는 데만 빠져들기 쉽다.

테스트도 역시 코드다. 특정 문제를 해결하는 것, 즉 애플리케이션의 정확성을 보장하는 것을 목표로 하는 코드베이스의 일부로 봐 한다. 다른 코드와 마찬가지로 단위 테스트도 버그에 취약하고 유지 보수가 필요하다.

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

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

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

100% 커버리지라고 해서 반드시 양질의 테스트 스위트라고 보장하지는 않는다. 높은 커버리지의 테스트 스위트도 품질이 떨어질 수 있다.

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

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

public static boolean isStringLong(String input) 
{
  if(inputs.length > 5) 
    return true;
  return false;
}

public void test() 
{
  boolean result = isStringLong("abc");
  Assert.Equal(false, result);
}
public static boolean isStringLong(String input) {
  return inputs.length > 5;
}

public void test() {
  boolean result = isStringLong("abc");
  Assert.Equal(false, result);
}

제품 코드베이스의 전체 라인수가 줄어들면 동일한 테스트에 대해 코드 커버리지 지표는 더 좋아지는데 이는 원래 라인 수만 처리하기 때문이다. 그리고 코드를 더 작게 해도 테스트 스위트의 가치나 기반 코드베이스의 유지 보수성이 변경되지 않는다.

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

또 다른 커버리지 지표는 분기 커버리지다. 분기 커버리지는 코드 커버리지의 단점을 극복하는 데 도움이 되므로 코드 커버리지보다 더 정확한 결과를 제공한다.

분기 커버리지 지표는 원시 코드 라인 수를 사용하는 대신 if문과 switch 문과 같은 제어 구조에 중점을 둔다.

public static boolean isStringLong(String input) {
  return inputs.length > 5;
}

public void test() {
  boolean result = isStringLong("abc");
  Assert.Equal(false, result);
}

IsStringLong 메서드는 두개의 분기가 있다.

  1. 문자열 인수의 길이가 다섯 자를 초과하는 상황
  2. 문자열 인수의 길이가 다섯자 이하인 상황

테스트는 이런 분기 중 하나에 대해서만 적용되므로 분기 커버리지 지표는 1/2 = 0.5 = 50%이다.

3.3 커버리지 지표의 문제점

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

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

모든 가능한 결과를 검증한다고 보장할 수 없다

public static bool WasLastString {get; private set;}

public static bool IsStringLong(String input)
{
  bool result = inputs.length > 5;
  WasLastString = result;
  return result;
}

public void test() {
  boolean result = isStringLong("abc");
  Assert.Equal(false, result);
}

IsStringLong 메서드에는 값을 반환하는 명시적인 결과와 WasLastString에 새로운 값을 쓰는 암묵적인 결과가 있다. 그리고 두 번째 암묵적인 결과를 검증하지 않더라도 커버리지 지표는 100% 코드 커버리지와 50% 분기 커버리지의 동일한 결과를 보여준다.

보시다시피 커버리지 지표는 기반 코드를 테스트했다고 보장할 수 없으며 일부 실행된 것만 보장한다.

외부 라이브러리의 코드 경로를 고려할 수 있는 커버리지 지표는 없다.

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

public static int Parse(String input) {
  return int.Parse(input);
}

public void Test() {
  int result = Parse("5");
  Assert.Equal(5, result);
}

위 예시에서 .NET 프레임워크의 int.Parse 메서드가 수행하는 코드 경로는 고려하지 않았다.

  • input이 null, 빈 문자열, “정수가 아님”, 너무 긴 문자열 인 경우를 고려하지 않음

수많은 예외 상황에 빠질 수 있지만, 테스트에서 모든 예외 상황을 다루는지 확인할 방법이 없다.

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

커버리지 지표로 테스트가 철저한지 또는 테스트가 충분한지 알 수는 없다.

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

커버리지 지표를 100%, 90%, 등등 특정 커버리지 숫자를 목표로 삼기 시작하면 위험 영역으로 이어질 수 있다. 커버리지 지표를 보는 가장 좋은 방법은 지표 그 자체로 보는 것이며, 목표로 여겨서는 안된다.

특정 커버리지 숫자를 목표로 하는 것은 단위 테스트의 목표와 반대되는 그릇된 동기 부여가 된다. 사람들은 중요한 것을 테스트하는 데 집중하는 대신 인공적인 목표를 달성하기 위한 방법을 찾기 시작한다

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

믿을 만한 방법은 스위트 내 각 테스트를 하나씩 따로 평가하는 것뿐이다. 물론 한 번에 모든 것을 평가할 필요는 없다. 꽤 큰 작업이 될 수 있고, 이에 앞서 노력을 상당히 들여야 할 수도 있다.

요점은 테스트 스위트가 얼마나 좋은지 자동으로 확인할 수 없다는 것이다. 개인 판단에 맡겨야 한다.

성공적인 테스트 스위트는 다음과 같은 특성을 갖고 있다.

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

4.1 개발 주기에 통합돼 있음

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

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

시스템의 가장 중요한 부분에 단위 테스트 노력을 기울이고, 다른 부분은 간략하게 또는 간접적으로 검증하는 것이 좋다. 대부분의 애플리케이션에서 가장 중요한 부분은 비즈니스 로직이 있는 부분이다.

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

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

그러나 이 중 일부는 단위 테스트를 철저히 해야 할 수 있다.

  • 예) 인프라 코드에 복잡하고 중요한 알고리즘이 있을 경우 등등

그러나 일반적으로 도메인 모델에 관심을 더 많이 갖는 것이 옳다.

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

가치가 유지비를 상회하는 테스트만 스위트에 유지하는 것이 중요하다. 이것은 두 가지로 나눌 수 있다.

  • 가치 있는 테스트 식별하기
  • 가치 있는 테스트 작성하기

가치가 높은 테스트를 식별하려면 기준틀이 필요하다. 반면에 가치 있는 테스트를 작성하려면 코드 설계 기술도 알아야 한다. 단위 테스트와 기반 코드는 서로 얽혀 있으므로 코드베이스에 노력을 많이 기울이지 않으면 가치 있는 테스트를 만들 수 없다.

'테스트' 카테고리의 다른 글

Unit Testing - 7장  (1) 2024.09.17
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함