C#を用いたシステム開発において、品質を維持しながら開発スピードを加速させるためには、ユニットテストの自動化が不可欠な要素となっています。
2026年現在のモダンな開発現場では、テストフレームワークとしてxUnitを採用し、依存関係の分離にMoqを活用する構成が標準的な選択肢となっています。
本記事では、C#におけるユニットテストの基礎から、xUnitとMoqを組み合わせた実践的なテスト手法について詳しく解説します。
ユニットテストの重要性と導入のメリット
ユニットテストとは、プログラムを構成する最小単位である「メソッド」や「クラス」が、意図通りに動作するかを検証する作業を指します。
手動での動作確認に頼る開発スタイルでは、コードの修正を行うたびに影響範囲の特定に多大な時間がかかり、デグレードのリスクも増大します。
ユニットテストを自動化することで、コードの変更が既存の機能に悪影響を与えていないかを即座に確認できるようになります。
また、テストコードを書くプロセスそのものが、設計の改善に寄与するという側面も無視できません。
テストが書きにくいコードは、多くの場合、クラス間の結合度が強すぎるか、一つのクラスに責務が集中しすぎているという予兆です。
テストコードの作成を通じて、自然と疎結合で保守性の高い設計へと導かれるのがユニットテストの大きなメリットです。
xUnitによるテスト環境の構築
C#で利用できるテストフレームワークにはMSTest、NUnit、xUnitの3つが主に挙げられますが、現在の主流はxUnitです。
xUnitは、テストの並列実行を標準でサポートしており、高速なフィードバックを得られる点が特徴です。
まずは、.NET CLIを使用してテストプロジェクトを作成する方法を確認しましょう。
dotnet new xunit -n MyProject.Tests
このコマンドを実行すると、xUnitのライブラリが最初から参照されたテストプロジェクトが生成されます。
Visual StudioやVisual Studio Codeを使用している場合は、ソリューションに「xUnitテストプロジェクト」を追加することでも簡単に開始できます。
Fact属性による基本的なテスト作成
xUnitにおいて、最も基本的なテストケースを定義するのが[Fact]属性です。
[Fact]は、常に真であるべき単一のテストシナリオを記述する際に使用します。
以下のコードは、単純な加算を行う計算機クラスのテスト例です。
using Xunit;
public class CalculatorTests
{
[Fact]
public void Add_TwoNumbers_ReturnsSum()
{
// Arrange (準備)
var calculator = new Calculator();
// Act (実行)
int result = calculator.Add(10, 20);
// Assert (検証)
Assert.Equal(30, result);
}
}
public class Calculator
{
public int Add(int x, int y) => x + y;
}
テストを実行すると、Assert.Equalによって期待値(30)と実際の値(result)が比較されます。
テスト結果は以下のように出力されます。
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Theory属性とInlineDataによるデータ駆動テスト
複数の入力パターンに対して同じテストロジックを適用したい場合は、[Theory]属性と[InlineData]属性を使用します。
これにより、テストコードの重複を避けつつ、網羅性の高い検証が可能になります。
public class CalculatorTests
{
[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
public void Add_MultipleInputs_ReturnsExpectedSum(int a, int b, int expected)
{
var calculator = new Calculator();
int result = calculator.Add(a, b);
Assert.Equal(expected, result);
}
}
Theoryを活用することで、境界値分析などのテストケースを簡潔に表現できるため、複雑なバリデーションロジックのテストには非常に効果的です。
Moqを用いた依存関係の分離
実際の開発では、テスト対象のクラスがデータベースや外部API、ファイルシステムに依存していることが多々あります。
これらの外部環境に依存したままテストを行うと、実行速度が低下したり、環境構築の手間が発生したり、テストの結果が不安定になったりします。
そこで役立つのが、モックライブラリであるMoqです。
Moqを使用すると、インターフェースに基づいた「偽のオブジェクト」を動的に生成し、その振る舞いを自由に定義できます。
Moqの基本的なセットアップ
Moqを利用するには、NuGetパッケージマネージャーからMoqパッケージをインストールします。
dotnet add package Moq
ここでは、ユーザー情報を取得するサービスが、外部のリポジトリに依存しているケースを想定してみましょう。
public interface IUserRepository
{
string GetUserName(int id);
}
public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
public string GetGreeting(int id)
{
var name = _repository.GetUserName(id);
return $"Hello, {name}!";
}
}
モックオブジェクトの作成と振る舞いの定義
UserServiceのテストにおいて、IUserRepositoryの実装クラスを実際に作成する必要はありません。
Moqを使用して、特定のIDが渡された時に期待する文字列を返すように設定します。
using Moq;
using Xunit;
public class UserServiceTests
{
[Fact]
public void GetGreeting_ValidId_ReturnsGreetingMessage()
{
// Arrange
var mockRepo = new Mock<IUserRepository>();
// 特定のメソッド呼び出しに対する戻り値を設定
mockRepo.Setup(repo => repo.GetUserName(1)).Returns("Taro");
var service = new UserService(mockRepo.Object);
// Act
var result = service.GetGreeting(1);
// Assert
Assert.Equal("Hello, Taro!", result);
}
}
このように、Setup()メソッドとReturns()メソッドを組み合わせることで、テストに必要な状況をプログラムから注入できます。
例外のシミュレーションと検証
Moqは正常系のテストだけでなく、異常系のテストでも威力を発揮します。
たとえば、リポジトリが例外をスローした場合の動作をテストしたいときは、Throws()メソッドを使用します。
[Fact]
public void GetGreeting_RepositoryThrowsException_PropagatesException()
{
// Arrange
var mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(repo => repo.GetUserName(It.IsAny<int>()))
.Throws(new System.Exception("DB Error"));
var service = new UserService(mockRepo.Object);
// Act & Assert
Assert.Throws<System.Exception>(() => service.GetGreeting(1));
}
また、Verify()メソッドを使用すれば、特定のメソッドが「何回呼び出されたか」まで厳密に検証することが可能です。
// 指定したメソッドが1回だけ実行されたことを検証
mockRepo.Verify(repo => repo.GetUserName(1), Times.Once());
テストの保守性を高めるベストプラクティス
ユニットテストは一度書いて終わりではなく、プロダクトコードと共に成長させていくものです。
保守性の低いテストコードは、リファクタリングの足枷になり、最終的にはメンテナンスされずに放置される「形骸化したテスト」になってしまいます。
Arrange-Act-Assert (AAA) パターンの遵守
テストコードの構造は、常にArrange(準備)、Act(実行)、Assert(検証)の3つのフェーズに分けるべきです。
各フェーズを空行で区切ることで、何に対してどのような条件でテストを行っているかが一目で理解できるようになります。
また、テストメソッドの名前には、「テスト対象メソッド名_テスト時の状態_期待される結果」という命名規則を採用することを推奨します。
DI (依存性の注入) を意識した設計
Moqを効果的に活用するためには、クラス内で直接外部依存インスタンスを生成(new)してはいけません。
依存関係をコンストラクタ経由で受け取る「依存性の注入 (DI)」を採用することで、テスト時にモックと入れ替えることが可能になります。
これはテストのしやすさ(Testability)を高めるだけでなく、プログラム全体の柔軟性を高めることにも繋がります。
効率的なテスト実行とCI/CDへの統合
テストは頻繁に実行されてこそ価値があります。
Visual Studioの「テストエクスプローラー」を使用すれば、コードを保存するたびにバックグラウンドでテストを実行する「ライブユニットテスト」機能を活用できます。
また、GitHub ActionsやAzure DevOpsなどのCI/CDパイプラインにテスト工程を組み込むことは必須です。
プルリクエストが作成された際に自動的に全テストが走り、一つでも失敗した場合はマージできないように制御することで、プロダクション環境へのバグ混入を未然に防ぎます。
| ツール名 | 主な役割 | 特徴 |
|---|---|---|
| xUnit | テストフレームワーク | 並列実行に強く、構文がシンプルで拡張性が高い。 |
| Moq | モックライブラリ | インターフェースから動的にモックを生成し、振る舞いを検証できる。 |
| FluentAssertions | アサーションライブラリ | Assert文を自然言語に近い形式で記述でき、可読性が向上する。 |
| Coverlet | カバレッジ計測 | コードのどの部分がテストされたかを可視化する。 |
まとめ
C#におけるユニットテストは、単なる品質チェックの工程ではなく、優れたソフトウェア設計を実現するための強力なツールです。
xUnitによる柔軟なテスト構造と、Moqによる依存関係の自在なコントロールを習得することで、開発者は自信を持ってコードの変更を行えるようになります。
まずは小さなメソッドからテストを書き始め、徐々にテスト駆動開発(TDD)などのプラクティスを取り入れてみてください。
自動テストによって支えられた堅牢なコードベースは、将来の仕様変更や技術スタックのアップデートにおいても、必ず大きな助けとなります。
効率的なテスト手法を身につけ、信頼性の高いシステム開発を推進していきましょう。
