C#入門編(28)自動テストは何から始める?モックとスモークテストの使いどころ
C#入門編です。今回はモックとスモークテストについて解説します。
前回の記事で自動テストの基本を学びましたが、「ファイルに依存するテストは不便」という課題が残りました。
本記事は以下の方に役立つ内容となっています。
- モックの概念と使いどころを理解したい方
- NSubstituteを使ったモックの書き方を学びたい方
- 自動テストを「何から始めればよいか」を知りたい方
モックはテストの目的を達成するための手段(便利な道具)です。必要なときに使えるようになりましょう!
また、初めて自動テストを導入するなら「スモークテスト」から始めるのがおすすめです。
前回の記事をまずみてもらえると、より理解が深まるかと思います。
演習コードをGitHubで公開しています。
講義1:モックとは何か
前回の課題を振り返る
前回の演習で、CalculateWithTaxメソッドのテストを書きました。設定ファイルからの税率読み込みを含めた動作を確認するテストを行っています。
[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
}このように外部ファイルを直接使う方法ですと、以下のようなテスト目的で課題が生じます。
- (1)テスト目的:様々な税率で計算ロジックの正しさを確認したい
→課題:税率5%や20%など、様々なケースをテストするにはファイルの内容を変更する必要があり、手間がかかる - (2)テスト目的:ファイルI/Oエラーの場合の挙動を確認したい
→課題:ファイルI/Oエラーを起こすには、ファイルを削除するなどの操作を適切なタイミングで行う必要があり、難しい場合がある
本当にテストしたいのは「税込計算のロジック」なのに、ファイルの準備が必要なのは面倒だね…。
ファイルアクセスだけではなく、外部APIの呼び出し、データベースアクセスなど様々な状況でこのような課題が発生します。
モックの概念
この課題を解決するのがモックです。モックとは、テスト対象が依存する部品の「代役」となるオブジェクトのことです。
本物の代わりにモックを使うことで、テスト対象を外部依存から切り離して検証できます。以下のようなイメージです。
- 【本番環境】
Calculator → TaxRateLoader → taxrate.txt(ファイル) - 【テスト環境(モック使用)】
Calculator → モック(任意の税率を返す)
モックを使うと、以下のメリットがあります。
| メリット | 説明 |
|---|---|
| 環境構築が不要 | ファイルやDBがなくてもテストできる |
| テストが高速 | ファイルI/Oやネットワーク通信が不要 |
| 自在にコントロール | 任意の値や例外を返せる |
| 原因究明が容易 | 問題の切り分けがしやすい |
インターフェイスと手動モック
前回の演習で、ITaxRateLoaderインターフェイスを定義しました。
public interface ITaxRateLoader
{
int GetTaxRatePercent();
}このインターフェイスを実装した「偽物」のクラスを自分で作ることで、モックを実現できます。
// 手動で作成したモック(テスト用の偽実装)
public class FakeTaxRateLoader : ITaxRateLoader
{
private readonly int _taxRate;
public FakeTaxRateLoader(int taxRate)
{
_taxRate = taxRate;
}
public int GetTaxRatePercent()
{
return _taxRate;
}
}このモックを使えば、ファイルなしでテストできます。
[Fact]
public void CalculateWithTax_WithTaxRate5Percent_ReturnsCorrectPrice()
{
// Arrange - 税率5%を返すモックを使用
var fakeTaxRateLoader = new FakeTaxRateLoader(5);
var calculator = new Calculator(fakeTaxRateLoader);
// Act
var result = calculator.CalculateWithTax(1000m);
// Assert
Assert.Equal(1050m, result); // 1000 * 1.05 = 1050
}インターフェイスを使って依存性を注入(DI)していたからこそ、モックに差し替えられるのです。設計の効果がここで活きてきます。
インターフェイス・依存性注入については以下も参考にしてください。
モックライブラリを使う
手動でモッククラスを作るのは基本ですが、テストケースごとに異なる振る舞いが必要な場合、毎回クラスを作るのは手間がかかります。
そこで便利なのがモックライブラリです。C#では以下のライブラリがよく使われます。
- Moq:長い歴史があり、広く使われている
- NSubstitute:シンプルで直感的な記法が特徴
本記事では、記法がシンプルで学びやすいNSubstituteを使います。NSubstituteを使うと、先ほどの手動モックと同じことが1行で書けます。
// NSubstituteを使ったモック作成
var taxRateLoader = Substitute.For<ITaxRateLoader>();
taxRateLoader.GetTaxRatePercent().Returns(5); // 5%を返すように設定クラスを定義する必要がなく、テストメソッド内で直接振る舞いを設定できます。
これなら、テストケースごとに違う税率を簡単に試せるね!
演習:NSubstituteでファイル読み込みをモック化する
前回記事の演習コードを改良してNSubstituteによるモック化を実践し、以下のテストを作成してみましょう。
- (1)様々な税率で計算ロジックの正しさを確認
- (2)ファイルI/Oエラーの場合の挙動を確認
手順1:NSubstituteのインストール
前回コードのソリューション構成は以下のようになっていました。

