Csharp Testing

作者 affaan-mef648e01899b无许可证275K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库3天前更新

xUnit、FluentAssertions、モッキング、統合テスト、テスト組織のベストプラクティスを使用したC#と.NETのテストパターン。

AI 生成的概览

使用 xUnit、FluentAssertions、模拟、集成测试和测试组织的 C# 与 .NET 测试模式。

功能
提供使用 xUnit、FluentAssertions、NSubstitute 或 Moq、Testcontainers、WebApplicationFactory 和 Bogus 编写 C#/.NET 测试的参考模式与代码示例。涵盖单元测试结构、参数化 Theory、模拟、ASP.NET Core 集成测试、测试数据构建器、测试组织以及常见反模式。还列出用于运行测试、覆盖率和筛选的 dotnet test 命令。
适用场景
适用于为 C# 代码编写新测试、审查测试质量与覆盖率、在 .NET 项目中搭建测试基础设施,或调试不稳定或缓慢的测试。
运行要求
仅为说明文档,不附带脚本。示例假定存在一个 .NET 项目,并包含 xUnit、FluentAssertions、NSubstitute 或 Moq 等模拟库,可选使用 Testcontainers、WebApplicationFactory 和 Bogus,以及 dotnet CLI。

C#テストパターン

xUnit、FluentAssertions、最新のテストプラクティスを使用した.NETアプリケーションの包括的なテストパターン。

起動条件

  • C#コードの新しいテストを書く場合
  • テスト品質とカバレッジのレビュー
  • .NETプロジェクトのテストインフラストラクチャの設定
  • フレーキーまたは遅いテストのデバッグ

テストフレームワークスタック

ツール目的
xUnitテストフレームワーク(.NETに推奨)
FluentAssertions読みやすいアサーション構文
NSubstituteまたはMoq依存関係のモッキング
Testcontainers統合テストでの実際のインフラ
WebApplicationFactoryASP.NET Core統合テスト
Bogus現実的なテストデータ生成

ユニットテスト構造

Arrange-Act-Assert

