C# assertionとは?Debug.AssertとユニットテストのAssertの違い・使い方を初心者向けに解説
はじめに
C# assertionとは、プログラムが「想定どおりの状態になっているか」を確認するための仕組みです。C#を学び始めると、Debug.Assert、Assert.AreEqual、Assert.IsTrue、Trace.Assert など、似た名前の機能がいくつも出てきます。
初心者が特につまずきやすいのは、「Debug.Assert」と「ユニットテストのAssert」を同じものだと思ってしまうことです。どちらも条件を確認するために使いますが、目的・実行される場所・失敗したときの扱いが大きく異なります。
この記事では、C# assertionの基本から、Debug.AssertとユニットテストのAssertの違い、使い分け、具体的なコード例、初心者が注意すべきポイントまで順番に解説します。
1. C#のassertionとは?初心者向けに意味を解説
1-1. assertionは「想定どおりかを確認する仕組み」
assertionは、日本語では「表明」「断言」などと訳されます。プログラミングでは、「この時点では、この条件が必ず成り立っているはずだ」とコード上に書く仕組みのことです。
たとえば、次のような考え方です。
C#// この時点で user は null ではないはず
Debug.Assert(user != null);
このコードは、「userはnullではないはず」という開発者の前提をプログラム上に明示しています。つまりassertionは、単なるチェック処理ではなく、コードの前提や設計上の期待を表すためのものです。
C# assertionをうまく使うと、バグを早い段階で見つけやすくなり、コードを読む人にも「ここでは何が保証されているべきか」が伝わりやすくなります。
1-2. 条件がtrueなら何も起きず、falseなら問題を知らせる
assertionの基本はとてもシンプルです。
C#Assertの種類(条件式);
条件式がtrueであれば、基本的には何も起きません。プログラムはそのまま次の処理へ進みます。
一方で、条件式がfalseになると、「想定外の状態が発生している」と判断されます。Debug.Assertであればデバッグ中に警告が表示されたり、ユニットテストのAssertであればテストが失敗したりします。
たとえば、次のようなコードを考えます。
C#int count = -1;
Debug.Assert(count >= 0, "count は 0 以上である必要があります");
countは0以上であるべきなのに、実際には-1です。そのため、このassertionは失敗します。
1-3. C#で「Assert」と呼ばれるものは複数ある
C#では「Assert」と呼ばれるものが1種類だけではありません。代表的には次のようなものがあります。
| 種類 | 主な用途 |
|---|---|
System.Diagnostics.Debug.Assert | 開発中の内部チェック |
ユニットテストのAssert | テスト結果の検証 |
System.Diagnostics.Trace.Assert | トレース用のアサーション |
UnityのAssert | Unity開発での不変条件チェック |
同じAssertという名前でも、使う場所や意味は異なります。
特に初心者は、アプリ本体のコードに書くDebug.Assertと、テストコードに書くAssert.AreEqualなどを混同しやすいです。この記事では、この違いを中心に整理していきます。
1-4. assertionが使われる主な場面
C# assertionは、主に次のような場面で使われます。
C#Debug.Assert(items.Count >= 0);
Debug.Assert(currentUser != null);
Debug.Assert(status == OrderStatus.Completed);
たとえば、次のような確認に向いています。
| 確認したいこと | assertionの例 |
|---|---|
| nullではないはず | Debug.Assert(value != null) |
| 数値が範囲内のはず | Debug.Assert(score >= 0 && score <= 100) |
| switch文で想定外の値が来ないはず | Debug.Assert(false, "想定外のenum値です") |
| メソッドの戻り値が仕様どおりか | ユニットテストのAssert.AreEqual |
ただし、assertionは万能ではありません。本番環境で必ず必要な入力チェックやエラー処理は、assertionではなく例外処理やバリデーションで実装する必要があります。
2. C#で使う代表的なAssertの種類
2-1. System.Diagnostics.Debug.Assert
Debug.Assertは、System.Diagnostics名前空間にあるデバッグ用のアサーションです。Microsoftのドキュメントでも、Debugクラスはデバッグを支援するメソッドを提供し、アサーションを使ってロジックを確認する用途で説明されています。
基本的には、開発中に「ここはこうなっているはず」という内部状態を確認するために使います。
C#using System.Diagnostics;
public void Process(string name)
{
Debug.Assert(name != null, "name は null ではないはずです");
Console.WriteLine(name.Length);
}
Debug.Assertは、アプリケーションの仕様をテストするというより、開発者が想定している内部状態を検証するためのものです。
2-2. ユニットテストのAssert
ユニットテストのAssertは、テストコードの中で「実際の結果が期待値と一致しているか」を判定するために使います。
たとえばMSTestでは、次のように書きます。
C#using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class CalculatorTests
{
[TestMethod]
public void Add_1と2を渡すと3を返す()
{
var calculator = new Calculator();
int result = calculator.Add(1, 2);
Assert.AreEqual(3, result);
}
}
MSTestは.NETアプリケーション向けのテストフレームワークで、テストの作成・実行やVisual Studio、Visual Studio Code、.NET CLIなどとの連携を提供します。
ユニットテストのAssertは、失敗すると「テスト失敗」として扱われます。CI/CDなどで自動実行されるテストにも使われます。
2-3. Trace.Assert
Trace.AssertもSystem.Diagnostics名前空間にあるアサーションです。Debug.Assertと似ていますが、Trace.AssertはTRACEシンボルに基づいて条件付きで呼び出されます。Microsoftのドキュメントでは、リリースビルドでアサーションを行いたい場合にはTrace.Assertを使い、Debug.Assertはデバッグビルドでのみ動作するものとして説明されています。
C#using System.Diagnostics;
Trace.Assert(index >= 0, "index は 0 以上である必要があります");
ただし、Trace.Assertを使えば本番環境の入力チェックがすべて解決するわけではありません。本番環境で処理すべきエラーは、例外処理や通常の条件分岐で扱うべきです。
2-4. UnityのAssertとの違い
UnityでC#を書いている場合は、UnityEngine.Assertions.Assertを使うことがあります。UnityのAssertクラスは、コード内の不変条件を設定するためのアサーションメソッドを含み、通常は開発ビルドで条件付きで含まれます。
C#using UnityEngine.Assertions;
Assert.IsNotNull(player);
Assert.IsTrue(player.isActiveAndEnabled);
UnityのAssertは、Unityエンジン上での開発に合わせた仕組みです。通常の.NETアプリケーションで使うDebug.Assertや、MSTestなどのユニットテストのAssertとは用途が異なります。
3. Debug.Assertとは?開発中のバグ検出に使うAssert
3-1. Debug.Assertの基本構文
Debug.Assertの基本構文は次のとおりです。
C#Debug.Assert(条件式);
Debug.Assert(条件式, "メッセージ");
使用するには、System.Diagnosticsをインポートします。
C#using System.Diagnostics;
よく使う形は次のようになります。
C#Debug.Assert(user != null, "user は null ではないはずです");
Debug.Assert(total >= 0, "total は 0 以上である必要があります");
Debug.Assert(items.Count > 0, "items は空ではないはずです");
メッセージを付けておくと、失敗したときに原因を調べやすくなります。
3-2. Debug.Assertの簡単な使用例
次の例では、商品価格が0以上であることを確認しています。
C#using System.Diagnostics;
public class Product
{
public int Price { get; }
public Product(int price)
{
Debug.Assert(price >= 0, "価格は 0 以上である必要があります");
Price = price;
}
}
このコードは、「このクラスに渡される価格は0以上のはず」という前提を表しています。
ただし、外部入力として不正な値が入る可能性がある場合は、Debug.Assertだけでは不十分です。その場合は、次のように例外を使うべきです。
C#public Product(int price)
{
if (price < 0)
{
throw new ArgumentOutOfRangeException(nameof(price), "価格は 0 以上である必要があります");
}
Price = price;
}
3-3. Debug.Assertが失敗したときの動き
Debug.Assertの条件がfalseになると、デバッグ中に失敗が通知されます。Microsoftのドキュメントでは、Debug.Assertは条件を確認し、条件がfalseの場合にメッセージを出力したり、呼び出し履歴を示すメッセージボックスを表示したりするメソッドとして説明されています。
たとえば次のコードでは、valueがマイナスなのでassertionが失敗します。
C#int value = -10;
Debug.Assert(value >= 0, "value は 0 以上である必要があります");
失敗時の見え方は、実行環境、デバッガー、トレースリスナーの設定によって変わることがあります。重要なのは、「開発者に想定外の状態を知らせるための仕組み」だという点です。
3-4. DebugビルドとReleaseビルドでの挙動の違い
Debug.Assertで最も重要なのは、DebugビルドとReleaseビルドで挙動が変わることです。
DebugクラスのメソッドにはConditionalAttributeが適用され、DEBUG条件付きコンパイルシンボルが定義されていない場合、対応する呼び出しはコンパイラによって無視されます。Visual StudioのC#プロジェクトでは、通常DebugビルドではDEBUGシンボルが定義され、Releaseビルドでは定義されません。
つまり、次のコードはDebugビルドでは実行されますが、Releaseビルドでは基本的に呼び出し自体が無視されます。
C#Debug.Assert(user != null, "user は null ではないはずです");
この特徴があるため、Debug.Assertを本番環境で必要なチェックの代わりにしてはいけません。
3-5. Debug.Assertを使うべき場面
Debug.Assertは、次のような場面に向いています。
| 場面 | 例 |
|---|---|
| メソッド内部の前提確認 | この時点で変数はnullではないはず |
| 計算後の不変条件確認 | 合計値は0以上のはず |
| 到達しないはずの分岐検出 | switch文のdefaultに来るはずがない |
| 開発中のロジック検証 | 配列インデックスは範囲内のはず |
たとえば、内部ロジック上は必ず値が入っているはずの場面で使います。
C#User? user = FindUser(id);
Debug.Assert(user != null, "指定されたIDのユーザーは存在する前提です");
// Debugビルドでは user が null の場合に気づきやすい
Console.WriteLine(user.Name);
3-6. Debug.Assertに書いてはいけない処理
Debug.Assertの条件式やメッセージ生成の中に、副作用のある処理を書いてはいけません。
悪い例です。
C#Debug.Assert(SaveLog() == true);
この例では、SaveLog()が何かの保存処理を行う可能性があります。しかしReleaseビルドではDebug.Assertの呼び出し自体が無視される可能性があるため、SaveLog()も呼ばれないことがあります。
次のように、処理と確認を分けましょう。
C#bool result = SaveLog();
Debug.Assert(result, "ログ保存に成功しているはずです");
Debug.Assertには、「実行されなくてもプログラムの動作に影響しない条件式」だけを書くのが基本です。
4. ユニットテストのAssertとは?テスト結果を判定するAssert
4-1. ユニットテストにおけるAssertの役割
ユニットテストのAssertは、「テスト対象のコードが期待どおりの結果を返したか」を確認するために使います。
一般的なテストは、次の3つの流れで書きます。
| ステップ | 内容 |
|---|---|
| Arrange | テストの準備 |
| Act | テスト対象の処理を実行 |
| Assert | 結果を検証 |
例を見てみましょう。
C#[TestMethod]
public void Add_2つの数値を加算できる()
{
// Arrange
var calculator = new Calculator();
// Act
int result = calculator.Add(2, 3);
// Assert
Assert.AreEqual(5, result);
}
ここでのAssert.AreEqual(5, result)は、「期待値は5で、実際の結果も5であるべき」という意味です。
4-2. Assert.AreEqualの使い方
Assert.AreEqualは、期待値と実際の値が等しいことを確認します。
C#Assert.AreEqual(期待値, 実際の値);
例です。
C#[TestMethod]
public void GetFullName_姓と名を結合する()
{
var user = new User("Yamada", "Taro");
string result = user.GetFullName();
Assert.AreEqual("Yamada Taro", result);
}
期待値と実際の値が異なる場合、テストは失敗します。
C#Assert.AreEqual("Yamada Taro", "Yamada Hanako");
この場合、期待していた文字列と実際の文字列が違うため、テスト失敗として表示されます。
4-3. Assert.IsTrue・Assert.IsFalseの使い方
Assert.IsTrueは、条件がtrueであることを確認します。
C#Assert.IsTrue(result);
例です。
C#[TestMethod]
public void IsAdult_20歳ならtrueを返す()
{
var user = new User(age: 20);
bool result = user.IsAdult();
Assert.IsTrue(result);
}
一方、Assert.IsFalseは、条件がfalseであることを確認します。
C#[TestMethod]
public void IsAdult_17歳ならfalseを返す()
{
var user = new User(age: 17);
bool result = user.IsAdult();
Assert.IsFalse(result);
}
ただし、単にAssert.IsTrue(result == 5)と書くよりも、値の比較にはAssert.AreEqual(5, result)を使ったほうが、失敗時に期待値と実際の値を把握しやすくなります。
4-4. Assert.IsNull・Assert.IsNotNullの使い方
Assert.IsNullは、値がnullであることを確認します。
C#Assert.IsNull(value);
Assert.IsNotNullは、値がnullではないことを確認します。
C#Assert.IsNotNull(value);
例です。
C#[TestMethod]
public void FindUser_存在しないIDならnullを返す()
{
var repository = new UserRepository();
User? result = repository.FindUser(999);
Assert.IsNull(result);
}
C#[TestMethod]
public void FindUser_存在するIDならユーザーを返す()
{
var repository = new UserRepository();
User? result = repository.FindUser(1);
Assert.IsNotNull(result);
}
nullチェックは、C#のテストでよく使う基本的なassertionです。
4-5. Assert.ThrowsExceptionで例外をテストする方法
メソッドが正しく例外を投げるかどうかも、ユニットテストで確認できます。MSTestではAssert.ThrowsException<T>を使います。
C#[TestMethod]
public void Constructor_名前がnullなら例外を投げる()
{
Assert.ThrowsException<ArgumentNullException>(() =>
{
var user = new User(null);
});
}
このテストは、「Userのコンストラクタにnullを渡すとArgumentNullExceptionが発生するべき」という仕様を確認しています。
例外が発生しなかったり、別の種類の例外が発生したりすると、テストは失敗します。
4-6. テストが失敗したときの見方
ユニットテストのAssertが失敗すると、テストランナーに失敗内容が表示されます。
たとえば、次のようなテストがあるとします。
C#[TestMethod]
public void Add_1と2を渡すと4を返す()
{
var calculator = new Calculator();
int result = calculator.Add(1, 2);
Assert.AreEqual(4, result);
}
実際の結果は3なのに、期待値を4にしているためテストは失敗します。
このとき、テスト結果には「期待値は4だったが、実際は3だった」という情報が表示されます。失敗したテスト名、失敗箇所、期待値、実際の値を確認することで、バグなのかテストの期待値が間違っているのかを判断できます。
5. Debug.AssertとユニットテストのAssertの違い
5-1. 目的の違い
Debug.Assertの目的は、開発中に内部状態の矛盾を見つけることです。
C#Debug.Assert(order.TotalAmount >= 0);
これは、「注文金額はマイナスにならないはず」という内部的な前提を確認しています。
一方、ユニットテストのAssertの目的は、外部から見た仕様を検証することです。
C#Assert.AreEqual(3000, order.CalculateTotal());
これは、「この入力なら合計金額は3000になるべき」という仕様を確認しています。
5-2. 実行されるタイミングの違い
Debug.Assertは、アプリケーションの実行中に通ったコード上で評価されます。つまり、その処理が実際に実行されたときに確認されます。
一方、ユニットテストのAssertは、テスト実行時に評価されます。アプリケーションの通常実行とは別に、テストランナーから実行されます。
C#// アプリ本体の実行中
Debug.Assert(value >= 0);
// テスト実行時
Assert.AreEqual(10, result);
5-3. Releaseビルドで動くかどうかの違い
Debug.Assertは、通常Releaseビルドでは実行されません。これはDEBUGシンボルが定義されていない場合にDebugメソッド呼び出しが無視されるためです。
一方、ユニットテストのAssertは、テストを実行すればRelease構成でビルドしたコードに対しても検証できます。テストプロジェクトを実行するかどうかが重要であり、アプリ本体の通常実行中に勝手に動くものではありません。
5-4. 失敗したときの扱いの違い
Debug.Assertが失敗すると、デバッグ中に警告やメッセージとして通知されます。開発者に「ここは想定外です」と知らせる役割です。
ユニットテストのAssertが失敗すると、そのテストは失敗として記録されます。CI/CDで実行している場合は、ビルドやデプロイを止める判断材料にもなります。
つまり、失敗の扱いは次のように違います。
| 種類 | 失敗時の扱い |
|---|---|
Debug.Assert | デバッグ中の警告・診断 |
ユニットテストのAssert | テスト失敗 |
5-5. コードに残す場所の違い
Debug.Assertは、アプリケーション本体のコードに書きます。
C#public void UpdateStatus(OrderStatus status)
{
Debug.Assert(status != OrderStatus.Unknown);
// 本体処理
}
ユニットテストのAssertは、テストプロジェクトのテストコードに書きます。
C#[TestMethod]
public void UpdateStatus_正常なステータスに更新できる()
{
var order = new Order();
order.UpdateStatus(OrderStatus.Completed);
Assert.AreEqual(OrderStatus.Completed, order.Status);
}
本体コードにユニットテストのAssertを書くのではなく、テストコードに書くのが基本です。
5-6. 比較表でわかるDebug.AssertとテストAssertの違い
| 比較項目 | Debug.Assert | ユニットテストのAssert |
|---|---|---|
| 主な目的 | 開発中の内部チェック | 仕様・結果の検証 |
| 書く場所 | アプリ本体のコード | テストコード |
| 実行タイミング | アプリ実行中 | テスト実行時 |
| Releaseビルド | 基本的に実行されない | テストを実行すれば使える |
| 失敗時 | デバッグ通知 | テスト失敗 |
| 向いている確認 | 不変条件、到達しない分岐 | 戻り値、状態変化、例外 |
| 品質保証への役割 | バグの早期発見を補助 | 自動テストとして品質を支える |
このように、Debug.AssertとユニットテストのAssertは似ているようで役割が大きく異なります。
6. Debug.AssertとユニットテストのAssertはどう使い分ける?
6-1. メソッド内部の不変条件チェックにはDebug.Assert
メソッド内部で「ここでは絶対にこの条件が成り立つはず」という確認には、Debug.Assertが向いています。
C#public decimal CalculateDiscountedPrice(decimal price, decimal discountRate)
{
Debug.Assert(price >= 0, "price は 0 以上の前提です");
Debug.Assert(discountRate >= 0 && discountRate <= 1, "割引率は 0〜1 の範囲の前提です");
return price * (1 - discountRate);
}
ただし、外部から渡される値が不正になる可能性があるなら、Debug.Assertだけでは不十分です。その場合は例外やバリデーションを使います。
6-2. 外部から見た仕様の確認にはユニットテストのAssert
「このメソッドにこの入力を渡したら、この結果になる」という仕様を確認するなら、ユニットテストのAssertを使います。
C#[TestMethod]
public void CalculateDiscountedPrice_10パーセント割引を計算できる()
{
var service = new PriceService();
decimal result = service.CalculateDiscountedPrice(1000m, 0.1m);
Assert.AreEqual(900m, result);
}
これは、メソッドの内部実装ではなく、外部から見た振る舞いを検証しています。
6-3. 引数チェックには例外を使うべきケースが多い
publicメソッドの引数チェックには、Debug.Assertではなく例外を使うべきケースが多いです。
悪い例です。
C#public void Register(string name)
{
Debug.Assert(name != null);
// 登録処理
}
ReleaseビルドではDebug.Assertが実行されない可能性があるため、nameがnullでもそのまま処理が進んでしまうことがあります。
よい例です。
C#public void Register(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
// 登録処理
}
外部から不正な値が渡される可能性がある場合は、例外や入力チェックで確実に処理しましょう。
6-4. 本番環境でも必要なチェックはDebug.Assertに任せない
本番環境でも必ず必要なチェックは、Debug.Assertに任せてはいけません。
たとえば、次のようなチェックです。
| 本番環境でも必要なチェック | 使うべきもの |
|---|---|
| ユーザー入力の検証 | バリデーション |
| null引数の拒否 | ArgumentNullException |
| 範囲外の数値の拒否 | ArgumentOutOfRangeException |
| 権限不足の検出 | 例外、エラー応答 |
| ファイルが存在しない場合の処理 | 例外処理、条件分岐 |
Debug.Assertは「開発中にバグを見つける補助」であり、「本番環境の安全装置」ではありません。
6-5. Debug.Assertとユニットテストを併用する考え方
Debug.Assertとユニットテストは、どちらか一方だけを使うものではありません。役割が違うため、併用できます。
たとえば、次のように考えます。
C#public int Divide(int x, int y)
{
if (y == 0)
{
throw new ArgumentException("0で割ることはできません", nameof(y));
}
int result = x / y;
Debug.Assert(y != 0, "ここでは y は 0 ではない前提です");
return result;
}
そしてテストコードでは、外部から見た仕様を確認します。
C#[TestMethod]
public void Divide_10を2で割ると5を返す()
{
var calculator = new Calculator();
int result = calculator.Divide(10, 2);
Assert.AreEqual(5, result);
}
[TestMethod]
public void Divide_0で割ると例外を投げる()
{
var calculator = new Calculator();
Assert.ThrowsException<ArgumentException>(() =>
{
calculator.Divide(10, 0);
});
}
アプリ本体ではDebug.Assertで内部の前提を確認し、テストコードではAssertで仕様を確認する。この使い分けが基本です。
7. C# assertionの具体例で使い方を理解する
7-1. nullでないことをDebug.Assertで確認する例
次の例では、検索結果がnullではない前提をDebug.Assertで確認しています。
C#using System.Diagnostics;
public void PrintUserName(int id)
{
User? user = FindUser(id);
Debug.Assert(user != null, "指定されたIDのユーザーは存在する前提です");
Console.WriteLine(user.Name);
}
このコードは、「このメソッドが呼ばれる時点では、指定IDのユーザーが存在するはず」という内部前提を表しています。
ただし、ユーザーが存在しないケースが通常の業務フローとしてあり得るなら、次のように明示的に処理するべきです。
C#public void PrintUserName(int id)
{
User? user = FindUser(id);
if (user == null)
{
Console.WriteLine("ユーザーが見つかりません");
return;
}
Console.WriteLine(user.Name);
}
7-2. 計算結果が想定範囲内か確認する例
計算結果が想定範囲に収まっていることを確認する例です。
C#public int CalculateScore(int correctCount, int totalCount)
{
if (totalCount == 0)
{
throw new ArgumentException("totalCount は 0 にできません", nameof(totalCount));
}
int score = correctCount * 100 / totalCount;
Debug.Assert(score >= 0 && score <= 100, "score は 0〜100 の範囲に収まるはずです");
return score;
}
このように、計算後の結果に対して「この範囲に入るはず」という不変条件を確認できます。
7-3. enumやswitch文の想定外ケースを検出する例
enumとswitch文では、「通常は到達しないはずの分岐」にDebug.Assertを書くことがあります。
C#public enum PaymentStatus
{
Pending,
Completed,
Failed
}
public string GetStatusText(PaymentStatus status)
{
switch (status)
{
case PaymentStatus.Pending:
return "処理中";
case PaymentStatus.Completed:
return "完了";
case PaymentStatus.Failed:
return "失敗";
default:
Debug.Assert(false, $"想定外のステータスです: {status}");
return "不明";
}
}
将来enumに値が追加されたのにswitch文の対応を忘れていた場合、開発中に気づきやすくなります。
7-4. ユニットテストで戻り値を検証する例
次は、戻り値をユニットテストで検証する例です。
C#public class TaxCalculator
{
public int AddTax(int price)
{
return (int)(price * 1.1);
}
}
テストコードは次のように書けます。
C#[TestClass]
public class TaxCalculatorTests
{
[TestMethod]
public void AddTax_1000円なら1100円を返す()
{
var calculator = new TaxCalculator();
int result = calculator.AddTax(1000);
Assert.AreEqual(1100, result);
}
}
このテストでは、「1000円に税を加えると1100円になる」という仕様を確認しています。
7-5. ユニットテストで例外発生を検証する例
次は、不正な価格を渡したときに例外が発生することを確認する例です。
C#public class PriceService
{
public int ApplyDiscount(int price, int discount)
{
if (price < 0)
{
throw new ArgumentOutOfRangeException(nameof(price));
}
if (discount < 0)
{
throw new ArgumentOutOfRangeException(nameof(discount));
}
return price - discount;
}
}
テストコードです。
C#[TestClass]
public class PriceServiceTests
{
[TestMethod]
public void ApplyDiscount_価格がマイナスなら例外を投げる()
{
var service = new PriceService();
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
service.ApplyDiscount(-100, 10);
});
}
}
このように、例外が仕様の一部である場合は、ユニットテストで明確に検証できます。
8. C# assertionで初心者がつまずきやすい注意点
8-1. Debug.AssertはReleaseビルドでは基本的に実行されない
C# assertionで最も重要な注意点は、Debug.AssertがReleaseビルドでは基本的に実行されないことです。
次のようなコードは危険です。
C#public void Save(string fileName)
{
Debug.Assert(fileName != null);
File.WriteAllText(fileName, "data");
}
ReleaseビルドではDebug.Assertが無視される可能性があるため、fileNameがnullでもチェックされないことがあります。
本当に必要なチェックは、次のように書きます。
C#public void Save(string fileName)
{
if (fileName == null)
{
throw new ArgumentNullException(nameof(fileName));
}
File.WriteAllText(fileName, "data");
}
8-2. Debug.Assertを入力チェックの代わりにしない
ユーザー入力、APIリクエスト、ファイル読み込み、データベースの値など、外部から来る値は信用しすぎてはいけません。
悪い例です。
C#Debug.Assert(request.UserId > 0);
外部入力に対しては、次のようにバリデーションや例外処理を使います。
C#if (request.UserId <= 0)
{
return BadRequest("UserId が不正です");
}
Debug.Assertは、外部入力の安全性を保証するものではありません。
8-3. Assert内で副作用のある処理を書かない
Debug.Assertの中で、状態を変更する処理を書いてはいけません。
悪い例です。
C#Debug.Assert(list.Remove(item));
このコードでは、Removeによってリストの内容が変わります。しかしReleaseビルドでは呼び出されない可能性があるため、DebugビルドとReleaseビルドで動作が変わってしまいます。
よい例です。
C#bool removed = list.Remove(item);
Debug.Assert(removed, "item は list に含まれている前提です");
処理とassertionは分けて書くのが安全です。
8-4. Debug.Assertだけで品質を保証できると思わない
Debug.Assertは便利ですが、それだけで品質を保証できるわけではありません。
品質を高めるには、次のような取り組みも必要です。
| 取り組み | 役割 |
|---|---|
| ユニットテスト | 仕様どおりに動くか確認する |
| 結合テスト | 複数の部品を組み合わせて確認する |
| 例外処理 | 本番環境の異常系に対応する |
| ログ出力 | 問題発生時の調査をしやすくする |
| コードレビュー | 設計や実装の問題を見つける |
Debug.Assertは、あくまで開発中のバグ検出を助ける道具です。
8-5. テストのAssertと本体コードのAssertを混同しない
初心者がよく混同するのが、テストコードのAssertと本体コードのDebug.Assertです。
本体コードでは、次のようにDebug.Assertを使います。
C#Debug.Assert(value >= 0);
テストコードでは、次のようにテストフレームワークのAssertを使います。
C#Assert.AreEqual(100, result);
本体コードにAssert.AreEqualを書くのではなく、テストコードに書きます。反対に、テストコードで内部実装の細かい状態ばかりを確認しすぎると、実装変更に弱いテストになることがあります。
9. Debug.Assert・Trace.Assert・例外処理の違い
9-1. Trace.Assertはどんなときに使うか
Trace.Assertは、Debug.Assertと似ていますが、TRACEシンボルに基づいて条件付きで呼び出されます。Visual StudioのC#プロジェクトでは、通常TRACEシンボルはDebugビルドとReleaseビルドの両方で定義されます。
C#Trace.Assert(connection != null, "connection は null ではないはずです");
ただし、Trace.Assertも「本番環境の業務エラーを処理するための仕組み」と考えるべきではありません。診断やトレース用途のアサーションとして使うものです。
9-2. 例外処理は本番環境のエラー制御に使う
本番環境で発生し得る異常や、呼び出し元に明確に知らせるべき問題は、例外処理を使います。
C#public User GetUser(int id)
{
if (id <= 0)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
User? user = repository.Find(id);
if (user == null)
{
throw new InvalidOperationException("ユーザーが見つかりません");
}
return user;
}
例外は、DebugビルドでもReleaseビルドでも動作します。そのため、本番環境で必要なエラー制御に向いています。
9-3. ArgumentNullExceptionやArgumentExceptionとの使い分け
引数が不正な場合は、ArgumentNullException、ArgumentException、ArgumentOutOfRangeExceptionなどを使います。
C#public void SendEmail(string email)
{
if (email == null)
{
throw new ArgumentNullException(nameof(email));
}
if (!email.Contains("@"))
{
throw new ArgumentException("メールアドレスの形式が不正です", nameof(email));
}
// メール送信処理
}
Debug.Assertで済ませるのではなく、呼び出し元が不正な値を渡したことを明確に伝える必要があります。
9-4. 「検出したいバグ」と「処理すべきエラー」を分けて考える
C# assertionを使い分けるうえで重要なのは、「検出したいバグ」と「処理すべきエラー」を分けることです。
| 種類 | 例 | 使うもの |
|---|---|---|
| 検出したいバグ | 本来あり得ない内部状態 | Debug.Assert |
| 診断したい状態 | トレースとして確認したい条件 | Trace.Assert |
| 処理すべきエラー | ユーザー入力が不正 | バリデーション、例外 |
| 仕様の確認 | この入力ならこの結果になる | ユニットテストのAssert |
この区別ができると、Debug.Assert、ユニットテストのAssert、例外処理を自然に使い分けられるようになります。
10. C# assertionのベストプラクティス
10-1. 常にtrueであるべき前提条件を書く
Debug.Assertには、「通常は必ずtrueになるはず」の条件を書きます。
C#Debug.Assert(items != null);
Debug.Assert(index >= 0 && index < items.Count);
Debug.Assert(total >= 0);
逆に、ユーザー操作や外部環境によって普通にfalseになり得る条件は、Debug.Assertではなく通常の条件分岐で処理します。
C#if (items == null)
{
return;
}
10-2. メッセージを付けて原因をわかりやすくする
Debug.Assertには、できるだけメッセージを付けると原因を特定しやすくなります。
C#Debug.Assert(user != null, "ログイン済みの処理なので user は null ではないはずです");
悪い例です。
C#Debug.Assert(user != null);
これでも意味はありますが、失敗したときに「なぜnullではないはずなのか」が分かりにくくなります。
よいメッセージには、次の情報を含めると効果的です。
| 含めたい情報 | 例 |
|---|---|
| 何が問題か | user が null です |
| なぜ想定外か | ログイン済みの処理なので null にならない前提です |
| 関連する値 | status: {status} |
10-3. 読みやすい条件式にする
assertionの条件式は、できるだけ読みやすく書きましょう。
読みにくい例です。
C#Debug.Assert(!(user == null || user.Age < 0 || user.Age > 120));
読みやすい例です。
C#Debug.Assert(user != null, "user は null ではないはずです");
Debug.Assert(user.Age >= 0 && user.Age <= 120, "年齢は 0〜120 の範囲のはずです");
複雑な条件を1つのassertionに詰め込みすぎると、失敗時に原因が分かりにくくなります。
10-4. ユニットテストでは1つのテストで1つの観点を確認する
ユニットテストのAssertでは、1つのテストで1つの観点を確認するのが基本です。
悪い例です。
C#[TestMethod]
public void UserTest()
{
var user = new User("Taro", 20);
Assert.AreEqual("Taro", user.Name);
Assert.AreEqual(20, user.Age);
Assert.IsTrue(user.IsAdult());
}
このテストは複数の観点をまとめて確認しているため、失敗時に意図が分かりにくくなります。
よい例です。
C#[TestMethod]
public void IsAdult_20歳ならtrueを返す()
{
var user = new User("Taro", 20);
Assert.IsTrue(user.IsAdult());
}
テスト名で何を確認しているかを明確にし、Assertもその観点に集中させると、保守しやすいテストになります。
10-5. assertionをデバッグ効率と品質向上に活用する
C# assertionは、正しく使えばデバッグ効率と品質向上に役立ちます。
Debug.Assertによって内部状態の矛盾に早く気づけます。ユニットテストのAssertによって、仕様が壊れていないかを継続的に確認できます。例外処理によって、本番環境で必要なエラー制御を行えます。
それぞれの役割を分けて使うことが重要です。
C#// 本番でも必要なチェック
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
// 開発中の内部前提チェック
Debug.Assert(name.Length > 0, "name は空ではない前提です");
// テストコードでの仕様確認
Assert.AreEqual("Taro", user.Name);
11. C# assertionに関するよくある質問
11-1. C#にassertキーワードはある?
C#には、C言語や一部の言語のようなassertキーワードはありません。C#のキーワード一覧にもassertは含まれていません。
C#でassertionを使う場合は、主に次のようなクラスやメソッドを使います。
C#Debug.Assert(condition);
Trace.Assert(condition);
Assert.AreEqual(expected, actual);
Assert.IsTrue(condition);
つまり、C#では言語のキーワードとしてのassertではなく、ライブラリやテストフレームワークのAssertを使うと理解するとよいです。
11-2. Debug.Assertは本番環境でも使える?
Debug.Assertをコードに書くこと自体はできます。しかし、通常のReleaseビルドではDEBUGシンボルが定義されていないため、Debug.Assertの呼び出しは基本的に無視されます。
そのため、本番環境で必ず実行したいチェックには使うべきではありません。
本番環境で必要なチェックは、次のように書きます。
C#if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
Debug.Assertは、あくまで開発中のバグ検出を補助するためのものです。
11-3. Debug.AssertとConsole.WriteLineは何が違う?
Console.WriteLineは、単に文字列を出力するためのものです。
C#Console.WriteLine("ここまで来ました");
一方、Debug.Assertは条件を確認し、条件がfalseの場合に問題を知らせるためのものです。
C#Debug.Assert(count >= 0, "count は 0 以上である必要があります");
Console.WriteLineは状態を表示するだけですが、Debug.Assertは「この条件が満たされていなければおかしい」という意図をコードで表せます。
11-4. Debug.Assertと例外はどちらを使うべき?
目的によって使い分けます。
| 目的 | 使うもの |
|---|---|
| 開発中に内部の矛盾を見つけたい | Debug.Assert |
| 本番環境でも不正な状態を止めたい | 例外 |
| ユーザー入力を検証したい | バリデーション |
| メソッドの仕様を確認したい | ユニットテストのAssert |
たとえば、publicメソッドの引数がnullの場合は、Debug.AssertではなくArgumentNullExceptionを使うべきです。
C#public void SetName(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
Debug.Assert(name.Length > 0, "name は空ではない前提です");
}
11-5. ユニットテストを書いていればDebug.Assertは不要?
ユニットテストを書いていても、Debug.Assertが不要になるわけではありません。
ユニットテストは、外部から見た仕様を確認するのが得意です。一方、Debug.Assertは、メソッド内部の一時的な状態や不変条件を確認するのに向いています。
たとえば、複雑な処理の途中で「この時点では必ずリストがソート済みのはず」と確認したい場合、Debug.Assertが役立ちます。
C#Debug.Assert(IsSorted(items), "この時点で items はソート済みのはずです");
ただし、Debug.Assertはユニットテストの代わりにはなりません。ユニットテストとDebug.Assertは、役割が違う補完関係にあります。
まとめ
C# assertionとは、「この条件は成り立っているはず」という前提をコードで確認する仕組みです。C#ではDebug.Assert、ユニットテストのAssert、Trace.Assert、UnityのAssertなど、複数のAssertが存在します。
特に重要なのは、Debug.AssertとユニットテストのAssertの違いです。
Debug.Assertは、アプリ本体のコードに書き、開発中に内部状態の矛盾を見つけるために使います。通常Releaseビルドでは基本的に実行されないため、本番環境で必要な入力チェックやエラー処理には使えません。
一方、ユニットテストのAssertは、テストコードに書き、メソッドの戻り値や状態変化、例外発生などが仕様どおりかを検証するために使います。テストが失敗すると、テストランナー上で失敗として扱われます。
使い分けの基本は次のとおりです。
| やりたいこと | 使うもの |
|---|---|
| メソッド内部の前提を確認したい | Debug.Assert |
| 仕様どおりの戻り値か確認したい | ユニットテストのAssert |
| 本番環境でも不正値を防ぎたい | 例外・バリデーション |
| リリースビルドでも診断用に確認したい | Trace.Assert |
C# assertionは、正しく使えばバグの早期発見やコードの意図の明確化に役立ちます。ただし、Debug.Assertを入力チェックの代わりにしたり、Assert内に副作用のある処理を書いたりすると、思わぬ不具合につながります。
Debug.Assert、ユニットテストのAssert、例外処理をそれぞれの役割に合わせて使い分けることが、読みやすく安全なC#コードを書くためのポイントです。

