チュートリアル: マネージ コードに対する単体テストの作成と実行
このチュートリアルでは、マネージ コード用の Microsoft 単体テスト フレームワークと Visual Studio テスト エクスプローラーを使用して一連の単体テストを作成、実行、およびカスタマイズする手順について説明します。開発中の C# プロジェクトで作業を開始し、そのコードを実行するテストを作成し、テストを実行し、結果を調べます。次に、プロジェクト コードを変更し、テストを再実行します。
このトピックは、次のセクションで構成されています。
Fix your code and rerun your tests
Use unit tests to improve your code
[!メモ]
このチュートリアルでは、マネージ コード用の Microsoft 単体テスト フレームワークを使用します。また、テスト エクスプローラー用のアダプターを備えたサード パーティの単体テスト フレームワークからテスト エクスプローラーを実行することもできます。詳細については、「方法: サードパーティ製の単体テスト フレームワークをインストールする」を参照してください。
[!メモ]
コマンド ラインからテストを実行する方法については、「チュートリアル: コマンド ライン テスト ユーティリティの使用」を参照してください。
必須コンポーネント
- Bank プロジェクト。「単体テストを作成するサンプル プロジェクト」を参照してください。
チュートリアルを準備する
Visual Studio を開きます。
[ファイル] メニューの [新規作成] をポイントし、[プロジェクト] をクリックします。
[新しいプロジェクト] ダイアログ ボックスが表示されます。
[インストールされているテンプレート] の [Visual C#] をクリックします。
アプリケーションの種類の一覧の [クラス ライブラリ] をクリックします。
[名前] ボックスに「Bank」と入力し、[OK] をクリックします。
[!メモ]
"Bank" という名前が既に使用されている場合は、別のプロジェクト名を選択します。
新しい Bank プロジェクトが作成され、コード エディターに Class1.cs ファイルが開いた状態でソリューション エクスプローラーが表示されます。
[!メモ]
コード エディターに Class1.cs ファイルが表示されない場合、ソリューション エクスプローラーのファイル Class1.cs をダブルクリックして開きます。
ソース コードを 単体テストを作成するサンプル プロジェクト からコピーします。
Class1.cs の元の内容を、単体テストを作成するサンプル プロジェクト のコードで置き換えます。
ファイルを BankAccount.cs として保存します。
[ビルド] メニューの [ソリューションのビルド] をクリックします。
Bank という名前のプロジェクトができます。これには、テストするソース コードとテストに使用するツールが含まれています。Bank の BankAccountNS 名前空間には、パブリック クラス BankAccount が含まれます。そのメソッドを次の手順でテストします。
このクイック スタートでは、Debit メソッドに焦点を当てます。Debit メソッドは、現金が口座から引き出されたときに呼び出され、次のコードが含まれています。
// method under test
public void Debit(double amount)
{
if(amount > m_balance)
{
throw new ArgumentOutOfRangeException("amount");
}
if (amount < 0)
{
throw new ArgumentOutOfRangeException("amount");
}
m_balance += amount;
}
単体テスト プロジェクトを作成する
必要条件: 「Prepare the walkthrough」の手順に従います。
単体テスト プロジェクトを作成するには
[ファイル] メニューの [追加] をポイントし、[新しいプロジェクト] をクリックします。
[新しいプロジェクト] ダイアログ ボックスで、[インストール済み]、[Visual C#] の順に展開し、[テスト] をクリックします。
テンプレートの一覧から、[単体テスト プロジェクト] を選択します。
[名前] ボックスに「BankTest」と入力し、[OK] をクリックします。
BankTests プロジェクトが Bank ソリューションに追加されます。
BankTests プロジェクトで、Bank ソリューションへの参照を追加します。
ソリューション エクスプローラーで、BankTests プロジェクトの [参照設定] をクリックし、コンテキスト メニューの [参照の追加] をクリックします。
[参照マネージャー] ダイアログ ボックスで、[ソリューション] を展開し、[Bank] チェックボックスをオンにします。
テスト クラスを作成する
BankAccount クラスを検証するためのテスト クラスが必要です。プロジェクト テンプレートによって生成された UnitTest1.cs を使用できますが、ファイルとクラスにはよりわかりやすい名前を付ける必要があります。ソリューション エクスプローラーでファイルの名前変更機能を使用すると、この処理を 1 つの手順で実行できます。
クラス ファイルの名前を変更する
ソリューション エクスプローラーで、BankTests プロジェクトの UnitTest1.cs ファイルを選択します。コンテキスト メニューの [名前の変更] をクリックし、ファイルの名前を BankAccountTests.cs に変更します。コード要素 UnitTest1 に対するプロジェクトのすべての参照を名前変更するかどうかを確認するダイアログで [はい] をクリックします。この手順により、クラスの名前が BankAccountTests に変更されます。
BankAccountTests.cs ファイルには次のコードが含まれるようになりました。
// unit test code
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace BankTests
{
[TestClass]
public class BankAccountTests
{
[TestMethod]
public void TestMethod1()
{
}
}
}
テスト対象のプロジェクトに using ステートメントを追加する
using ステートメントをクラスに追加して、完全修飾名を使用せずにテスト対象のプロジェクトに呼び出すことができるようにすることも可能です。クラス ファイルの先頭に次のように追加します。
using BankAccountNS;
テスト クラスの要件
テスト クラスの最小要件は次のとおりです。
マネージ コード用の Microsoft 単体テスト フレームワークでは、テスト エクスプローラーで実行する単体テスト メソッドを含むすべてのクラスについて、[TestClass] 属性が必要です。
テスト エクスプローラーで実行する各テスト メソッドには [TestMethod] 属性が必要です。
単体テスト プロジェクトで [TestClass] 属性がない別のクラスを使用することができます。また、テスト クラスで [TestMethod] 属性がない別のメソッドを使用することもできます。こうした別のクラスやメソッドをテスト メソッドで使用できます。
最初のテスト メソッドを作成する
この手順では、Debit クラスの BankAccount メソッドの動作を検証する単体テスト メソッドを記述します。メソッドは上に一覧表示されています。
テスト対象のメソッドを分析したところ、チェックする必要のある動作が 3 つ以上あると判断されます。
現金の引き出し金額が残高を上回る場合、このメソッドは [ArgumentOutOfRangeException] をスローします。
また、引き出し金額が 0 未満の場合も、このメソッドは ArgumentOutOfRangeException をスローします。
1. と 2. のチェックで金額が有効な範囲内であることが確認された場合、このメソッドは口座残高から当該金額を減算します。
最初のテストでは、有効な金額 (口座残高未満かつ 0 を上回る金額) を使用して口座から正しい金額を引き出します。
テスト メソッドを作成するには
使用する BankAccountNS; ステートメントを BankAccountTests.c ファイルに追加します。
次のメソッドを BankAccountTests クラスに追加します。
// unit test code [TestMethod] public void Debit_WithValidAmount_UpdatesBalance() { // arrange double beginningBalance = 11.99; double debitAmount = 4.55; double expected = 7.44; BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance); // act account.Debit(debitAmount); // assert double actual = account.Balance; Assert.AreEqual(expected, actual, 0.001, "Account not debited correctly"); }
このメソッドはやや単純です。期首残高を含む新しい BankAccount オブジェクトを設定し、有効な金額を引き出します。マネージ コード用の Microsoft 単体テスト フレームワークの AreEqual メソッドを使用して、期末残高が想定どおりであることを確認します。
テスト メソッドの要件
テスト メソッドは次の条件を満たしている必要があります。
メソッドは [TestMethod] 属性で装飾される必要があります。
メソッドは void を返します。
メソッドはパラメーターを含むことができません。
テストをビルドして実行する
テストをビルドして実行するには
[ビルド] メニューの [ソリューションのビルド] をクリックします。
エラーがない場合は、[UnitTestExplorer] ウィンドウが表示され、[テストを実行しない] グループに [Debit_WithValidAmount_UpdatesBalance] が表示されます。ビルドの成功後にテスト エクスプローラーが表示されない場合は、メニューの [テスト] をクリックし、[ウィンドウ]、[テスト エクスプローラー] の順にクリックします。
[すべて実行] をクリックしてテストを実行します。テストの実行中は、ウィンドウの上部にあるステータス バーがアニメーション化されます。テストの実行の終了時に、すべてのテスト メソッドが成功した場合はステータス バーが緑色に変わり、いずれかのテストが失敗した場合は赤色に変わります。
この場合は、テストが失敗します。このテスト メソッドは、[失敗したテスト] に移動します。グループ。エクスプローラーでテスト メソッドを選択すると、ウィンドウの下部に詳細が表示されます。
コードを修正してテストを再実行する
テスト結果を分析する
テスト結果には失敗を示すメッセージが含まれています。AreEquals メソッドについて、メッセージには、想定された事項 (Expected<XXX> パラメーター) および実際に受け取られた事項 (Actual<YYY> パラメーター) が示されます。ここでは、残高が期首残高よりも減少していることを想定していましたが、逆に、引き出し額の分が増加していました。
Debit コードの再検査では、単体テストでバグを検出できたことが示されます。引き出し額は、減算する必要があるときに口座残高に追加されます。
バグを修正する
エラーを修正するには、単純に行を置き換えます。
m_balance += amount;
with
m_balance -= amount;
テストを再実行する
テスト エクスプローラーで、[すべて実行] をクリックしてテストを再実行します。赤色/緑色のステータス バーは緑色になり、テストは [成功したテスト] グループに移動します。
単体テストを使用してコードを改良する
このセクションでは、分析の反復処理、単体テストの進展、およびリファクタリングが、実稼働のコードの堅牢性と有効性を高めるうえでどのように役立つかを説明します。
問題を分析する
Debit メソッドで有効な金額が正しく差し引かれることを確認するテスト メソッドを作成したら、元の分析における残りのケースに移ることができます。
現金の引き出し金額が残高を上回る場合、このメソッドは ArgumentOutOfRangeException をスローします。
また、引き出し金額が 0 未満の場合も、このメソッドは ArgumentOutOfRangeException をスローします。
テスト メソッドを作成する
これらの問題に対処するためのテスト メソッド作成への最初の試みは、期待の持てるものです。
//unit test method
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange()
{
// arrange
double beginningBalance = 11.99;
double debitAmount = -100.00;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// act
account.Debit(debitAmount);
// assert is handled by ExpectedException
}
ExpectedExceptionAttribute 属性を使用して、正しい例外がスローされたことをアサートします。この属性により、ArgumentOutOfRangeException がスローされない限り、テストは失敗します。正と負の両方の debitAmount 値を使用してテストを実行し、金額が 0 未満のときに汎用的な ApplicationException をスローするようにメソッドを一時的に変更すると、テストが正しく動作することが示されます。引き出し金額が残高を上回るケースをテストするために必要なことは次のとおりです。
Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange という新しいテスト メソッドを作成します。
メソッド本体を Debit_WhenAmountIsLessThanZero_ShouldThrowArgumentOutOfRange から新しいメソッドにコピーします。
debitAmount を、残高を上回る数値に設定します。
テストを実行
debitAmount を異なる値にして 2 つのメソッドを実行すると、テストで残りのケースが適切に処理されることが示されます。3 つのすべてのテストを実行すると、元の分析のすべてのケースが正しく処理されることを確認できます。
分析を継続する
一方で、最後の 2 つのテスト メソッドには多少の問題もあります。一方のテストの実行時に、テスト対象のコードのいずれの条件がスローされたかがはっきりしません。そこで、2 つの条件を区別するのに役立つ方法があります。この問題についてさらに考えてみると、いずれの条件に違反したかを把握することでテストの信頼性が高まることが明らかになります。また、この情報は、テスト対象のメソッドで例外がスローされたときに例外を処理する実稼働のメカニズムにも役立つ可能性があります。メソッドでのスロー時により多くの情報が生成されると、すべての懸念事項に役立ちますが、ExpectedException 属性ではこの情報を提供できません。
テスト対象のメソッドをもう一度確認すると、引数の名前をパラメーターとして使用する ArgumentOutOfRangeException コンストラクターが両方の条件ステートメントで使用されていることがわかります。
throw new ArgumentOutOfRangeException("amount");
また、MSDN ライブラリを検索すると、さらに豊富な情報を報告するコンストラクターが存在することがわかります。#ctor(String, Object, String) には、引数の名前、引数の値、およびユーザー定義のメッセージが含まれています。このコンストラクターを使用するようにテスト対象のメソッドをリファクタリングできます。さらに、一般に公開されている型メンバーを使用して、エラーを指定できます。
テスト対象のコードをリファクタリングする
最初に、エラー メッセージの 2 つの定数をクラス スコープで定義します。
// class under test
public const string DebitAmountExceedsBalanceMessage = "Debit amount exceeds balance";
public const string DebitAmountLessThanZeroMessage = "Debit amount less than zero";
次に、Debit メソッドの 2 つの条件ステートメントを変更します。
// method under test
// ...
if (amount > m_balance)
{
throw new ArgumentOutOfRangeException("amount", amount, DebitAmountExceedsBalanceMessage);
}
if (amount < 0)
{
throw new ArgumentOutOfRangeException("amount", amount, DebitAmountLessThanZeroMessage);
}
// ...
テスト メソッドをリファクタリングする
このテスト メソッドでは、最初に ExpectedException 属性を削除します。その代わりに、スローされた例外をキャッチし、その例外が正しい条件ステートメントでスローされたことを確認します。また一方、2 つのオプションのどちらを使用して残りの条件を検証するかをここで決める必要があります。たとえば Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange メソッドでは、次のアクションのいずれかを使用できます。
例外 (ActualValue コンストラクターの 2 番目のパラメーター) の ArgumentOutOfRangeException プロパティが期首残高を上回っていることをアサートします。このオプションでは、テスト メソッドの ActualValue 変数に対して例外の beginningBalance プロパティをテストし、ActualValue が 0 を上回ることを確認する必要があります。
DebitAmountExceedsBalanceMessage クラスに定義された BankAccount がメッセージ (コンストラクターの 3 番目のパラメーター) に含まれていることをアサートします。
Microsoft 単体テスト フレームワークの StringAssert.Contains メソッドでは、1 番目のオプションで必要とされる計算を行わなくても、2 番目のオプションを確認できます。
Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange の変更の 2 回目の試行は次のようになります。
[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
// arrange
double beginningBalance = 11.99;
double debitAmount = 20.0;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// act
try
{
account.Debit(debitAmount);
}
catch (ArgumentOutOfRangeException e)
{
// assert
StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage);
}
}
再テストする、書き換える、再分析する
テスト メソッドを別の値で再テストする場合、次の事項が発生します。
debitAmount が残高を上回るというアサートを使用することで適切なエラーをキャッチした場合、Contains アサートは成功し、例外は無視されます。したがって、テスト メソッドは成功することになります。これは想定どおりの動作です。
0 より小さい debitAmount を使用した場合、不適切なエラー メッセージが返されるため、アサートは失敗します。テスト対象メソッドのコード パス上の別のポイントで一時的な ArgumentOutOfRange 例外を導入した場合も、アサートは失敗します。これも適切です。
debitAmount 値が有効な場合 (残高未満だが 0 よりは大きい場合)、例外はキャッチされないので、アサートはキャッチされません。テスト メソッドは成功します。これは適切ではありません。例外がスローされない場合はテスト メソッドが失敗することを想定しているためです。
3 番目の事項はテスト メソッドのバグです。この問題を解決しようとする場合は、テスト メソッドの最後に Fail アサートを追加して、例外がスローされないケースを処理するようにします。
ただし、再テストでは、正しい例外がキャッチされた場合にテストが失敗したことが示されます。catch ステートメントで例外がリセットされ、メソッドは継続して実行されますが、新しいアサートで失敗します。この新しい問題を解決するために、return の後に StringAssert ステートメントを追加します。再テストにより、問題を修正したことを確認します。Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange の最終バージョンは、次のようになります。
[TestMethod]
public void Debit_WhenAmountIsMoreThanBalance_ShouldThrowArgumentOutOfRange()
{
// arrange
double beginningBalance = 11.99;
double debitAmount = 20.0;
BankAccount account = new BankAccount("Mr. Bryan Walton", beginningBalance);
// act
try
{
account.Debit(debitAmount);
}
catch (ArgumentOutOfRangeException e)
{
// assert
StringAssert.Contains(e.Message, BankAccount. DebitAmountExceedsBalanceMessage);
return;
}
Assert.Fail("No exception was thrown.");
}
この最後のセクションでは、テスト コード向上の作業を実行したことにより、より堅牢でわかりやすいテスト メソッドになりました。さらに重要なこととして、分析の追加によってテスト対象のプロジェクトのコードを改善することもできます。