단위 테스트 케이스 설계 방법론

단위 테스트 케이스를 설계할 때 고려해야 할 두 가지 주요 접근 방식은 다음과 같습니다:
  • 파라미터 기반 접근법
  • 실행 경로 기반 접근법
다음은 C#으로 작성된 예제 코드를 통해 각각의 접근법을 설명합니다. 단위 테스트 라이브러리로는 Fluent Assertions를 사용하겠습니다. 테스트 대상 메서드는 다음과 같습니다:
public static bool ContainsNamelessItems(this List<Item> items)
{
    return items.Any(item => item.Name.IsNullOrEmpty());
}
이 메서드는 항목 리스트를 입력으로 받아 각 항목의 이름 속성이 비어 있는지 확인합니다. 이름이 존재하고 비어 있지 않으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

파라미터 기반 접근법으로 테스트 케이스 구성하기

이 접근법은 메서드에 전달될 수 있는 다양한 입력 값들을 고려하는 것입니다. 입력 파라미터인 items는 다음과 같은 가능한 상태를 가질 수 있습니다:
  • 빈 리스트
  • 하나 이상의 이름이 없는 항목 포함
  • 모든 항목이 이름을 갖는 경우
  • null 값
각각의 경우는 독립적인 테스트 케이스로 정의할 수 있습니다. 다음은 각각의 시나리오에 대한 테스트 코드입니다:
  1. 빈 리스트인 경우, 결과는 false여야 합니다.
public void WhenItemListIsEmpty_ShouldReturnFalse()
{
    var items = new List<Item>();

    var result = items.ContainsNamelessItems();

    result.Should().BeFalse("because an empty list cannot have nameless items");
}
  1. 이름이 없는 항목이 하나라도 포함된 리스트인 경우, 결과는 true여야 합니다.
public void WhenListContainsNamelessItem_ShouldReturnTrue()
{
    var items = new List<Item>
    {
        new Item { Name = "Item1" },
        new Item { Name = string.Empty } // nameless item
    };

    var result = items.ContainsNamelessItems();

    result.Should().BeTrue("because there is at least one nameless item");
}
  1. 모든 항목이 유효한 이름을 갖는 경우, 결과는 false여야 합니다.
public void WhenAllItemsHaveNames_ShouldReturnFalse()
{
    var items = new List<Item>
    {
        new Item { Name = "Item1" },
        new Item { Name = "Item2" }
    };

    var result = items.ContainsNamelessItems();

    result.Should().BeFalse("because all items have valid names");
}
  1. null 리스트인 경우, ArgumentNullException이 발생해야 합니다.
public void WhenItemListIsNull_ShouldThrowArgumentNullException()
{
    List<Item> items = null;

    Action act = () => items.ContainsNamelessItems();

    act.Should().Throw<ArgumentNullException>("because the input list is null");
}

실행 경로 기반 접근법으로 테스트 케이스 구성하기

이 방법은 메서드 내부에서 실행되는 모든 가능한 경로를 분석하여 테스트 케이스를 설계하는 것입니다. 기존 메서드는 단일 실행 경로만 가지고 있지만, 조건문을 추가하면 여러 경로가 생성됩니다. 예를 들어 null 체크를 추가해보겠습니다. 새로운 메서드 구현은 다음과 같습니다:
public static bool ContainsNamelessItems(List<Item> items)
{
    if (items == null)
        throw new ArgumentException("Items collection cannot be null.");

    return items.Any(item => item.Name.IsNullOrEmpty());
}
이 경우에 해당하는 테스트는 다음과 같습니다:
public void WhenItemListIsNull_ShouldThrowArgumentException()
{
    List<Item> items = null;

    Action act = () => items.ContainsNamelessItems();

    act.Should().Throw<ArgumentException>()
       .WithMessage("Items collection cannot be null.");
}

정리

입력 값에 대해 동등 클래스 분할을 활용하여 다양한 시나리오를 테스트할 수 있습니다. 특히 강타입 언어에서는 컴파일러가 일부 오류를 감지하지만, 약타입 언어에서는 더 많은 숨겨진 오류를 유발할 수 있으므로 주의가 필요합니다. 실행 경로 기반 테스트는 분기 커버리지를 확보하기 위해 입력에 따라 서로 다른 코드 경로를 모두 테스트해야 합니다. 예를 들어, 유효한 입력 값 중에서도 빈 리스트와 비어 있지 않은 리스트는 서로 다른 실행 경로를 따릅니다. 복잡한 메서드는 작게 분리함으로써 테스트 가능성을 높일 수 있습니다.

태그: unit testing test case design csharp fluent assertions Software Testing

8월 3일 20:58에 게시됨