C#入門編(27)自動テスト入門 ~xUnitで「壊れたら気づける」仕組みを作る~
C#入門編です。今回はC#における自動テストについて解説します。
「コードを変更するたびに、手動で動作確認するのが大変…」「いつの間にか動かなくなっていた…」という経験はありませんか?
本記事は以下の方に役立つ内容となっています。
- テスト自動化の必要性を理解したい方
- xUnitを使ったテストの書き方を学びたい方
- 実務で役立つテストの考え方を身につけたい方
テスト自動化の本質は、壊れたことを自動で検知できる仕組みを作ることです。まずは1本、テストを書いてみましょう!
演習コードをGitHubで公開してます。
動画も作成しています。
講義1:テスト自動化とxUnitの基本
手動確認の限界
アプリを開発するとき、「ちゃんと動くかな?」と確認しますよね。
例えば、商品の割引価格を計算するアプリを作ったとします。手動で確認するなら、こんな流れになるでしょう。(これを、様々なパターンで繰り返します。)
- アプリを起動する
- 価格に「1000」を入力する
- 割引後の価格が正しく表示されることを目視で確認する
さらに大変なのは、コードを修正するたびに、この確認を最初からやり直す必要があることです。
修正によって、今まで動いていた部分が壊れてしまうことをリグレッション(デグレード) といいます。
アプリの品質を維持しつつ開発を続けていくには、このリグレッションを効率よく発見・修正する必要があるのです。
アプリを少し修正するたびにいろいろなパターンで動作確認をする…ってかなり大変だね!
自動テストの価値
手動では大変なテストですが、テストをコードとして書いて自動実行できるようにしておくと、状況が一変します。
- ボタン一つで自動確認できる
- コード修正後も、すぐに問題がないか確認できる
- 再現可能な検証手段がコードとして残る
テスト自動化の価値は、以下です。
既存の期待される振る舞いが壊れたことを自動で検知できること
(結果として、リグレッションを恐れずにアプリケーションを修正できる)
何をテストするか
テストを書くときは、「どのモジュール(または組み合わせ)で、どのような動作を確認したいか」を決めることが重要です。
例えば、割引価格を計算するアプリなら、以下のようなテストが考えられます。
| 確認したいこと | テスト対象 |
|---|---|
| アプリ全体が正しく動くか | アプリ全体(設定読み込み→計算→出力) |
| 割引計算のロジックは正しいか | 計算ロジックのみ |
| 設定ファイルから値を読み込めるか | 設定読み込み部分のみ |
テストの「粒度」(どこまでの範囲をテストするか)には、よく以下のような分類が使われます。
- 単体テスト(Unit Test):(例)メソッドやクラスなど小さな単位
- 統合テスト(Integration Test):(例)ビジネスロジック+DB+外部API連携
- 総合テスト(System Test) :(例)システム全体の動作(性能確認含む)
- EndToEndテスト:(例)ユーザ操作の流れが通るかの確認
ただし、これらの分類は後付けの整理ラベルに過ぎません。境界は厳密に定義されているわけではなく、プロジェクトによって解釈が異なります。
大切なのは分類ではなく、「何を確認したいか」を考えてテストを書くことです。
xUnitとは
C#でテストを書くためのフレームワークはいくつかあります。
- xUnit: モダンな設計で、シンプルかつ拡張性が高い
- NUnit: 長い歴史と豊富な属性による柔軟なテスト記述
- MSTest: Microsoft純正で、Visual Studioなどとの統合に強い
自動テストの初心者や新規開発であれば、.NETコミュニティで広く使われていて、シンプルで学びやすいxUnitがおすすめです。
本記事でもxUnitを使います。
「xUnit」という名前に「Unit」が含まれていますが、これは歴史的な経緯でついたものです。
xUnitは単体テストだけでなく、統合テストやE2Eテストなど、あらゆる自動テストに利用できる汎用的なフレームワークです。
xUnitの主要な概念
xUnitでテストを書くために知っておくべき主要な概念を紹介します。
- [Fact]属性 – 「このメソッドはテストである」ことを示します。
- [Theory]属性と[InlineData] – 複数のデータでテストを実行したいときに使います。
- Assertクラス – 結果が期待通りかを検証します。
最もシンプルなテストコードの例を見てみましょう。
[Fact]
public void Add_WithTwoNumbers_ReturnsSum()
{
// Arrange(準備)
var calculator = new Calculator();
// Act(実行)
var result = calculator.Add(2, 3);
// Assert(検証)
Assert.Equal(5, result);
}テストメソッドの命名には「テスト対象名_条件_期待結果」というパターンがよく使われます。(テスト対象は粒度によってメソッド・機能・シナリオと色々あります)
上の例では「Addメソッドが、2つの数値を受け取ったとき、合計を返す」ことを表しています。この命名規則により、テストが失敗したときに何が問題かすぐにわかります。
また、テストコードはこのようにAAAパターン(Arrange-Act-Assert)で書くのが一般的です。
| フェーズ | 説明 |
|---|---|
| Arrange(準備) | テストに必要なオブジェクトやデータを用意する |
| Act(実行) | テスト対象の処理を呼び出す |
| Assert(検証) | 結果が期待通りか確認する |
それでは演習で、実際にテストを書いてみましょう!
演習:計算機アプリのテストを書こう
演習として計算機アプリのテストを書いてみます。
概要
以下の機能を持つ計算機クラスを作成し、テストを書きます。
- Add – 足し算
- Divide – 割り算(0除算時は例外)
- CalculateWithTax – 設定ファイルから税率を読み込み、税込価格を計算
テストとしては、「設定ファイルからの読み込みを含めたアプリ全体の動作」を確認するものを作ります。
手順1:プロジェクト構成
Visual Studioで以下の手順でプロジェクトを作成します。
- 1.コンソールアプリプロジェクト
CalcAppを作成 - 2.ソリューションを右クリック →「追加」→「新しいプロジェクト」
- 3.「xUnit テスト プロジェクト」を選択し、
CalcApp.Testsという名前で作成 - 4.
CalcApp.Testsを右クリック →「追加」→「プロジェクト参照」→CalcAppにチェック
この手順で次のような構成になります。

