C# で開発をしていると、単体テストを書く機会は必ず訪れます。
ただ、いざ書こうとすると「どこから手をつければいいんだろう」と迷うことも多いですよね。
この記事では、xUnit を使った単体テストの基本を中心に、
実務でつまずきやすいポイントや、外部依存を扱う際の考え方も交えて紹介します。
xUnit を選ぶ理由
C# のテストフレームワークには MSTest、NUnit、xUnit などがありますが、
xUnit は次のような理由で扱いやすいと感じています。
- コードがシンプルで読みやすい
- [Fact] と [Theory] の使い分けが直感的
- 依存注入(DI)と相性が良い
- .NET の公式テンプレートでも採用されている
まずは基本のテストから見ていきましょう。
最小の単体テストを書いてみる
xUnit の基本をつかむには、まずはシンプルなテストを書くのが一番です。
テスト対象のクラス:
public class Calculator
{
public int Add(int a, int b) => a + b;
}
xUnit のテスト:
using Xunit;
public class CalculatorTests
{
// 足し算が正しく行われることを確認
[Fact]
public void Add_ShouldReturnCorrectSum()
{
// Arrange
var calc = new Calculator();
// Act
var result = calc.Add(2, 3);
// Assert
Assert.Equal(5, result);
}
}
テストを読みやすくする AAA パターンの考え方
単体テストは、書く人だけでなく「読む人」にとっても分かりやすいことが重要です。
そのための基本形として広く使われているのが AAA(Arrange / Act / Assert)パターンです。
● Arrange(準備)
テストに必要なオブジェクトやデータを準備するフェーズです。
- テスト対象のインスタンスを作る
- 入力値を用意する
- 必要ならモックをセットアップする
Arrange が複雑になりすぎる場合、
「クラスの責務が大きすぎるサイン」であることもあります。
● Act(実行)
テスト対象のメソッドを 1 回だけ 呼び出します。
- 余計な処理を入れない
- 1 テストにつき 1 メソッド呼び出しが基本
Act が複数あると、テストの意図が曖昧になりがちです。
● Assert(検証)
Act の結果が期待通りかどうかを確認します。
- 戻り値の検証
- 状態の変化の確認
- 例外の発生確認
よく使う Assert の例
Assert.Equal(expected, actual); // 値の一致
Assert.NotNull(obj); // null でないこと
Assert.True(condition); // 条件が true
Assert.Throws<Exception>(() => method()); // 例外の発生
実務では Assert が増えすぎるとテストが壊れやすくなるため、
「1 テスト 1 アサーション」を基本にすると安定します。
[Theory] を使ったパラメータ化テスト
複数パターンをまとめてテストしたいときに便利なのが [Theory] です。
public class CalculatorTests
{
// 複数パターンで正しく計算されることを確認
[Theory]
[InlineData(1, 2, 3)]
[InlineData(10, 20, 30)]
[InlineData(-5, 5, 0)]
public void Add_ShouldReturnCorrectSum_ForMultiplePatterns(int a, int b, int expected)
{
var calc = new Calculator();
var result = calc.Add(a, b);
Assert.Equal(expected, result);
}
}
境界値や複数パターンの検証が必要な場面で特に役立ちます。
外部依存があるコードをテストしたいときの考え方
単体テストを書いていると、次のようなケースに出会います。
- DB にアクセスする処理がある
- 外部 API を呼び出す
- ファイル I/O が含まれている
- 依存オブジェクトの動きを自由に変えてテストしたい
こうした外部依存があるコードは、そのままテストすると遅くなったり、
環境によって結果が変わったりして、テストが安定しません。
そこで役に立つのが モック(mock) という考え方です。
モックの基本概念
モック(mock)は 「外部依存の代わりに使う偽物オブジェクト」 です。
- DB や API を本当に呼ばずにテストできる
- 戻り値を自由に決められる
- 例外パターンも再現できる
つまり、テストしたいロジックだけに集中するための道具です。
C# では Moq というモックライブラリがよく使われます。
ここでは、実務で最低限知っておくと便利な Moq の使い方を紹介します。
Moq を使った最小のモック例
テスト対象のサービス:
public interface IUserRepository
{
User? GetUser(int id);
}
public class UserService
{
private readonly IUserRepository _repo;
public UserService(IUserRepository repo)
{
_repo = repo;
}
public string GetUserName(int id)
{
var user = _repo.GetUser(id);
return user?.Name ?? "Unknown";
}
}
Moq を使ったテスト:
using Moq;
using Xunit;
public class UserServiceTests
{
// ユーザーが存在する場合は名前を返すことを確認
[Fact]
public void GetUserName_ShouldReturnUserName_WhenUserExists()
{
// Arrange
var mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(x => x.GetUser(1))
.Returns(new User { Id = 1, Name = "Taro" });
var service = new UserService(mockRepo.Object);
// Act
var result = service.GetUserName(1);
// Assert
Assert.Equal("Taro", result);
}
}
Moq の動きを理解する
① new Mock<IUserRepository>()
IUserRepository の「偽物」を作っています。
この時点では中身は空っぽで、何を呼んでも既定値(参照型なら null)を返します。
② Setup(x => x.GetUser(1))
「GetUser(1) が呼ばれたらどう振る舞うか」を定義する準備です。
③ .Returns(new User { ... })
実際に返す値を指定します。
④ mockRepo.Object
モックの “本体” です。
UserService に渡すことで、
本物のリポジトリの代わりにモックが使われる ようになります。
実務でテストを書くときのコツ
- テスト名は仕様を書く
- AAA パターンを徹底する
- Theory で複数パターンを網羅する
- 外部依存はモックで切り離す
- バグを見つけたら再発防止テストを 1 本追加する
テストは「未来の自分を助けるための投資」です。
xUnit の基本を押さえておくだけで、開発が本当に楽になります。