단위 테스팅과 통합, 승인, 기능 테스팅

단위 테스팅이란 용어는 자주 사용되지만 유사한 모습을 가진 테스팅 기법들과 자주 혼동됩니다. 용어가 혼동되면 의사소통에 문제가 발생하고 각 용어의 대상이 가지는 특징을 이해할 수 없기 때문에 필요한 곳에 적절한 도구를 사용하지 못합니다. 이 게시물은 단위 테스팅, 그리고 단위 테스팅과 흔히 혼동되는 테스팅 기법 몇 가지를 설명합니다.

설명을 돕기 위해 데이터를 입력하고 수정하는 간단한 예제 응용프로그램을 정의합니다.

public interface IRepository
{
    void Insert(string title);
    void Update(int id, string title);
}

public class SqlRepository : IRepository
{
    private readonly SqlDatabase _database;

    public SqlRepository(SqlDatabase database)
    {
        _database = database;
    }

    public void Insert(string title) =>
        _database.Execute("Insert ...");

    public void Update(int id, string title) =>
        _database.Execute("Update ...");
}

public class BusinessLayer
{
    private readonly IRepository _repository;

    public BusinessLayer(IRepository repository)
    {
        _repository = repository;
    }

    public void InsertItem(string title)
    {
        // Input validation
        // Business logic

        _repository.Insert(title);
    }

    public void UpdateItem(int id, string title)
    {
        // Input validation
        // Business logic

        _repository.Update(id, title);
    }
}

public class Application
{
    public static void Main(string[] args)
    {
        var database = new SqlDatabase(...);
        IRepository repository = new SqlRepository(database);
        var business = new BusinessLayer(repository);

        switch (args[0])
        {
            case "insert":
                business.Insert(args[1]);
                break;

            case "update":
                business.Update(int.Parse(args[1]), args[2]);
                break;
        }
    }
}

단위 테스팅(Unit Testing)

소프트웨어의 테스트 가능한 가장 작은 단위의 코드를 테스트하는 기법입니다. 개체지향 프로그래밍(object-oriented programming)에서는 주로 클래스나 메서드가 테스트 대상입니다. 일반적으로 하나의 테스트 대상에 대해서 세부적인 논리별로 각각 테스트 케이스를 작성합니다.

이상적으로 단위 테스트 케이스는 테스트 대상이 아닌 코드의 결점에 독립적이어야 합니다. 그래서 테스트 대상이 의존하는 모듈은 다른 테스트 케이스에 의해 검증된 코드, 신뢰할 수 있는 라이브러리 또는 테스트 대역(test double)이 사용됩니다. 개체지향 프로그래밍에서 단위 테스팅에 테스트 대역을 사용하기 위해서는 테스트 대상에 의존성 역전 원리(Dependency Inversion Principal)가 적용되어야 합니다.

단위 테스팅은 테스트 대상의 범위가 작고 검증 대상이 세부적이기 때문에 테스트 케이스가 실패하면 전체 시스템 중 어디에서 어떤 문제가 발생했는지 파악하기 쉽습니다. 즉, 프로그래머에게 제공되는 피드백의 품질이 높습니다. 그리고 테스트 케이스가 실핼될 때 동작하는 코드의 크기가 작기 때문에 매우 빠릅니다. 피드백 품질이 높고 빠르기 때문에 단위 테스팅은 프로그래머에게 테스팅을 장려하는 효과를 가집니다.

단위 테스팅이 소프트웨어의 세부 사항을 검증하긴 하지만 독립적으로 검증된 모듈들이 조립(통합)되었을 때 문제가 없다는 것을 보장하지는 않습니다. 모듈 통합 오류나 시스템 수준의 오류는 단위 테스팅이 검출하지 못하기 때문에 다른 테스팅 기법의 도움을 받아야합니다. 그리고 일반적으로 단위 테스팅은 GUI 계층에 적용하기 어렵습니다.

예제 응용프로그램의 데이터 수정 비즈니스 계층에 대한 단위 테스트 케이스는 다음과 같이 작성될 수 있습니다.

public void UpdateItem_changes_title()
{
    // Arrange
    IRepository repository = Mock.Of();
    var sut = new BusinessLayer(repository);
    int id = new Random().Next();
    string title = Guid.NewGuid().ToString();

    // Act
    sut.UpdateItem(id, title);

    // Assert
    Mock.Get(repository)
        .Verify(x => x.Update(id, title), Times.Once());
}

IRepository에 의존하는 BusinessLayer의 기능을 테스트하기 위해 운영 데이터베이스 서버에 연결되는 구현체를 사용할 경우 BusinessLayer 코드에 결함이 없더라도 네트워크 연결에 문제가 있거나 누군가 데이터베이스 스키마를 변경한 경우 단위 테스트 케이스는 실패합니다. 즉, 테스트 대상을 벗어난 시스템의 상태에 테스트 케이스 결과가 종속됩니다. 그렇기 때문에 테스트 케이스에서 IRepository에 대한 테스트 대역이 사용되었습니다. 이것을 통해 BusinessLayer.UpdateItem() 메서드가 IRepository를 적절히 사용하고 있는지 검증합니다.

