C#を用いたアプリケーション開発において、品質を担保するために欠かせないのが自動テストの導入です。

近代的なソフトウェア開発では、継続的なデリバリーを実現するために、コードの正確性を即座に検証できる仕組みが求められています。

その中でも、.NETエコシステムで最も広く利用されているテスティングフレームワークの一つがxUnitです。

xUnitは、シンプルかつ拡張性に優れた設計思想を持っており、多くのオープンソースプロジェクトや企業開発で標準的に採用されています。

本記事では、xUnitを利用した単体テストの基礎から、実務で役立つ実践的なテストコードの書き方までを詳しく解説します。

C#における単体テストの役割と重要性

単体テスト(ユニットテスト)とは、プログラムを構成する最小単位であるメソッドやクラスが、意図した通りに動作するかを確認する作業です。

開発の初期段階でバグを発見することで、修正コストを大幅に削減できるという大きなメリットがあります。

また、リファクタリングを行う際にも、既存の機能が壊れていないかを即座に確認できるため、コードの品質を維持しやすくなります。

2026年現在のソフトウェア開発においては、CI/CDパイプラインによる自動テストの実行が不可欠なプロセスとなっています。

単体テストが整備されていることで、チーム全体の開発スピードを向上させ、安定したリリースを実現することが可能になります。

xUnitの概要と主要な特徴

xUnitは、JavaのJUnitの流れを汲む「xUnitパターン」に従った、.NET向けのモダンなテスティングフレームワークです。

他のフレームワークであるMSTestやNUnitと比較して、シンプルで無駄のない構文が特徴とされています。

xUnitでは、テストクラスのインスタンスがテストメソッドごとに作成されるため、各テストの独立性が高く保たれる設計になっています。

また、属性(Attribute)を利用した宣言的な記述により、テストの意図を明確に示すことができます。

拡張性が非常に高く、カスタム属性の作成や出力のカスタマイズが容易である点も、多くのプログラマに支持される理由の一つです。

開発環境のセットアップ

xUnitを使用したテストプロジェクトを作成するには、まず.NET SDKがインストールされている必要があります。

Visual Studioを使用している場合は、新しいプロジェクトの作成から「xUnit テスト プロジェクト」を選択するだけで、必要なライブラリが揃った環境を構築できます。

CLI(コマンドラインインターフェース)を使用する場合は、以下のコマンドを実行することでプロジェクトを作成できます。

C#
// プロジェクト作成用のコマンド例
dotnet new xunit -n MyProject.Tests

作成されたプロジェクトには、xunitxunit.runner.visualstudioなどのNuGetパッケージが自動的に含まれます。

これにより、IDEのエクスプローラー上から簡単にテストを実行し、結果を確認できるようになります。

xUnitの基本:Fact属性とTheory属性

xUnitでテストを記述する際、最も頻繁に使用するのが[Fact][Theory]という2つの属性です。

Fact属性による基本的なテスト

[Fact]は、常に真であるべき単一の条件をテストするために使用されます。

引数を持たないメソッドに付与し、そのメソッド内で行われる検証が成功するかどうかを確認します。

C#
using Xunit;

public class CalculatorTests
{
    [Fact]
    public void Add_ShouldReturnSumOfTwoNumbers()
    {
        // 準備
        var calculator = new Calculator();

        // 実行
        var result = calculator.Add(10, 20);

        // 検証
        Assert.Equal(30, result);
    }
}

このコードでは、10と20を足した結果が30になることを検証しています。

Theory属性によるデータ駆動型テスト

[Theory]は、複数の異なる入力データに対して同じテストロジックを実行したい場合に使用されます。

[InlineData]属性と組み合わせることで、テストメソッドに引数を渡すことができます。

C#
using Xunit;

public class CalculatorTests
{
    [Theory]
    [InlineData(1, 2, 3)]
    [InlineData(-1, 1, 0)]
    [InlineData(0, 0, 0)]
    public void Add_ShouldReturnCorrectSum(int a, int b, int expected)
    {
        var calculator = new Calculator();

        var result = calculator.Add(a, b);

        Assert.Equal(expected, result);
    }
}

このように記述することで、一つのメソッドで複数のパターンを網羅できるため、テストコードの重複を避けることが可能です。

実践的なテストコードの書き方:AAAパターン

読みやすくメンテナンス性の高いテストコードを書くためには、AAA(Arrange, Act, Assert)パターンを採用することが推奨されます。

Arrange(準備)

テストに必要なオブジェクトの生成や、前提条件のセットアップを行うフェーズです。

テスト対象となるクラスのインスタンス化や、入力データの準備をここで行います。

Act(実行)

テスト対象のメソッドを呼び出し、実際の結果を取得するフェーズです。

原則として、このフェーズでは1つの操作のみを行うようにし、テストの焦点を絞ることが重要です。

Assert(検証)

実行結果が期待通りであるかをチェックするフェーズです。