まず、テストプロジェクト(CalcApp.Test)で、以下を行います。
- NSubstituteをインストール
あと、前回の演習では「xunit」パッケージを使っていたのですが、こちらはメンテナンスが終了しており、「xunit.v3」の使用が奨励されているため、以下のように入れ替えます。
- xunitをアンインストール
- xunit.v3をインストール
バージョンをあげても、テストコードはそのまま動くの大丈夫です。
xUnitのプロジェクトひな型が、xunit(v2系)を使っていたようです。
NuGetパッケージマネージャで、以下のように非推奨マークがでていたら、チェックするようにしましょう。

手順2:ITaxRateLoaderをモック化
CalculatorTests.csを編集し、まずは練習としてモックを使った簡単なテストを追加して実行します。
namespace CalcApp.Tests;
using CalcApp;
using NSubstitute;
public class CalculatorTests
{
...
[Fact]
public void CalculateWithTax_WithMockedTaxRate10Percent_ReturnsCorrectPrice()
{
// Arrange - モックを作成し、税率10%を返すように設定
var taxRateLoader = Substitute.For<ITaxRateLoader>();
taxRateLoader.GetTaxRatePercent().Returns(10);
var calculator = new Calculator(taxRateLoader);
// Act
var result = calculator.CalculateWithTax(1000m);
// Assert
Assert.Equal(1100m, result);
}
}このテストはtaxrate.txtファイルがなくても動作します。この追加したテストを実行し、テストが成功することを確認しましょう。

テストエクスプローラ上でテストを選択して「右クリック>実行」で、個別にテストを実行することができます。
手順3:(1)様々な税率パターンをテスト
次に、[Theory]と[InlineData]を組み合わせて、様々な税率パターンをテストしてみましょう。
[Theory]
[InlineData(1000, 5, 1050)] // 税率5%: 1000 → 1050
[InlineData(1000, 8, 1080)] // 税率8%: 1000 → 1080
[InlineData(1000, 10, 1100)] // 税率10%: 1000 → 1100
[InlineData(500, 10, 550)] // 税率10%: 500 → 550
[InlineData(0, 10, 0)] // 税率10%: 0 → 0
public void CalculateWithTax_WithVariousTaxRates_ReturnsExpected(
decimal price, int taxRate, decimal expected)
{
// Arrange
var taxRateLoader = Substitute.For<ITaxRateLoader>();
taxRateLoader.GetTaxRatePercent().Returns(taxRate);
var calculator = new Calculator(taxRateLoader);
// Act
var result = calculator.CalculateWithTax(price);
// Assert
Assert.Equal(expected, result);
}テストを実行して成功することを確認しましょう。

