Testcontainers for .NET 시작하기

Testcontainers for .NET 시작하기

Testcontainers for .NET과 실제 PostgreSQL 인스턴스로 .NET 어플리케이션을 만들고 데이터베이스 상호작용을 테스트하는 방법을 배워볼게요.

출처: 문서

본문

이 가이드에서 배울 내용:

  • 소스 및 테스트 프로젝트가 있는 .NET 솔루션 만들기
  • PostgreSQL에서 고객 레코드를 관리하는 CustomerService 구현하기
  • Testcontainers와 xUnit으로 통합 테스트 작성하기
  • IAsyncLifetime 으로 컨테이너 수명주기 관리하기

사전 요구사항

  • .NET 8.0+ SDK
  • Testcontainers가 지원하는 Docker 환경

참고: Testcontainers를 처음 접한다면 Testcontainers 개요 를 방문해 Testcontainers와 그 이점에 대해 알아보세요.

.NET 프로젝트 만들기

솔루션 설정

소스 및 테스트 프로젝트가 있는 .NET 솔루션을 만들어주세요:

$ dotnet new sln -o TestcontainersDemo
$ cd TestcontainersDemo
$ dotnet new classlib -o CustomerService
$ dotnet sln add ./CustomerService/CustomerService.csproj
$ dotnet new xunit -o CustomerService.Tests
$ dotnet sln add ./CustomerService.Tests/CustomerService.Tests.csproj
$ dotnet add ./CustomerService.Tests/CustomerService.Tests.csproj reference ./CustomerService/CustomerService.csproj

소스 프로젝트에 Npgsql 의존성을 추가해주세요:

$ dotnet add ./CustomerService/CustomerService.csproj package Npgsql

비즈니스 로직 구현

Customer 레코드 타입을 만들어주세요:

namespace Customers;

public readonly record struct Customer(long Id, string Name);

데이터베이스 연결을 관리하는 DbConnectionProvider 클래스를 만들어주세요:

using System.Data.Common;
using Npgsql;

namespace Customers;

public sealed class DbConnectionProvider
{
    private readonly string _connectionString;

    public DbConnectionProvider(string connectionString)
    {
        _connectionString = connectionString;
    }

    public DbConnection GetConnection()
    {
        return new NpgsqlConnection(_connectionString);
    }
}

CustomerService 클래스를 만들어주세요:

namespace Customers;

public sealed class CustomerService
{
    private readonly DbConnectionProvider _dbConnectionProvider;

    public CustomerService(DbConnectionProvider dbConnectionProvider)
    {
        _dbConnectionProvider = dbConnectionProvider;
        CreateCustomersTable();
    }

    public IEnumerable<Customer> GetCustomers()
    {
        IList<Customer> customers = new List<Customer>();
        using var connection = _dbConnectionProvider.GetConnection();
        using var command = connection.CreateCommand();
        command.CommandText = "SELECT id, name FROM customers";
        command.Connection?.Open();
        using var dataReader = command.ExecuteReader();
        while (dataReader.Read())
        {
            var id = dataReader.GetInt64(0);
            var name = dataReader.GetString(1);
            customers.Add(new Customer(id, name));
        }
        return customers;
    }

    public void Create(Customer customer)
    {
        using var connection = _dbConnectionProvider.GetConnection();
        using var command = connection.CreateCommand();
        var id = command.CreateParameter();
        id.ParameterName = "@id";
        id.Value = customer.Id;
        var name = command.CreateParameter();
        name.ParameterName = "@name";
        name.Value = customer.Name;
        command.CommandText = "INSERT INTO customers (id, name) VALUES(@id, @name)";
        command.Parameters.Add(id);
        command.Parameters.Add(name);
        command.Connection?.Open();
        command.ExecuteNonQuery();
    }

    private void CreateCustomersTable()
    {
        using var connection = _dbConnectionProvider.GetConnection();
        using var command = connection.CreateCommand();
        command.CommandText = "CREATE TABLE IF NOT EXISTS customers (id BIGINT NOT NULL, name VARCHAR NOT NULL, PRIMARY KEY (id))";
        command.Connection?.Open();
        command.ExecuteNonQuery();
    }
}