テストコードとプロダクトコードは、別のプロジェクトに分けるんだね!
C#プロジェクトの作り方や、Visual Studioの使い方の基本については以下の記事も参考にしてください。
手順2:プロダクトコード作成
CalcAppプロジェクトの直下で以下のファイルを作成し、計算機アプリを構成するクラスを作成します。
ITaxRateLoader.cs – 税率読み込み機能のインターフェイス
namespace CalcApp;
public interface ITaxRateLoader
{
int GetTaxRatePercent();
}
TaxRateLoader.cs – 設定ファイルから税率を読み込む
namespace CalcApp;
public class TaxRateLoader : ITaxRateLoader
{
private readonly string _filePath;
public TaxRateLoader(string filePath = "taxrate.txt")
{
_filePath = filePath;
}
public int GetTaxRatePercent()
{
var text = File.ReadAllText(_filePath);
return int.Parse(text.Trim());
}
}Calculator.cs – 計算機本体
namespace CalcApp;
public class Calculator
{
private readonly ITaxRateLoader _taxRateLoader;
public Calculator(ITaxRateLoader taxRateLoader)
{
_taxRateLoader = taxRateLoader;
}
public int Add(int a, int b)
{
return a + b;
}
public int Divide(int a, int b)
{
if (b == 0)
throw new DivideByZeroException();
return a / b;
}
public decimal CalculateWithTax(decimal price)
{
var taxRate = _taxRateLoader.GetTaxRatePercent();
return price * (100 + taxRate) / 100;
}
}taxrate.txtを作成し、内容を 10 とします(税率10%)。
10プロジェクトファイル(CalcApp.csproj)に以下を追加して、ビルド時に出力フォルダへコピーされるようにします。
...
<ItemGroup>
<None Update="taxrate.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
...
テストプロジェクトからCalcAppを参照しているので、この設定だけでテスト実行時にもtaxrate.txtが利用できます。
CalculatorへTaxRateLoaderをコンストラクタで注入しています。これは依存性注入(DI)の考え方ですね。以下の記事も参考にしてください。
インターフェイスにすることで、税率読み込み機能について様々な実装(例:設定ファイル読み込み)に差し替え可能です。以下も参考にしてください。
これでプロダクトコードは完成です。次にテストを書いていきましょう!
手順3:はじめてのテストを書いて実行
CalcApp.TestsプロジェクトにあるUnitTest1.csをCalculatorTests.csにリネームし、以下のように編集します。
まずは各機能に対して1つずつ、基本的なテストを書いてみましょう。
namespace CalcApp.Tests;
using CalcApp;
public class CalculatorTests
{
[Fact]
public void Add_WithTwoNumbers_ReturnsSum()
{
// Arrange(準備)
var taxRateLoader = new TaxRateLoader();
var calculator = new Calculator(taxRateLoader);
// Act(実行)
var result = calculator.Add(2, 3);
// Assert(検証)
Assert.Equal(5, result);
}
[Fact]
public void Divide_WithValidInputs_ReturnsQuotient()
{
// Arrange
var taxRateLoader = new TaxRateLoader();
var calculator = new Calculator(taxRateLoader);
// Act
var result = calculator.Divide(10, 2);
// Assert
Assert.Equal(5, result);
}
[Fact]
public void Divide_WithZeroDivisor_ThrowsDivideByZeroException()
{
// Arrange
var taxRateLoader = new TaxRateLoader();
var calculator = new Calculator(taxRateLoader);
// Act & Assert
Assert.Throws<DivideByZeroException>(() => calculator.Divide(10, 0));
}
[Fact]
public void CalculateWithTax_WithValidPrice_ReturnsPriceWithTax()
{
// Arrange
var taxRateLoader = new TaxRateLoader();
var calculator = new Calculator(taxRateLoader);
// Act
var result = calculator.CalculateWithTax(1000m);
// Assert
Assert.Equal(1100m, result); // 税率10%: 1000 * 1.1 = 1100
}
}ここまでで以下のような構成になります。