xUnitのAssertクラスが提供する様々なメソッドを使用して、値の一致や例外の発生を確認します。

C#
[Fact]
public void Withdraw_ValidAmount_ShouldDecreaseBalance()
{
    // Arrange (準備)
    var account = new BankAccount(1000);

    // Act (実行)
    account.Withdraw(200);

    // Assert (検証)
    Assert.Equal(800, account.Balance);
}

この構造を徹底することで、第三者がテストコードを読んだ際にも、何をテストしているのかが一目で理解できるようになります。

xUnitでよく使われる検証メソッド(Assertions)

xUnitには、結果を検証するための豊富なメソッドが用意されています。

代表的な検証メソッドとその用途を以下の表にまとめました。

メソッド名用途
Assert.Equal(expected, actual)期待値と実際の値が等しいかを確認します。
Assert.NotEqual(expected, actual)期待値と実際の値が異なることを確認します。
Assert.True(condition)条件が真であることを確認します。
Assert.False(condition)条件が偽であることを確認します。
Assert.Null(object)オブジェクトがnullであることを確認します。
Assert.NotNull(object)オブジェクトがnullでないことを確認します。
Assert.Throws<TException>(action)特定の例外が発生することを確認します。

特にAssert.Throwsは、異常系のテスト(エラーハンドリングの検証)において非常に重要です。

C#
[Fact]
public void Withdraw_NegativeAmount_ShouldThrowArgumentException()
{
    var account = new BankAccount(1000);

    // 例外が発生することを検証
    Assert.Throws<ArgumentException>(() => account.Withdraw(-50));
}

外部依存関係の切り離しとモックの活用

実務的なアプリケーションでは、データベースや外部APIと連携するクラスが多いため、そのままでは純粋な単体テストが困難です。

このような場合、インターフェースを利用した依存性の注入(DI)と、モックライブラリを組み合わせて使用します。

2026年現在でも、NSubstituteMoqといったライブラリが主流として使われています。

モックを使用することで、外部サービスの状態に左右されず、テスト対象のロジックのみを分離して検証できるようになります。

C#
// NSubstituteを使用した例
[Fact]
public void SendWelcomeEmail_ShouldCallEmailService()
{
    // Arrange
    var mockEmailService = Substitute.For<IEmailService>();
    var userService = new UserService(mockEmailService);

    // Act
    userService.RegisterUser("test@example.com");

    // Assert
    // 指定したメソッドが呼び出されたかを確認
    mockEmailService.Received().SendEmail("test@example.com", Arg.Any<string>());
}

本物のデータベースに接続せずにテストを実行できるため、実行速度が劇的に向上し、環境に依存しないテストが可能になります。

非同期メソッドのテスト

現代のC#開発では、async/awaitを用いた非同期処理が標準的です。

xUnitは非同期テストをネイティブにサポートしており、テストメソッドの戻り値をTaskにすることで簡単に記述できます。

C#
[Fact]
public async Task FetchDataAsync_ShouldReturnData()
{
    // Arrange
    var apiClient = new MyApiClient();

    // Act
    var result = await apiClient.GetDataAsync();

    // Assert
    Assert.NotNull(result);
}

テストメソッドをasync voidにしないよう注意してください(xUnitが正しく完了を待機できなくなるためです)。

常にasync Taskを使用することで、非同期処理の完了を確実に待ってから検証が行われるようになります。

テストのメンテナンス性を高めるためのベストプラクティス

テストコードが増えてくると、その管理自体が負担になることがあります。

メンテナンス性を維持するために、いくつかの重要なルールを守ることが推奨されます。

1. テストメソッドに明確な命名を行う

「何を」「どのような条件で」「どうなるべきか」が分かる名前を付けましょう。

例えば、Add_WithTwoPositiveNumbers_ReturnsCorrectSumのような命名形式が一般的です。

2. テストコードでの論理演算を避ける

テストコード内にif文やfor文などの複雑なロジックを記述してはいけません。

テスト自体にバグが混入する原因となり、信頼性が損なわれてしまいます。

3. 各テストを独立させる

テスト実行の順番に依存するようなコードは避けてください。

xUnitはデフォルトでテストを並列実行するため、静的変数などを通じて状態を共有すると、予期せぬ失敗を招く恐れがあります。

まとめ

xUnitは、C#開発者が高品質なコードを維持するための強力な武器となります。

基本となる[Fact][Theory]の使い分けを理解し、AAAパターンに則った記述を心がけることで、誰にとっても読みやすく信頼性の高いテストセットを構築できます。

また、インターフェースとモックを適切に活用することで、複雑なビジネスロジックであっても確実な検証が可能になります。

2026年の高度化した開発シーンにおいても、自動テストの習慣はエンジニアの市場価値を高める重要なスキルであり続けるでしょう。

まずは小さなメソッドのテストから書き始め、徐々にテスト駆動開発(TDD)のサイクルを取り入れてみてください。