ストア アプリ用の Visual C++ DLL の単体テスト
このトピックでは、Visual Studio 2012 Express for Windows 8 および C++ 用の Microsoft 単体テスト フレームワークを使用して Windows ストア アプリの C++ DLL の単体テストを作成する方法の 1 つについて説明します。RooterLib DLL は、指定した数値の平方根の概算を計算する関数を実装することによって、微積分の限界理論の不明瞭なメモリを示します。DLL は、数学で実行できる楽しいことをユーザーに示す Windows ストア アプリに含めることができます。
注意
このセクションのトピックでは、Visual Studio 2012 Express for Windows 8 の機能について説明します。Visual Studio Ultimate、VS Premium、および VS Professional には、単体テストの追加機能が備わっています。
-
VS Ultimate、VS Premium、および VS Professional では、Microsoft テスト エクスプローラーのアドオン アダプターを作成したサードパーティまたはオープン ソースの単体テスト フレームワークを使用できます。また、テストのコード カバレッジ情報を分析して表示することもできます。
-
VS Ultimate および VS Premium では、ビルドの後に毎回テストを実行できます。
詳細については、MSDN ライブラリの「単体テストを使用したコードの検証」を参照してください。
このトピックでは、開発の第一歩として単体テストを使用する方法を示します。この方法ではまず、テスト対象のシステムの特定の動作を検証するテスト メソッドを作成し、テストに合格するコードを記述します。後述する手順の順序を変更することにより、この方法を逆にして、テストするコードを最初に記述し、単体テストを作成することができます。
このトピックでは、テストする単体テストと DLL に 1 つの Visual Studio ソリューションと個別のプロジェクトも作成します。また、DLL プロジェクトに単体テストを直接含めることも、単体テストと .DLL ごとに個別のソリューションを作成することもできます。使用できる構造体のヒントについては、「テスト エクスプローラーを使用した既存の C++ アプリケーションの単体テスト」を参照してください。
このトピックの内容
ここでは次のタスクを実行します。
ソリューションと単体テスト プロジェクトを作成する
テスト エクスプローラーでテストの実行を確認します。
DLL プロジェクトをソリューションに追加する
dll プロジェクトにテスト プロジェクトを結合します。
テストを繰り返し増やして、成功させる
失敗したテストをデバッグする
テストを変更せずにコードをリファクタリングする
ソリューションと単体テスト プロジェクトを作成する
[ファイル] メニューの [新規作成] をポイントし、[新しいプロジェクト] をクリックします。
[新しいプロジェクト] ダイアログ ボックスで [インストール済み]、[Visual C++] の順に展開し、[Windows ストア] をクリックします。プロジェクト テンプレートの一覧の [単体テスト ライブラリ (Windows ストア アプリ)] をクリックします。
プロジェクトに「RooterLibTests」という名前を付け、場所を指定します。ソリューションに「RooterLib」という名前を付け、[ソリューションのディレクトリを作成] チェックボックスがオンになっていることを確認します。
新しいプロジェクトで unittest1.cpp を開きます。
次の点に注意してください。
各テストは TEST_METHOD(YourTestName){...} を使用して定義されます。
従来の関数のシグネチャを記述する必要はありません。シグネチャは、マクロ TEST_METHOD によって作成されます。マクロは、void を返すインスタンス関数を生成します。また、テスト メソッドに関する情報を返す静的関数を生成します。この情報により、テスト エクスプローラーはメソッドを見つけることができます。
テスト メソッドは、TEST_CLASS(YourClassName){...} を使用して、クラスにグループ化されます。
テストの実行時に、各テスト クラスのインスタンスが作成されます。テスト メソッドは、不特定の順序で呼び出されます。各モジュール、クラス、またはメソッドの前後に呼び出される特殊なメソッドを定義できます。詳細については、MSDN ライブラリの「Microsoft.VisualStudio.TestTools.CppUnitTestFramework の使用」を参照してください。
テスト エクスプローラーでテストの実行を確認します。
テスト コードを挿入します。
TEST_METHOD(TestMethod1) { Assert::AreEqual(1,1); }
Assert クラスは、テスト メソッドの結果を検証するために使用できる複数の静的メソッドを提供します。
[テスト] メニューの [実行] をポイントし、[すべて実行] をクリックします。
テスト プロジェクトがビルドされ、実行されます。テスト エクスプローラーのウィンドウが表示され、テストが [成功したテスト] に表示されます。ウィンドウの下部の [概要] ウィンドウに、選択したテストに関する詳細情報が表示されます。
DLL プロジェクトをソリューションに追加する
ソリューション エクスプローラーでソリューション名を選択します。ショートカット メニューの [追加] をポイントし、[新しいプロジェクトの追加] をクリックします。
[新しいプロジェクトの追加] ダイアログ ボックスの [DLL (Windows ストア アプリ)] を選択します。
次のコードを RooterLib.h ファイルに追加します。
// The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the ROOTERLIB_EXPORTS // symbol defined on the command line. This symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // ROOTERLIB_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef ROOTERLIB_EXPORTS #define ROOTERLIB_API __declspec(dllexport) #else #define ROOTERLIB_API __declspec(dllimport) #endif //ROOTERLIB_EXPORTS class ROOTERLIB_API CRooterLib { public: CRooterLib(void); double SquareRoot(double v); };
コメントは、dll の開発者だけでなく、プロジェクトで DLL を参照するユーザーにも ifdef ブロックについて説明しています。DLL のプロジェクト プロパティを使用して、コマンド ラインに ROOTERLIB_EXPORTS シンボルを追加できます。
CRooterLib クラスは、コンストラクターと SqareRoot エスティメーターのメソッドを宣言します。
コマンド ラインに ROOTERLIB_EXPORTS のシンボルを追加します。
ソリューション エクスプローラーで RooterLib プロジェクトをクリックし、ショートカット メニューの [プロパティ] をクリックします。
RooterLib の [プロパティ ページ] ダイアログ ボックスで [構成プロパティ]、[C++] の順に展開し、[プリプロセッサ] をクリックします。
[プリプロセッサの定義] ボックスの一覧の [<編集...>] をクリックし、[プリプロセッサの定義] ダイアログ ボックスに ROOTERLIB_EXPORTS を追加します。
宣言された関数の最小限の実装を追加します。RooterLib.cpp を開き、次のコードを追加します。
// constructor CRooterLib::CRooterLib() { } // Find the square root of a number. double CRooterLib::SquareRoot(double v) { return 0.0; }
dll プロジェクトにテスト プロジェクトを結合します。
RooterLibTests プロジェクトに RooterLib を追加します。
ソリューション エクスプローラーで RooterLibTests プロジェクトを選択し、ショートカット メニューの [参照] をクリックします。
RooterLib の[プロジェクトのプロパティ] ダイアログ ボックスで [共通プロパティ] を展開し、[Framework と参照] をクリックします。
[新しい参照の追加] をクリックします。
[参照の追加] ダイアログ ボックスで [ソリューション] を展開し、[プロジェクト] をクリックします。次に [RouterLib] 項目をクリックします。
unittest1.cpp に RooterLib のヘッダー ファイルをインクルードします。
unittest1.cpp を開きます。
#include "CppUnitTest.h" 行の下に次のコードを追加します。
#include "..\RooterLib\RooterLib.h"
インポートした関数を使用するテストを追加します。unittest1.cpp に次のコードを追加します。
TEST_METHOD(BasicTest) { CRooterLib rooter; Assert::AreEqual( // Expected value: 0.0, // Actual value: rooter.SquareRoot(0.0), // Tolerance: 0.01, // Message: L"Basic test failed", // Line number - used if there is no PDB file: LINE_INFO()); }
ソリューションをビルドします。
新しいテストがテスト エクスプローラーの [テストを実行しない] ノードに表示されます。
テスト エクスプローラーで [すべて実行] をクリックします。
テストとコード プロジェクトを設定し、コード プロジェクトの関数を実行するテストを実行できることを確認しました。これで、実際のテストおよびコードの記述を開始できます。
テストを繰り返し増やして、成功させる
新しいテストを追加します。
TEST_METHOD(RangeTest) { CRooterLib rooter; for (double v = 1e-6; v < 1e6; v = v * 3.2) { double expected = v; double actual = rooter.SquareRoot(v*v); double tolerance = expected/1000; Assert::AreEqual(expected, actual, tolerance); } };
ヒント
成功したテストは、変更しないことをお勧めします。代わりに、新しいテストを追加し、テストが成功するようにコードを更新して、さらに別のテストを追加していきます。
ユーザーが要件を変更したら、不適切になったテストを無効にします。新しいテストを作成し、同じように増やしながら、1 つずつ機能させます。
テスト エクスプローラーで [すべて実行] をクリックします。
テストが失敗します。
ヒント
そのテストを作成した直後に、各テストが失敗することを確認します。これで、失敗することがないテストを作成するという簡単な誤りを回避できます。
新しいテストが成功するように、テスト対象のコードを増やします。次のコードに RooterLib.cpp を追加します。
#include <math.h> ... // Find the square root of a number. double CRooterLib::SquareRoot(double v) { double result = v; double diff = v; while (diff > result/1000) { double oldResult = result; result = result - (result*result - v)/(2*result); diff = abs (oldResult - result); } return result; }
ソリューションをビルドし、テスト エクスプローラーで [すべて実行] をクリックします。
両方のテストが成功します。
ヒント
テストを 1 つずつ追加して、コードを開発します。テストを繰り返すたびに、すべてのテストが成功することを確認します。
失敗したテストをデバッグする
別のテストを unittest1.cpp に追加します。
// Verify that negative inputs throw an exception. TEST_METHOD(NegativeRangeTest) { wchar_t message[200]; CRooterLib rooter; for (double v = -0.1; v > -3.0; v = v - 0.5) { try { // Should raise an exception: double result = rooter.SquareRoot(v); swprintf_s(message, L"No exception for input %g", v); Assert::Fail(message, LINE_INFO()); } catch (std::out_of_range ex) { continue; // Correct exception. } catch (...) { swprintf_s(message, L"Incorrect exception for %g", v); Assert::Fail(message, LINE_INFO()); } } };
テスト エクスプローラーで [すべて実行] をクリックします。
テストが失敗します。テスト エクスプローラーでテスト名を選択します。アサーション エラーが強調表示されます。エラー メッセージは、テスト エクスプローラーの詳細ペインに表示されます。
テストが失敗した理由を確認するには、関数をステップ実行します。
SquareRoot 関数の先頭にブレークポイントを設定します。
失敗したテストのショートカット メニューの [選択したテストのデバッグ] をクリックします。
ブレークポイントで実行が停止したら、コードをステップ実行します。
例外をキャッチするには、RooterLib.cpp にコードを追加します。
#include <stdexcept> ... double CRooterLib::SquareRoot(double v) { //Validate the input parameter: if (v < 0.0) { throw std::out_of_range("Can't do square roots of negatives"); } ...
テスト エクスプローラーで [すべて実行] をクリックして、修正されたメソッドをテストし、回帰が生じていないことを確認します。
これで、すべてのテストが合格しました。
テストを変更せずにコードをリファクタリングする
SquareRoot 関数の計算全体を簡略化します。
// old code //result = result - (result*result - v)/(2*result); // new code result = (result + v/result) / 2.0;
[すべて実行] をクリックして、リファクタリングされたメソッドをテストし、回帰が生じていないことを確認します。
ヒント
安定した一連の適切な単体テストを実行することで、コードを変更したときにバグが生じていないことを確信できます。
リファクタリングは、他の変更から切り離しておきます。