CustomerService 가 하는 일:

  • 생성자가 CreateCustomersTable() 을 호출해 테이블이 존재하게 보장해요.
  • GetCustomers() 가 customers 테이블의 모든 행을 가져와 Customer 객체로 반환해요.
  • Create() 가 데이터베이스에 고객 레코드를 삽입해요.

Testcontainers로 테스트 작성하기

Testcontainers 의존성 추가

테스트 프로젝트에 Testcontainers PostgreSQL 모듈을 추가해주세요:

$ dotnet add ./CustomerService.Tests/CustomerService.Tests.csproj package Testcontainers.PostgreSql

테스트 작성

테스트 프로젝트에 CustomerServiceTest.cs 를 만들어주세요:

using Testcontainers.PostgreSql;

namespace Customers.Tests;

public sealed class CustomerServiceTest : IAsyncLifetime
{
    private readonly PostgreSqlContainer _postgres =
        new PostgreSqlBuilder()
            .WithImage("postgres:16-alpine")
            .Build();

    public Task InitializeAsync()
    {
        return _postgres.StartAsync();
    }

    public Task DisposeAsync()
    {
        return _postgres.DisposeAsync().AsTask();
    }

    [Fact]
    public void ShouldReturnTwoCustomers()
    {
        // Given
        var customerService = new CustomerService(
            new DbConnectionProvider(_postgres.GetConnectionString()));

        // When
        customerService.Create(new Customer(1, "George"));
        customerService.Create(new Customer(2, "John"));
        var customers = customerService.GetCustomers();

        // Then
        Assert.Equal(2, customers.Count());
    }
}

테스트가 하는 일:

  • postgres:16-alpine Docker 이미지로 PostgreSqlBuilder 를 사용해 PostgreSqlContainer 를 선언해요.
  • 컨테이너 수명주기 관리를 위해 IAsyncLifetime 을 구현해요:
    • InitializeAsync() 가 테스트 실행 전에 컨테이너를 시작해요.
    • DisposeAsync() 가 테스트 완료 후 컨테이너를 중지하고 제거해요.
  • ShouldReturnTwoCustomers() 가 컨테이너의 연결 세부사항으로 CustomerService 를 만들고, 두 고객을 삽입하며, 모든 고객을 가져와 개수를 검증해요.

테스트 실행 및 다음 단계

테스트 실행

테스트를 실행해주세요:

$ dotnet test

출력에서 Testcontainers가 Postgres Docker 이미지를 Docker Hub에서 내려받고(로컬에 없을 경우), 컨테이너를 시작하며, 테스트를 실행하는 것을 볼 수 있어요.

Testcontainers로 통합 테스트를 작성하는 것은 IDE에서 실행할 수 있는 단위 테스트를 작성하는 것과 같아요. 팀원들이 Postgres를 자신의 머신에 설치하지 않고도 프로젝트를 클론해 테스트를 실행할 수 있어요.

요약

Testcontainers for .NET 라이브러리는 목(mock) 대신 프로덕션에서 사용하는 것과 같은 유형의 데이터베이스(Postgres)를 사용해 통합 테스트를 작성하게 도와줘요. 목을 사용하지 않고 실제 서비스와 대화하므로 코드를 자유롭게 리팩터링하면서도 어플리케이션이 예상대로 작동하는지 검증할 수 있어요.

Postgres 외에도 Testcontainers는 많은 SQL 데이터베이스, NoSQL 데이터베이스, 메시징 큐 등을 위한 전용 모듈 을 제공해요.

Testcontainers에 대해 더 배우려면 Testcontainers 개요 를 방문해주세요.

추가 자료

  • ASP.NET Core 웹 앱 테스트

더 알아보기 (Learn more)

  • Testcontainers 개요
  • Testcontainers for .NET
  • ASP.NET Core 앱 테스트