예제에서 테스트 대역(test double)으로 mock이 사용되었습니다. 테스트 대역과 mock은 동의어가 아닙니다. Mock은 테스트 대역의 유형 중 하나입니다. Martin Fowler는 이 글에서 테스트 대역 유형에 대해 간단히 설명합니다. 단순한 예제 코드를 위해 mock을 사용했지만 데이터베이스에 종속되는 단위 테스트 케이스에 항상 mock이 사용되는 것은 아닙니다. 로컬 데이터베이스에 연결된 SqlRepository 인스턴스가 테스트 대역으로 사용될 수도 있습니다. 물론 이때 검증 코드도 함께 달라집니다. 최근 저는 이 방식을 더 선호합니다.

sutSystem Under Test를 의미합니다.

통합 테스팅(Integration Testing)

통합 테스팅은 둘 이상의 시스템 구성요소가 통합되었을 때 기대한 대로 동작하는지 검사하는 것을 말합니다. 충분히 단위 테스트된 하위 시스템들이라도 상호작용에 문제가 발생할 수 있습니다. 예를 들어 어떤 추상 서비스에 의존하는 개체가 테스트 대역(test double)을 사용한 모든 단위 테스트 케이스를 통과했지만 서비스를 구현하는 특정 외부 라이브러리와 조립되었을 때 기대하지 않은 결과를 얻을 수 있습니다. 통합 테스팅의 목적은 이런 오류들을 검출하는 것입니다. 일반적으로 통합 테스팅은 단위 테스팅과 기능 테스팅 사이에서 동작합니다.

통합 테스팅을 설명하는 몇 가지 표현을 인용합니다.

  1. en.wikipedia.org
    “Integration testing is the phase in software testing in which individual software modules are combined and tested as a group.”
  2. c2.com
    “IntegrationTest evaluates that subsystem as integrated within a larger subsystem or within the complete system, testing interdependencies with other subsystems, including other project and third party libraries, tools, etc.”
  3. blogs.msdn.microsoft.com
    “Integration tests cover a wide variety of scenarios where you connect two or more units into an integrated subsystem.”

승인 테스팅(Acceptance Testing)

소프트웨어가 승인 기준을 만족하는지 검사하는, 구현에 독립적인 black-box 테스팅입니다. 입력에 대해 기대한 출력이 반환되는지, 충분한 성능이 제공되는지 등을 검사합니다. 이상적으로 승인 테스트 케이스는 사용자(고객)가 작성합니다. 그것이 불가능한 경우, 사용자의 의견을 수집해 비즈니스 분석가나 프로그래머가 대신합니다. 승인 테스팅은 운영 환경, 혹은 운영 환경과 유사한 환경에서 진행됩니다.

승인 테스트 케이스는 내부 디자인이나 시스템 아키텍처 변경에 영향을 거의 받지 않습니다. 그리고 단위 테스팅통합 테스팅은 검출하지 못하는 시스템 수준이나 호스팅 환경에서 발생하는 문제를 검출합니다.

DPAPI를 사용하는 데이터 보호 코드는 server farm 환경에서 문제를 일으킬 수 있습니다. 이 문제는 승인 테스팅이 아니면 발견하기 어렵습니다.

승인 테스트 케이스가 실행되면 테스트 대상 기능과 관련된 모든 구성요소(코드, 데이터베이스, 네트워크, 메시지 큐, 운영체제, …)가 동작합니다. 따라서 단위 테스팅에 비해 테스트 케이스 소요시간이 길고 테스트 케이스가 실패한 경우 시스템의 어느 위치에서 어떤 문제가 발생했는지 피드백되지 않습니다.

승인 테스팅은 자동화가 필수적이지 않습니다. 경우에 따라 수동으로 수행됩니다.

기능 테스팅(Functional Testing)

기능 테스팅은 승인 테스팅과 유사하지만 필수적으로 자동화됩니다. E2E(end-to-end) 테스팅이라고도 부릅니다.

예제 응용프로그램에 대한 기능 테스트 케이스는 다음과 같이 작성될 수 있습니다.

public void UpdateCommand_changes_title_of_item()
{
    // Arrange
    var database = new SqlDatabase(...);
    int id = database.Insert("Hello World");
    string title = Guid.NewGuid().ToString();

    // Act
    ExecuteCommand($"Application.exe update {id} {title}");

    // Assert
    database
        .Execute("SELECT TITLE FROM ...")
        .Should().Be(title);

    // Cleanup
    database.Execute("DELETE ...");
}

이 테스트 케이스는 Application.exe가 배포된 환경에서 실행되어야합니다. 테스트 케이스는 응용프로그램이 제공하는 콘솔 인터페이스를 통해 update 명령을 전달한 후 데이터베이스에 기대한 변경이 있었는지 검사합니다. 만약 기대한 결과를 얻지 못했다면 기능 테스트 케이스가 주는 피드백은 디버깅에 충분하지 않기 때문에 이제 프로그래머는 문제를 해결하기 위해 원인을 조사합니다. 콘솔 명령 인터페이스에 문제가 있을 수도 있고 비즈니스 계층 혹은 데이터 접근 계층의 버그가 원인일 수도 있습니다. 더불어 데이터베이스 연결 문자열 설정, 테이블 스키마, 방화벽 설정 등도 살펴봐야 합니다.

테스팅 기법 선택

이제 다양한 테스팅 기법 중 어떤 것을 선택해야 하는지에 대한 고민이 남습니다. 이 문제에 대한 제 생각과 매우 유사한 ‘Just Say No to More End-to-End Tests‘라는 글을 소개합니다.

Advertisements

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중