• システム開発に関わる内容をざっくりと書いていく

【C#】テストフレームワークを使った結合テストと単体テストの違い

C#のテストフレームワーク xUnit を使用して、

  • 結合テスト(Integration Test)
  • 単体テスト(Unit Test)

の違いを実装ベースで解説

今回の方針:

  • 結合テスト: DBや外部通信の環境はコンテナで用意し、アプリ側は接続設定のみ記述
  • 単体テスト: Mock を活用して外部依存を排除

結合テスト(Integration Test)とは?

結合テストは、実際の環境(DBや外部API)と統合した状態で動作を確認するテストです。

  • 実際の DBや外部API を使用する
  • コンテナを活用してテスト環境を構築
  • アプリケーション全体の動作を検証

メリット:

・ 実際の環境に近い形でテストができる
・データベースの動作や外部APIとの連携を確認できる

デメリット:

テストの実行時間が長くなる
コンテナ環境のセットアップが必要

単体テスト(Unit Test)とは?

単体テストは、個々のメソッドやクラス単位で動作を検証するテストです。

  • DBや外部APIは使用しない
  • Mockを活用 して外部依存を排除
  • 実行時間が短く、素早くフィードバックを得られる

メリット:

実行が速く、即座に結果を確認できる
クラス単位で動作を細かく検証できる

デメリット:

実際のDBや外部APIとの動作は確認できない

結合テストの実装(xUnit)

1環境設定(DBの接続文字列を設定)

結合テストでは、実際のDBを利用。
環境として コンテナ上にPostgreSQLを立ち上げ、アプリ側では 接続設定のみ記述 。(実アプリケーションでは設定ファイルを使用)

// IntegrationTestBase.cs
using System.Threading.Tasks;
using Xunit;

public class IntegrationTestBase : IAsyncLifetime
{
    protected string ConnectionString;

    public IntegrationTestBase()
    {
        // 実際のコンテナ環境に合わせて接続文字列を設定
        ConnectionString = "Host=localhost;Port=5432;Database=test_db;Username=test_user;Password=test_pass";
    }

    public Task InitializeAsync() => Task.CompletedTask;
    public Task DisposeAsync() => Task.CompletedTask;
}

2 結合テストの実装

UserRepository.cs(DBとのデータ操作)

// UserRepository.cs
using System.Threading.Tasks;
using Dapper;
using Npgsql;

public class UserRepository
{
    private readonly string _connectionString;

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

    public async Task<int> AddUserAsync(string name)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        return await connection.ExecuteAsync("INSERT INTO users (name) VALUES (@name)", new { name });
    }

    public async Task<string> GetUserAsync(int id)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        return await connection.QuerySingleOrDefaultAsync<string>("SELECT name FROM users WHERE id = @id", new { id });
    }
}

UserRepositoryTests.cs(結合テスト)

// UserRepositoryTests.cs
using System.Threading.Tasks;
using Xunit;

public class UserRepositoryTests : IntegrationTestBase
{
    [Fact]
    public async Task AddUser_Should_SaveToDatabase()
    {
        var repository = new UserRepository(ConnectionString);

        await repository.AddUserAsync("Alice");
        var result = await repository.GetUserAsync(1);

        Assert.Equal("Alice", result);
    }
}

UserRepository実際のPostgreSQL環境でテスト
データの登録・取得が正しく動作するかを検証

単体テストの実装(xUnit + Moq)

次に、UserService というクラスを作成し、
UserRepositoryMock に置き換えた単体テスト を作成。

1 UserService.cs(ビジネスロジック)

// UserService.cs
using System.Threading.Tasks;

public class UserService
{
    private readonly IUserRepository _userRepository;

    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public async Task<bool> RegisterUserAsync(string name)
    {
        var userId = await _userRepository.AddUserAsync(name);
        return userId > 0;
    }
}

2 UserServiceTests.cs(単体テスト)

// UserServiceTests.cs
using System.Threading.Tasks;
using Moq;
using Xunit;

public class UserServiceTests
{
    [Fact]
    public async Task RegisterUser_Should_ReturnTrue_When_Success()
    {
        var mockRepo = new Mock<IUserRepository>();
        mockRepo.Setup(repo => repo.AddUserAsync(It.IsAny<string>())).ReturnsAsync(1);

        var service = new UserService(mockRepo.Object);

        var result = await service.RegisterUserAsync("Alice");

        Assert.True(result);
    }
}

UserRepositoryMoq でモック化 し、DB操作をシミュレート
RegisterUserAsync("Alice") が正常に動作するかをテスト

結合テストと単体テストの違い

項目結合テスト(Integration Test)単体テスト(Unit Test)
テスト対象システム全体の動作確認クラス単位の動作確認
DB/外部APIの使用実際のDB(コンテナ)を使用Mock で代替
実行速度遅い速い
バグの発見実環境に近い動作を確認メソッド単位で早期発見
メリット環境に近い動作検証が可能即座に結果が得られる
デメリット環境構築が必要実際のDB動作は未確認

まとめ

  • 結合テストは「本番環境に近い形での動作確認」
    • 実際の DB・外部API を利用
    • コンテナ環境の接続文字列のみ設定
  • 単体テストは「クラス単位の小さな検証」
    • Mock で外部依存を排除
    • テストの実行が高速

結合テストと単体テストを適切に使い分け、
品質の高いアプリケーションを開発しましょう!