手順4:(2)ファイルI/Oエラーのテスト
モックを使うと、エラーケースのテストも簡単です。例えば、「税率の読み込みに失敗した場合、例外がスローされる」ことを確認できます。
[Fact]
public void CalculateWithTax_WhenTaxRateLoadingFails_ThrowsException()
{
// Arrange - 例外をスローするモックを設定
var taxRateLoader = Substitute.For<ITaxRateLoader>();
taxRateLoader.GetTaxRatePercent().Returns(x => throw new IOException("ファイル読み込みエラー"));
var calculator = new Calculator(taxRateLoader);
// Act & Assert
Assert.Throws<IOException>(() => calculator.CalculateWithTax(1000m));
}本物のファイルでこのテストを行おうとすると、ファイルを削除したり権限を変更したりする必要があります。モックなら、テストコード内で簡単に異常系を再現できます。
今回は例外がスローされたことを確認しているだけですね。
実務アプリでは、例外発生に伴う様々な処理(エラーメッセージ出力や後始末)が適切に行われるかをチェックすることになります。
モックを使うとことで、様々なパターンの税率でテストや、エラーケースのテストが簡単に作れるようになったよ!
講義2:自動テストを何から始めるか
開発におけるペインの解消
初めて自動テストを導入する場合、自分の開発における「ペイン(苦痛)」を解消するテストから始めるのがおすすめです。
誰しもが感じるペインとしては、例えば「開発で繰り返し行う面倒な作業」があります。
面倒な作業の代表例として「少し修正するたびに、アプリを起動して正常に動くか確認する」があるでしょう。この確認を自動化したものを「スモークテスト」と呼びます。
スモークテストとは、アプリ全体(または中枢部分)が壊れていないことを即座に検知するためのテストです。
スモークテストの語源は電子機器の動作確認で「電源を入れて煙が出ないか確認する」ことに由来します。
スモークテストの始め方
スモークテストを書く際は、「壊れていないことを素早く確認できる」層を選ぶのが現実的です。
| 層 | 特徴 |
|---|---|
| UI(E2E) | ユーザ操作を忠実に再現できるが、環境構築が大変で壊れやすい |
| API | 環境構築が比較的容易で、安定したテストを書きやすい |
| ビジネスロジック | 最も安定しており、高速にテストできる |
多くの場合、API層やビジネスロジック層から始めるのが現実的です。UIのE2Eテストは環境構築が複雑で、UIの変更に弱いため、最初の一歩としてはハードルが高くなりがちです。
Webアプリだったら、主要なユーザシナリオ(正常系)で使われるAPIの動作を確認するテストを1個ずつ用意する、といった方法があります。
スモークテストは「入口」である
スモークテストは網羅性を担保するものではありません。あくまで「壊れていないことの最低限の確認」です。
しかし、テスト自動化の価値を即座に体感できるという点で、最初の一歩として最も重要です。
スモークテストで「ボタン一つで確認できる」体験をすると、自然と次のステップに進みたくなります。
- 「このケースも自動化したい」→ テストケースの拡充
- 「この部分は切り離してテストしたい」→ より小さい部品を単体でテスト
- 「外部依存を排除したい」→ モックの活用
スモークテストは、テスト自動化を継続・拡張していくための「入口」なのです。
モックの使いどころを判断する
原則として、どの粒度のテストであっても、本物の外部依存(ファイル、DB等)を使ってテストして構いません。
ただし、本物の外部依存を使うとテストが書きにくくなる場合は、モックの出番です。モックを使うかどうかは、以下の観点で判断します。
| 困りごと | なぜ困るか | 対応 |
|---|---|---|
| テストが遅い | DB・外部APIなどのI/Oで実行時間がかかる | 依存部分をモックする |
| 環境構築が大変 | APIキーや専用DBが必要 | モックで代替する |
| 再現が難しい | エラーやタイミング依存の挙動を再現しづらい | モックで意図的に振る舞いを制御する |
| 原因を切り分けたい | どこで失敗しているか分かりにくい | 依存部分をモックして影響範囲を限定する |
(注意:ただし、テストしたい部分までモック化しないように注意)
モックはあくまで手段です。「何をテストしたいか」を明確にした上で、必要なら使う、という判断をしましょう。
例えば、メソッドレベルの「いわゆる単体テスト」であっても、データベース・外部API呼びだしをそのまま使うという判断はありえます。
(※テスト実行時間・再現性・環境コストなどを許容できる場合)
まとめ
今回はモックの基本と、自動テストの始め方を学びました。
モックは、テスト対象を外部依存から切り離すための「代役」です。インターフェイスを定義しておけば、手動で偽の実装クラスを作ることでモックを実現できます。
ただし、テストケースごとに異なる振る舞いが必要な場合は、NSubstituteなどのモックライブラリを使うと簡潔に書けます。
自動テストを始めるなら、まずはスモークテストからがおすすめです。「小さな単体テストから積み上げる」のではなく、開発のペインを解消するテストから始めましょう。
スモークテストは、アプリ全体(または中枢部分)が壊れていないことを即座に検知するためのテストです。
網羅性を担保するものではありませんが、テスト自動化の価値を体感し、継続・拡張につなげるための「入口」として最も重要な役割を果たします。
モックを使うかどうかは、テストの速度・原因究明のしやすさ・環境構築コストを考慮して判断します。
「何をテストしたいか」という目的が先にあり、モックはそれを実現するための手段です。
引き続き、一緒にC#プログラミングや自動テストについて学んでいきましょう!