csharp
public sealed class OrderServiceTests{    private readonly IOrderRepository _repository = Substitute.For<IOrderRepository>();    private readonly ILogger<OrderService> _logger = Substitute.For<ILogger<OrderService>>();    private readonly OrderService _sut;
    public OrderServiceTests()    {        _sut = new OrderService(_repository, _logger);    }
    [Fact]    public async Task PlaceOrderAsync_ReturnsSuccess_WhenRequestIsValid()    {        // Arrange        var request = new CreateOrderRequest        {            CustomerId = "cust-123",            Items = [new OrderItem("SKU-001", 2, 29.99m)]        };
        // Act        var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
        // Assert        result.IsSuccess.Should().BeTrue();        result.Value.Should().NotBeNull();        result.Value!.CustomerId.Should().Be("cust-123");    }
    [Fact]    public async Task PlaceOrderAsync_ReturnsFailure_WhenNoItems()    {        // Arrange        var request = new CreateOrderRequest        {            CustomerId = "cust-123",            Items = []        };
        // Act        var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
        // Assert        result.IsSuccess.Should().BeFalse();        result.Error.Should().Contain("at least one item");    }}

Theoryによるパラメータ化テスト

csharp
[Theory][InlineData("", false)][InlineData("a", false)][InlineData("[email protected]", false)][InlineData("[email protected]", true)][InlineData("[email protected]", true)]public void IsValidEmail_ReturnsExpected(string email, bool expected){    EmailValidator.IsValid(email).Should().Be(expected);}
[Theory][MemberData(nameof(InvalidOrderCases))]public async Task PlaceOrderAsync_RejectsInvalidOrders(CreateOrderRequest request, string expectedError){    var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
    result.IsSuccess.Should().BeFalse();    result.Error.Should().Contain(expectedError);}
public static TheoryData<CreateOrderRequest, string> InvalidOrderCases => new(){    { new() { CustomerId = "", Items = [ValidItem()] }, "CustomerId" },    { new() { CustomerId = "c1", Items = [] }, "at least one item" },    { new() { CustomerId = "c1", Items = [new("", 1, 10m)] }, "SKU" },};

NSubstituteによるモッキング

csharp
[Fact]public async Task GetOrderAsync_ReturnsNull_WhenNotFound(){    // Arrange    var orderId = Guid.NewGuid();    _repository.FindByIdAsync(orderId, Arg.Any<CancellationToken>())        .Returns((Order?)null);
    // Act    var result = await _sut.GetOrderAsync(orderId, CancellationToken.None);
    // Assert    result.Should().BeNull();}
[Fact]public async Task PlaceOrderAsync_PersistsOrder(){    // Arrange    var request = ValidOrderRequest();
    // Act    await _sut.PlaceOrderAsync(request, CancellationToken.None);
    // Assert — リポジトリが呼び出されたことを検証    await _repository.Received(1).AddAsync(        Arg.Is<Order>(o => o.CustomerId == request.CustomerId),        Arg.Any<CancellationToken>());}

ASP.NET Core統合テスト

WebApplicationFactoryのセットアップ

csharp
public sealed class OrderApiTests : IClassFixture<WebApplicationFactory<Program>>{    private readonly HttpClient _client;
    public OrderApiTests(WebApplicationFactory<Program> factory)    {        _client = factory.WithWebHostBuilder(builder =>        {            builder.ConfigureServices(services =>            {                // テスト用にインメモリDBで実際のDBを置き換え                services.RemoveAll<DbContextOptions<AppDbContext>>();                services.AddDbContext<AppDbContext>(options =>                    options.UseInMemoryDatabase("TestDb"));            });        }).CreateClient();    }
    [Fact]    public async Task GetOrder_Returns404_WhenNotFound()    {        var response = await _client.GetAsync($"/api/orders/{Guid.NewGuid()}");
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);    }
    [Fact]    public async Task CreateOrder_Returns201_WithValidRequest()    {        var request = new CreateOrderRequest        {            CustomerId = "cust-1",            Items = [new("SKU-001", 1, 19.99m)]        };
        var response = await _client.PostAsJsonAsync("/api/orders", request);
        response.StatusCode.Should().Be(HttpStatusCode.Created);        response.Headers.Location.Should().NotBeNull();    }}

Testcontainersによるテスト

csharp
public sealed class PostgresOrderRepositoryTests : IAsyncLifetime{    private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()        .WithImage("postgres:16-alpine")        .Build();
    private AppDbContext _db = null!;
    public async Task InitializeAsync()    {        await _postgres.StartAsync();        var options = new DbContextOptionsBuilder<AppDbContext>()            .UseNpgsql(_postgres.GetConnectionString())            .Options;        _db = new AppDbContext(options);        await _db.Database.MigrateAsync();    }
    public async Task DisposeAsync()    {        await _db.DisposeAsync();        await _postgres.DisposeAsync();    }
    [Fact]    public async Task AddAsync_PersistsOrder()    {        var repo = new SqlOrderRepository(_db);        var order = Order.Create("cust-1", [new OrderItem("SKU-001", 2, 10m)]);
        await repo.AddAsync(order, CancellationToken.None);
        var found = await repo.FindByIdAsync(order.Id, CancellationToken.None);        found.Should().NotBeNull();        found!.Items.Should().HaveCount(1);    }}

テスト組織

tests/  MyApp.UnitTests/    Services/      OrderServiceTests.cs      PaymentServiceTests.cs    Validators/      EmailValidatorTests.cs  MyApp.IntegrationTests/    Api/      OrderApiTests.cs    Repositories/      OrderRepositoryTests.cs  MyApp.TestHelpers/    Builders/      OrderBuilder.cs    Fixtures/      DatabaseFixture.cs

テストデータビルダー

csharp
public sealed class OrderBuilder{    private string _customerId = "cust-default";    private readonly List<OrderItem> _items = [new("SKU-001", 1, 10m)];
    public OrderBuilder WithCustomer(string customerId)    {        _customerId = customerId;        return this;    }
    public OrderBuilder WithItem(string sku, int quantity, decimal price)    {        _items.Add(new OrderItem(sku, quantity, price));        return this;    }
    public Order Build() => Order.Create(_customerId, _items);}
// テストでの使用var order = new OrderBuilder()    .WithCustomer("cust-vip")    .WithItem("SKU-PREMIUM", 3, 99.99m)    .Build();

よくあるアンチパターン

アンチパターン修正方法
実装の詳細をテストする動作と結果をテストする
共有の可変テスト状態テストごとに新しいインスタンス(xUnitはコンストラクタでこれを行う)
非同期テストでのThread.Sleepタイムアウトまたはポーリングヘルパーを使用したTask.Delay
ToString()出力のアサーション型付きプロパティのアサーション
テストごとに1つの巨大なアサーションテストごとに1つの論理的なアサーション
実装を記述するテスト名動作で命名: Method_ExpectedResult_WhenCondition
CancellationTokenを無視する常に渡してキャンセルを確認する

テストの実行

bash
# すべてのテストを実行dotnet test
# カバレッジを付けて実行dotnet test --collect:"XPlat Code Coverage"
# 特定のプロジェクトを実行dotnet test tests/MyApp.UnitTests/
# テスト名でフィルタリングdotnet test --filter "FullyQualifiedName~OrderService"
# 開発中のウォッチモードdotnet watch test --project tests/MyApp.UnitTests/

来源与署名

来源:affaan-m/ecc位于docs/ja-JP/skills/csharp-testing提交ef648e0

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架