チュートリアル: テスト エクスプローラーを使用したテスト駆動型開発
単体テストを作成すれば、段階的にコードに変更を加える方法でコードの正しい動作を容易に維持することができます。 サード パーティ製を含めていくつかのフレームワークを単体テストの記述に使用できます。 一部のテスト フレームワークは、別々の言語またはプラットフォームでのテストに特化されています。 テスト エクスプローラーは、1 つのインターフェイスで、どのフレームワークでの単体テストにも対応します。 テスト エクスプローラーの詳細については、「テスト エクスプローラーを使用して単体テストを実行する」およびテスト エクスプローラーに関する FAQ をご覧ください。
このチュートリアルでは、Microsoft テスト フレームワーク (MSTest) を使用して、テスト済みメソッドを C# で開発する方法を示します。 他の言語にも、NUnit など他のテスト フレームワークにも容易に適合させることができます。 詳細については、「サードパーティ製の単体テスト フレームワークをインストールする」をご覧ください。
テストを作成してコードを生成する
.NET または .NET Standard 用の C# クラス ライブラリ プロジェクトを作成します。 このプロジェクトには、テストするコードを含めます。 プロジェクトには「MyMath」という名前を付けます。
同じソリューションに、.NET 用の新しい MSTest テスト プロジェクトを追加します。
Visual Studio 2019 バージョン 16.9 では、MSTest プロジェクト テンプレートの名前は単体テスト プロジェクトです。
このテスト プロジェクトには「MathTests」という名前を付けます。
特定の入力に対して取得される結果を検証するシンプルなテスト メソッドを記述します。 以下のコードを
UnitTest1
クラスに追加します。[TestMethod] public void BasicRooterTest() { // Create an instance to test: Rooter rooter = new Rooter(); // Define a test input and output value: double expectedResult = 2.0; double input = expectedResult * expectedResult; // Run the method under test: double actualResult = rooter.SquareRoot(input); // Verify the result: Assert.AreEqual(expectedResult, actualResult, delta: expectedResult / 100); }
テスト コードから型を作成します。
Rooter
にカーソルを置き、電球メニューから [型 'Rooter' を生成する]>[新しい型の生成] の順に選択します。[型の生成] ダイアログ ボックスで、[プロジェクト] を [MyMath] (クラス ライブラリ プロジェクト) に設定して、[OK] を選択します。
テスト コードからメソッドを生成します。 カーソルを
SquareRoot
に置いて、電球メニューから Generate method 'Rooter.SquareRoot' を選択します。単体テストを実行します。
テスト エクスプローラーを開きます。
[テスト] メニューからテスト エクスプローラーを開くには、[テスト エクスプローラー] を選択します。
[テスト] メニューからテスト エクスプローラーを開くには、[Windows]>[テスト エクスプローラー] を選択します。
テスト エクスプローラーで、[すべて実行] ボタンを選択してテストを再実行します。
ソリューションがビルドされ、テストが実行されて失敗します。
テストの名前を選択します。
テストの詳細は、[テスト詳細の概要] ウィンドウに表示されます。
[スタック トレース] の下にあるトップ リンクを選択して、テストが失敗した場所に移動します。
この時点で、テストとスタブが作成されています。テストに合格するには、これらを修正する必要があります。
コードの変更を確認する
Class1.cs ファイルで、
SquareRoot
のコードを改良します。public double SquareRoot(double input) { return input / 2; }
テスト エクスプローラーで [すべて実行] をクリックします。
ソリューションがビルドされ、テストが実行されて合格します。
入力の範囲を拡張する
あらゆる場合にコードが動作するという信頼性を強化するには、より広範囲の入力値を試みるテストを追加します。
ヒント
合格した既存のテストは変更しないでください。 代わりに、新しいテストを追加します。 既存のテストを変更するのは、ユーザー要件が変更されたときのみに限定します。 このポリシーにより、コードを拡張する作業を行うときに、既存の機能が失われないようにすることができます。
一定範囲の入力値を試みるために、次のテストをテスト クラスに追加します。
[TestMethod] public void RooterValueRange() { // Create an instance to test. Rooter rooter = new Rooter(); // Try a range of values. for (double expected = 1e-8; expected < 1e+8; expected *= 3.2) { RooterOneValue(rooter, expected); } } private void RooterOneValue(Rooter rooter, double expectedResult) { double input = expectedResult * expectedResult; double actualResult = rooter.SquareRoot(input); Assert.AreEqual(expectedResult, actualResult, delta: expectedResult / 1000); }
テスト エクスプローラーで [すべて実行] をクリックします。
(最初のテストには今回も合格しますが) 新しいテストには失敗します。 失敗した箇所を確認するには、失敗しているテストを選択し、[テスト詳細の概要] ウィンドウで詳細を確認します。
テスト対象のメソッドを調べて、問題点を確認します。
SquareRoot
コードを次のように変更します。public double SquareRoot(double input) { double result = input; double previousResult = -input; while (Math.Abs(previousResult - result) > result / 1000) { previousResult = result; result = result - (result * result - input) / (2 * result); } return result; }
テスト エクスプローラーで [すべて実行] をクリックします。
今回は、両方のテストに合格します。
例外的なケース用にテストを追加する
負数の入力用に新しいテストを追加します。
[TestMethod] public void RooterTestNegativeInput() { Rooter rooter = new Rooter(); Assert.ThrowsException<ArgumentOutOfRangeException>(() => rooter.SquareRoot(-1)); }
テスト エクスプローラーで [すべて実行] をクリックします。
テスト対象のメソッドはループするため、実行を手動で取り消す必要があります。
テスト エクスプローラーのツールバーの [キャンセル] を選択します。
テストの実行が停止します。
メソッドの先頭に次の
if
ステートメントを追加して、SquareRoot
コードを修正します。public double SquareRoot(double input) { if (input <= 0.0) { throw new ArgumentOutOfRangeException(); } ...
テスト エクスプローラーで [すべて実行] をクリックします。
すべてのテストに合格します。
テスト対象のコードをリファクタリングする
テストを変更せずに、コードをリファクターします。
ヒント
"リファクタリング" とは、コードのパフォーマンスを高めたり、コードをわかりやすくすることを目的として行う変更です。 コードの動作を変更することを意図しないため、テストは変更されません。
リファクタリングの手順は、機能を拡張する手順とは別に実行することをお勧めします。 テストを変更しないことで、リファクタリング時に誤ってバグが生じる状況を回避できます。
result
メソッドでSquareRoot
を計算する行を次のように変更します。public double SquareRoot(double input) { if (input <= 0.0) { throw new ArgumentOutOfRangeException(); } double result = input; double previousResult = -input; while (Math.Abs(previousResult - result) > result / 1000) { previousResult = result; result = (result + input / result) / 2; //was: result = result - (result * result - input) / (2*result); } return result; }
[すべて実行] を選択し、すべてのテストに引き続き合格することを確認します。