C#のテストフレームワーク xUnit を使用して、
の違いを実装ベースで解説
今回の方針:
結合テストは、実際の環境(DBや外部API)と統合した状態で動作を確認するテストです。
メリット:
・ 実際の環境に近い形でテストができる
・データベースの動作や外部APIとの連携を確認できる
デメリット:
テストの実行時間が長くなる
コンテナ環境のセットアップが必要
単体テストは、個々のメソッドやクラス単位で動作を検証するテストです。
メリット:
実行が速く、即座に結果を確認できる
クラス単位で動作を細かく検証できる
デメリット:
実際のDBや外部APIとの動作は確認できない
結合テストでは、実際の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;
}
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環境でテスト
データの登録・取得が正しく動作するかを検証
次に、UserService
というクラスを作成し、UserRepository
を Mock に置き換えた単体テスト を作成。
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;
}
}
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);
}
}
UserRepository
を Moq でモック化 し、DB操作をシミュレートRegisterUserAsync("Alice")
が正常に動作するかをテスト
項目 | 結合テスト(Integration Test) | 単体テスト(Unit Test) |
---|---|---|
テスト対象 | システム全体の動作確認 | クラス単位の動作確認 |
DB/外部APIの使用 | 実際のDB(コンテナ)を使用 | Mock で代替 |
実行速度 | 遅い | 速い |
バグの発見 | 実環境に近い動作を確認 | メソッド単位で早期発見 |
メリット | 環境に近い動作検証が可能 | 即座に結果が得られる |
デメリット | 環境構築が必要 | 実際のDB動作は未確認 |
結合テストと単体テストを適切に使い分け、
品質の高いアプリケーションを開発しましょう!