C#テスト入門|単体テストの書き方・実行方法とMSTest/xUnit/NUnitの選び方

はじめに

C#での開発を進める中で、「テストを書くこと」は品質を保つ上で欠かせない作業です。本記事では、C#の単体テストに焦点を当て、初心者でも理解しやすいように基礎知識から実践方法までを解説します。MSTest、xUnit、NUnitといった主要フレームワークの特徴や選び方も紹介し、効率的にテストを活用する方法をまとめました。

1. C#テストとは?検索ユーザーが最初に知りたい基礎知識

1-1. C#における「テスト」の意味とは

C#におけるテストとは、コードが正しく動作するかを確認するプロセスです。テストを行うことで、バグを早期に発見し、品質を維持できます。特に単体テストは、個々のメソッドやクラスの動作を確認することに重点を置きます。

1-2. 単体テスト・結合テスト・E2Eテストの違い

  • 単体テスト:クラスやメソッド単位で動作を確認するテスト

  • 結合テスト:複数のコンポーネントが正しく連携するかを確認

  • E2Eテスト(End-to-End):アプリケーション全体のフローを検証

単体テストは開発初期段階で効率よくバグを防ぐのに適しています。

1-3. なぜC#開発でテストを書くべきなのか

C#のようなオブジェクト指向言語では、複雑なロジックや依存関係が生まれやすく、テストを導入することで以下のメリットがあります。

  • バグを早期に発見できる

  • 保守性の向上

  • リファクタリング時の安全性確保

  • チーム開発での品質統一

1-4. テストを書くことで防げる典型的なバグ

  • 計算や文字列操作の誤り

  • null参照による例外

  • データベースやAPI呼び出しの不整合

  • 条件分岐ロジックの漏れ

単体テストは、こうした問題をコードレベルで防ぐことができます。

2. C#の単体テストでできること・できないこと

2-1. 単体テストの対象になるコード

  • メソッド単位のロジック

  • 計算処理や文字列操作

  • データ変換やフォーマット処理

2-2. 単体テストに向いていない処理

  • UI操作や画面描画

  • ネットワーク・ファイルI/O依存処理

  • データベース接続や外部APIのレスポンス

こうした処理はモックや統合テストで補うことが一般的です。

2-3. 良いテストコードと悪いテストコードの違い

  • 良いテスト:簡潔、再現性が高い、読みやすい

  • 悪いテスト:複雑で依存関係が多い、失敗しやすい、保守困難

2-4. テストしやすいC#コードの特徴

  • 単一責任のクラス・メソッド

  • 依存関係の注入が可能

  • 副作用が少なく純粋な関数に近い

3. C#で単体テストを始めるための準備

3-1. 必要な開発環境

  • Visual Studio または Visual Studio Code

  • .NET SDK

  • テストフレームワーク(MSTest、xUnit、NUnit)

3-2. テストプロジェクトの作成方法

Visual Studioでは「新しいプロジェクト」→「単体テストプロジェクト」を選択します。CLIでは以下のコマンドを使用します。

Bash
dotnet new mstest -n MyProject.Tests

3-3. テスト対象プロジェクトを参照する方法

テストプロジェクトから対象プロジェクトを参照することで、クラスやメソッドを呼び出してテスト可能になります。

Bash
dotnet add reference ../MyProject/MyProject.csproj

3-4. NuGetパッケージとテストアダプターの役割

  • テストフレームワークパッケージ:Assertやテスト属性を提供

  • テストアダプター:IDEでテストを実行するための橋渡し

4. C#単体テストの基本的な書き方

4-1. テストメソッドの基本構造

C#
[TestMethod]
public void Add_TwoNumbers_ReturnsSum()
{
// Arrange
var calculator = new Calculator();

// Act
var result = calculator.Add(2, 3);

// Assert
Assert.AreEqual(5, result);
}

4-2. Arrange・Act・Assertの考え方

  1. Arrange:テスト対象の準備

  2. Act:メソッドを実行

  3. Assert:期待結果と比較

4-3. Assertで結果を検証する方法

  • Assert.AreEqual(expected, actual)

  • Assert.IsTrue(condition)

  • Assert.ThrowsException<ExceptionType>(action)

4-4. 正常系・異常系・境界値のテスト例

  • 正常系:期待通り動作するケース

  • 異常系:例外やエラーを検証

  • 境界値:入力の端値での挙動を確認

4-5. テスト名の付け方と読みやすくするコツ

  • 「メソッド名_条件_期待結果」の形式

  • 意味が分かりやすく短くまとめる

  • 他の開発者が見ても理解できるようにする

5. C#単体テストの実行方法

5-1. Visual Studioでテストを実行する方法

  • 「テスト エクスプローラー」から実行

  • 個別テストや全体テストを選択可能

5-2. .NET CLIでテストを実行する方法

Bash
dotnet test

5-3. Visual Studio Codeでテストを実行する方法

  • 「.NET Test Explorer」拡張機能を使用

  • CLIのdotnet testも利用可能

5-4. テスト結果の見方

  • 緑:成功

  • 赤:失敗

  • 黄色:スキップ

5-5. よくある実行エラーと対処法

  • 参照エラー:プロジェクト参照を確認

  • NuGetパッケージ未導入:必要なパッケージを追加

  • 属性の間違い:正しいテスト属性に修正

6. MSTest・xUnit・NUnitの違い

6-1. 3つのテストフレームワークの概要

  • MSTest:Microsoft純正、Visual Studioとの相性が良い

  • xUnit:最新設計で拡張性が高い

  • NUnit:歴史があり多機能で安定