Visual Studioの「テスト」メニュー →「テストエクスプローラー」を開きます。
「すべてのテストを実行」(Ctrl+R, A)をクリックすると、テストが実行されます。

すべてのテストが緑色になることを確認しましょう。
4つのテストが全部緑になった!
ボタン一つで全テストを実行できます。コードを修正したら、すぐにテストを実行する習慣をつけられるとよいですね。
Gitによる版管理と組み合わせることで、安全にコード修正を行えます!
Gitの使い方については以下の記事も参考にしてください。
手順4:[Theory]でテストを発展させる
基本テストが書けたら、複数のパターンをテストしたくなりますよね。
[Theory]と[InlineData]を使うと、1つのメソッドで複数のデータをテストできます。
AddとCalculateWithTaxのテストを発展させてみましょう。以下のテストをCalculatorTests.csに追加します。
...
[Theory]
[InlineData(2, 3, 5)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
[InlineData(100, 200, 300)]
public void Add_WithVariousInputs_ReturnsExpectedSum(
int a, int b, int expected)
{
// Arrange
var taxRateLoader = new TaxRateLoader();
var calculator = new Calculator(taxRateLoader);
// Act
var result = calculator.Add(a, b);
// Assert
Assert.Equal(expected, result);
}
...
[Theory]
[InlineData(1000, 1100)] // 1000円 → 1100円
[InlineData(500, 550)] // 500円 → 550円
[InlineData(0, 0)] // 0円 → 0円
public void CalculateWithTax_WithVariousPrices_ReturnsExpected(
decimal price, decimal expected)
{
// Arrange
var taxRateLoader = new TaxRateLoader();
var calculator = new Calculator(taxRateLoader);
// Act
var result = calculator.CalculateWithTax(price);
// Assert
Assert.Equal(expected, result);
}
...テストを実行すると、テストエクスプローラーでは各[InlineData]が個別のテストとして表示されます。

1つのメソッドで複数パターンをテストできるんだ!これは便利!
手順5:リグレッションを検出してみる
テストの威力を体感するため、わざとバグを仕込んでみましょう。Calculator.csのAddメソッドを誤った実装にしてみましょう。
public int Add(int a, int b)
{
return a - b; // + を - に変更(バグ)
}テストを実行すると…赤い×マークが表示され、テストが失敗します。

このように、コードを壊してしまっても、テストがすぐに教えてくれます。これが「壊れたら気づける」仕組みです!
(確認できたら、コードを元に戻しておきましょう。)
演習の振り返りと次回予告
今回は、以下のようなテストを作成しました。
| テスト対象 | 外部依存 |
|---|---|
| Add(足し算) | なし |
| Divide(割り算) | なし |
| CalculateWithTax(税込計算) | ファイル(taxrate.txt) |
AddやDivideは外部依存がないため、テストが書きやすく、高速に実行できます。
一方、CalculateWithTaxはtaxrate.txtを読み込むため、ファイルがないとテストが動きません。
また、税率5%や20%など様々なケースをテストしたい場合、ファイルの内容を毎回変更する必要があり不便です。
たしかに、ファイルに依存するテストは不便だね…。どうすればいいの?
この問題を解決するのが「モック」というテクニックです。次回、詳しく解説します。
あわせて、初心者が最初に自動テストを導入する際に「何からはじめればよいか?」についても説明します!
まとめ
今回は自動テストの基本を学びました。
- テスト自動化の価値は「壊れたことを自動で検知できる」こと
- xUnitは.NETで広く使われる汎用的なテストフレームワーク
- テストとは「どのモジュールで、どのような動作を確認したいか」を決めて実施するもの
- [Fact] で単一のテスト、[Theory] と [InlineData] でパラメータ化テスト
- AAAパターン(Arrange-Act-Assert)でテストを構造化
次回は「モック」を使って、ファイルやDBなどの外部依存を切り離してテストする方法を学びます。これにより、より柔軟にテストを書けるようになりますよ!
引き続き、一緒にC#プログラミングについて学んでいきましょう。