6-2. MSTestの特徴・メリット・デメリット

  • 特徴:属性が多く公式サポート

  • メリット:Visual Studioとの統合が容易

  • デメリット:拡張性はxUnitに劣る

6-3. xUnitの特徴・メリット・デメリット

  • 特徴:柔軟でモダンな設計

  • メリット:データ駆動テストが簡単

  • デメリット:慣れるまで属性が少し分かりづらい

6-4. NUnitの特徴・メリット・デメリット

  • 特徴:長く使われている安定フレームワーク

  • メリット:幅広いAssert機能、属性が豊富

  • デメリット:セットアップがやや複雑

6-5. 属性・Assert・テスト実行方法の比較

フレームワークテスト属性主なAssert実行方法
MSTest[TestMethod]AreEqual, IsTrueVisual Studio, CLI
xUnit[Fact]Equal, TrueVisual Studio, CLI
NUnit[Test]AreEqual, IsTrueVisual Studio, CLI

6-6. パラメータ化テストの書き方の違い

  • MSTest:[DataRow]

  • xUnit:[Theory] + [InlineData]

  • NUnit:[TestCase]

7. C#テストフレームワークの選び方

7-1. 初心者におすすめの選び方

  • Visual StudioユーザーならMSTest

  • モダンな書き方を学びたいならxUnit

7-2. Visual Studio中心の開発で選ぶ場合

  • MSTestは統合が簡単で手軽

  • CI/CDの構築も容易

7-3. チーム開発・CI/CDを重視する場合

  • xUnitやNUnitの拡張性が有利

  • データ駆動テストや依存関係注入がしやすい

7-4. 既存プロジェクトで選ぶ場合

  • 既存フレームワークに合わせる

  • 移行コストを考慮

7-5. 迷ったときの判断基準

  • 開発環境との相性

  • CI/CDの対応状況

  • チームメンバーの経験

8. 実践例:C#で単体テストを書いてみる

8-1. テスト対象のサンプルコード

C#
public class Calculator
{
public int Add(int a, int b) => a + b;
}

8-2. MSTestでテストを書く例

C#
[TestClass]
public class CalculatorTests
{
[TestMethod]
public void Add_ReturnsCorrectSum()
{
var calc = new Calculator();
var result = calc.Add(2, 3);
Assert.AreEqual(5, result);
}
}

8-3. xUnitでテストを書く例

C#
public class CalculatorTests
{
[Fact]
public void Add_ReturnsCorrectSum()
{
var calc = new Calculator();
var result = calc.Add(2, 3);
Assert.Equal(5, result);
}
}

8-4. NUnitでテストを書く例

C#
[TestFixture]
public class CalculatorTests
{
[Test]
public void Add_ReturnsCorrectSum()
{
var calc = new Calculator();
Assert.AreEqual(5, calc.Add(2, 3));
}
}

8-5. 同じテストを3つのフレームワークで比較

  • 基本構造はほぼ同じ

  • 属性やAssertメソッドの書き方に差がある

  • CI/CDやチーム開発で選択基準となる

9. C#単体テストでよく使うテクニック

9-1. 例外が発生することをテストする

C#
Assert.ThrowsException<InvalidOperationException>(() => method());

9-2. 非同期メソッドをテストする

C#
[TestMethod]
public async Task AsyncMethod_ReturnsExpected()
{
var result = await AsyncMethod();
Assert.AreEqual(expected, result);
}

9-3. privateメソッドはテストすべきか

  • 基本的にはpublicメソッドを通してテスト

  • privateはリファクタリングで影響を受けやすいため直接テストは避ける

9-4. DateTimeや乱数など変わる値を扱う方法

  • 依存性注入で固定値を使う

  • モックやスタブを利用

9-5. モックを使って依存関係を切り離す方法

  • MoqNSubstituteを使う

  • 外部サービスやDBへの依存を排除してテストの安定性を確保

10. C#テストでよくある失敗と解決策

10-1. テストが実行されない

  • 属性が間違っている

  • プロジェクトの参照が不足している

10-2. 参照エラーが出る

  • 対象プロジェクトの参照追加

  • NuGetパッケージの確認

10-3. Assertの書き方がわからない

  • フレームワークの公式Assertドキュメントを確認

  • 基本的なEqual, True, Nullを理解する

10-4. テストが不安定になる

  • 非同期処理や乱数を固定化

  • モックで外部依存を制御

10-5. テストコードの保守が大変になる

  • Arrange・Act・Assertの順序を守る

  • 1テスト1目的を徹底

  • 名前付け規則を統一

11. C#テストを実務で活かすためのベストプラクティス

11-1. 何をどこまでテストするべきか

  • ビジネスロジック中心にテスト

  • UIやライブラリは必要に応じて統合テスト

11-2. テストケースの優先順位の決め方

  • 重要度が高い機能からテスト

  • バグ発生リスクが高い箇所を優先

11-3. テストコードを読みやすく保つ方法

  • コメントは最小限

  • 名前付けを一貫

  • Arrange・Act・Assertを明確に

11-4. CI/CDで自動テストを実行する方法

  • GitHub ActionsやAzure DevOpsでdotnet testを実行

  • プルリクエスト時に自動検証

11-5. コードカバレッジとの付き合い方

  • 高カバレッジは目安であり目的ではない

  • 本当に重要なロジックを確実にテスト

まとめ

C#での単体テストは、コード品質を保つための強力な手段です。MSTest、xUnit、NUnitの特性を理解し、適切なフレームワークを選ぶことで、効率的にテストを実行できます。テストコードは単なる作業ではなく、リファクタリングやチーム開発の安全網として活用することが大切です。正しい書き方・実行方法・ベストプラクティスを身につけ、C#開発をより堅牢にしていきましょう。