UWP アプリ用の Visual C++ DLL をテストする方法How to test a Visual C++ DLL for UWP apps

このトピックでは、C++ 用の Microsoft テスト フレームワークを使用して、ユニバーサル Windows プラットフォーム (UWP) アプリ用の C++ DLL の単体テストを作成する 1 つの方法について説明します。This topic describes one way to create unit tests for a C++ DLL for Universal Windows Platform (UWP) apps with the Microsoft Test Framework for C++. RooterLib DLL は、指定した数値の平方根の概数を計算する関数を実装することによって、微積分の限界理論の不明瞭なメモリを示します。The RooterLib DLL demonstrates vague memories of limit theory from the calculus by implementing a function that calculates an estimate of the square root of a given number. この DLL を、ユーザーが数学で実行できる楽しい作業を提示する UWP アプリに組み込むことができます。The DLL might then be included in a UWP app that shows a user the fun things that can be done with math.

このトピックでは、開発の第一歩として単体テストを使用する方法を示します。This topic shows you how to use unit testing as the first step in development. この方法ではまず、テスト対象のシステムの特定の動作を検証するテスト メソッドを作成し、テストに合格するコードを記述します。In this approach, you first write a test method that verifies a specific behavior in the system that you are testing and then you write the code that passes the test. 後述する手順の順序を変更することにより、この方法を逆にして、テストするコードを最初に記述し、単体テストを作成することができます。By making changes in the order of the following procedures, you can reverse this strategy to first write the code that you want to test and then write the unit tests.

このトピックでは、テストする単体テストと DLL に 1 つの Visual Studio ソリューションと個別のプロジェクトも作成します。This topic also creates a single Visual Studio solution and separate projects for the unit tests and the DLL that you want to test. また、DLL プロジェクトに単体テストを直接含めることも、単体テストと DLL ごとに個別のソリューションを作成することもできます。You can also include the unit tests directly in the DLL project, or you can create separate solutions for the unit tests and the .DLL. 使用できる構造体のヒントについては、「テスト エクスプローラーを使用した既存の C++ アプリケーションの単体テスト」をご覧ください。See Adding unit tests to existing C++ applications for tips on which structure to use.

このトピックの内容In this topic

ソリューションと単体テスト プロジェクトを作成するCreate the solution and the unit test project

テストがテスト エクスプ ローラーで実行されることを確認するVerify that the tests run in Test Explorer

DLL プロジェクトをソリューションに追加するAdd the DLL project to the solution

DLL 関数をテスト コードに表示させるMake the DLL functions visible to the test code

テストを繰り返し増やして成功させるIteratively augment the tests and make them pass

失敗したテストをデバッグするDebug a failing test

テストを変更せずにコードをリファクタリングするRefactor the code without changing tests

ソリューションと単体テスト プロジェクトを作成するCreate the solution and the unit test project

  1. [ファイル] メニューの [新規作成] をポイントし、[新しいプロジェクト] をクリックします。On the File menu, choose New, and then choose New Project.

  2. [新しいプロジェクト] ダイアログで [インストール済み][Visual C++] の順に展開し、[UWP] を選択します。On the New Project dialog, expand Installed, then expand Visual C++ and choose UWP. 次に、プロジェクト テンプレートの一覧から [単体テスト ライブラリ (UWP アプリ)] を選択します。Then choose Unit Test Library (UWP apps) from the list of project templates.

    C++ 単体テスト ライブラリの作成Create a C++ unit test library

  3. プロジェクトに RooterLibTests という名前を付けます。場所を指定します。ソリューションに RooterLib という名前を付けます。[ソリューションのディレクトリを作成] チェックボックスがオンになっていることを確認します。Name the project RooterLibTests; specify the location; name the solution RooterLib; and make sure Create directory for solution is checked.

    ソリューションとプロジェクトの名前と場所を指定Specify the solution and project name and location

  4. 新しいプロジェクトで、unittest1.cpp を開きます。In the new project, open unittest1.cpp.

    unittest1.cppunittest1.cpp

    次の点に注意してください。Note that:

    • 各テストは TEST_METHOD(YourTestName){...} を使用して定義されます。Each test is defined by using TEST_METHOD(YourTestName){...}.

      従来の関数の署名を記述する必要はありません。You do not have to write a conventional function signature. 署名は、マクロ TEST_METHOD によって作成されます。The signature is created by the macro TEST_METHOD. マクロは、void を返すインスタンス関数を生成します。The macro generates an instance function that returns void. また、テスト メソッドに関する情報を返す静的関数も生成します。It also generates a static function that returns information about the test method. この情報により、テスト エクスプ ローラーはメソッドを見つけます。This information allows the test explorer to find the method.

    • テスト メソッドは、 TEST_CLASS(YourClassName){...}を使用してクラスにグループ化されます。Test methods are grouped into classes by using TEST_CLASS(YourClassName){...}.

      テストが実行されると、各テスト クラスのインスタンスが作成されます。When the tests are run, an instance of each test class is created. テスト メソッドが呼び出される順序は決まっていません。The test methods are called in an unspecified order. 各モジュール、クラス、またはメソッドの前後に呼び出される特殊なメソッドを定義することができます。You can define special methods that are invoked before and after each module, class, or method. 詳細については、MSDN ライブラリの「Microsoft.VisualStudio.TestTools.CppUnitTestFramework の使用」をご覧ください。For more information, see Using Microsoft.VisualStudio.TestTools.CppUnitTestFramework in the MSDN Library.

テストがテスト エクスプローラーで実行されることを確認するVerify that the tests run in Test Explorer

  1. 幾らかのテスト コードを挿入します。Insert some test code:

    TEST_METHOD(TestMethod1)  
    {  
        Assert::AreEqual(1,1);  
    }  
    

    Assert クラスは、テスト メソッドで結果を確認するために使用するいくつかの静的メソッドを提供することに注意してください。Notice that the Assert class provides several static methods that you can use to verify results in test methods.

  2. [テスト] メニューの [実行] をポイントし、[すべて実行] をクリックします。On the Test menu, choose Run and then choose Run All.

    テスト プロジェクトがビルドされ、実行されます。The test project builds and runs. テスト エクスプローラーのウィンドウが表示され、テストが [成功したテスト] に表示されます。The Test Explorer window appears, and the test is listed under Passed Tests. ウィンドウの下部の [概要] ウィンドウに、選択したテストに関する詳細情報が表示されます。The Summary pane at the bottom of the window provides additional details about the selected test.

    テスト エクスプ ローラーTest Explorer

DLL プロジェクトをソリューションに追加するAdd the DLL project to the solution

  1. ソリューション エクスプローラーでソリューション名を選択します。In Solution Explorer, choose the solution name. ショートカット メニューの [追加] をポイントし、[新しいプロジェクトの追加] を選択します。From the shortcut menu, choose Add, and then Add New Project.

    RooterLib プロジェクトの作成Create the RooterLib project

  2. [新しいプロジェクトの追加] ダイアログ ボックスの [DLL (UWP アプリ)] を選択します。In the Add New Project dialog box, choose DLL (UWP apps).

  3. RooterLib.h ファイルに次のコードを追加します。Add the following code to the RooterLib.h file:

    // 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 ブロックについて説明しています。The comments explain the ifdef block not only to the developer of the dll, but to anyone who references the DLL in their project. DLL のプロジェクト プロパティを使用して、コマンド ラインに ROOTERLIB_EXPORTS シンボルを追加できます。You can add the ROOTERLIB_EXPORTS symbol to the command line by using the project properties of the DLL.

    CRooterLib クラスは、コンストラクターと SqareRoot エスティメーターのメソッドを宣言します。The CRooterLib class declares a constructor and the SqareRoot estimator method.

  4. コマンド ラインに ROOTERLIB_EXPORTS のシンボルを追加します。Add the ROOTERLIB_EXPORTS symbol to the command line.

    1. ソリューション エクスプローラーで RooterLib プロジェクトを選択し、ショートカット メニューの [プロパティ] を選択します。In Solution Explorer, choose the RooterLib project, and then choose Properties from the shortcut menu.

      プリプロセッサ シンボル定義の追加Add a preprocessor symbol definition

    2. RooterLib の [プロパティ ページ] ダイアログ ボックスで [構成プロパティ][C++] の順に展開し、[プリプロセッサ] を選択します。In the RooterLib Property Page dialog box, expand Configuration Properties, expand C++ and choose Preprocessor.

    3. [プリプロセッサの定義] ボックスの一覧の [<編集...>] を選択し、[プリプロセッサの定義] ダイアログ ボックスに ROOTERLIB_EXPORTS を追加します。Choose <Edit...> from the Preprocessor Definitions list, and then add ROOTERLIB_EXPORTS in the Preprocessor Definitions dialog box.

  5. 宣言された関数の最小限の実装を追加します。Add minimal implementations of the declared functions. RooterLib.cpp を開き、次のコードを追加します。Open RooterLib.cpp and add the following code:

    // constructor  
    CRooterLib::CRooterLib()  
    {  
    }  
    
    // Find the square root of a number.  
    double CRooterLib::SquareRoot(double v)  
    {  
        return 0.0;  
    }  
    

DLL 関数をテスト コードに表示させるMake the dll functions visible to the test code

  1. RooterLibTests プロジェクトに RooterLib を追加します。Add RooterLib to the RooterLibTests project.

    1. ソリューション エクスプローラーで RooterLibTests プロジェクトを選択し、ショートカット メニューの [参照...] を選択します。In Solution Explorer, choose the RooterLibTests project and then choose References... on the shortcut menu.

    2. RooterLib の [プロジェクトのプロパティ] ダイアログ ボックスで [共通プロパティ] を展開し、[Framework と参照] を選択します。On the RooterLib Project Properties dialog box, expand Common Properties and choose Framework and References.

    3. [新しい参照の追加...] をクリックします。Choose Add New Reference....

    4. [参照の追加] ダイアログ ボックスで [ソリューション] を展開し、[プロジェクト] を選択します。In the Add Reference dialog box, expand Solution and then choose Projects. 次に [RouterLib] 項目を選択します。Then select the RouterLib item.

  2. unittest1.cpp に RooterLib のヘッダー ファイルをインクルードします。Include the RooterLib header file in unittest1.cpp.

    1. unittest1.cpp を開きます。Open unittest1.cpp.

    2. #include "CppUnitTest.h" 行の下に次のコードを追加します。Add this code to below the #include "CppUnitTest.h" line:

      #include "..\RooterLib\RooterLib.h"  
      
  3. インポートした関数を使用するテストを追加します。Add a test that uses the imported function. unittest1.cpp に次のコードを追加します。Add the following code to 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());  
    }  
    
  4. ソリューションをビルドします。Build the solution.

    新しいテストがテスト エクスプローラーの [テストを実行しない] ノードに表示されます。The new test appears in Test Explorer in the Not Run Tests node.

  5. テスト エクスプローラーで [すべて実行]をクリックします。In Test Explorer, choose Run All.

    基本テスト成功Basic Test passed

    テストとコード プロジェクトをセット アップして、コード プロジェクトで関数を実行するテストを実行できることを確認しました。You have set up the test and the code projects, and verified that you can run tests that run functions in the code project. ここで、実際のテストおよびコードの記述を開始できます。Now you can begin to write real tests and code.

テストを繰り返し増やして成功させるIteratively augment the tests and make them pass

  1. 新しいテストを追加します。Add a new test:

    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);  
        }  
    };  
    

    ヒント

    合格したテスト内容を変更しないことをお勧めします。We recommend that you do not change tests that have passed. 代わりに、新しいテストを追加し、テストが合格するようにコードを更新してから別のテストを追加する、という過程を繰り返します。Instead, add a new test, update the code so that the test passes, and then add another test, and so on.

    ユーザーが要件を変更したら、正しくなくなったテストを無効にします。When your users change their requirements, disable the tests that are no longer correct. 新しいテストを作成し、一度に 1 つずつ、同じ増分方式で処理するようにします。Write new tests and make them work one at a time, in the same incremental manner.

  2. テスト エクスプローラーで [すべて実行]をクリックします。In Test Explorer, choose Run All.

  3. テストが失敗します。The test fails.

    RangeTest 失敗The RangeTest fails

    ヒント

    各テストが記述した後すぐに失敗することを確認します。Verify that each test fails immediately after you have written it. これは、絶対に失敗しないテストを記述するという簡単なミスを避けることに役立ちます。This helps you avoid the easy mistake of writing a test that never fails.

  4. 新しいテストが成功するように、テスト対象のコードを増やします。Enhance the code under test so that the new test passes. RooterLib.cpp に次のコードを追加します。Add the following to 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;  
    }  
    
  5. ソリューションをビルドし、テスト エクスプ ローラーで [すべて実行]を選択します。Build the solution and then in Test Explorer, choose Run All.

    両方のテストが合格します。Both tests pass.

ヒント

一度に 1 つのテストを追加してコードを開発します。Develop code by adding tests one at a time. 各反復処理の後にすべてのテストが合格することを確認します。Make sure that all the tests pass after each iteration.

失敗したテストをデバッグするDebug a failing test

  1. unittest1.cpp に別のテストを追加します。Add another test to 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());  
         }  
       }  
    };  
    
  2. テスト エクスプローラーで [すべて実行]をクリックします。In Test Explorer, choose Run All.

    テストが失敗します。The test fails. テスト エクスプローラーでテスト名を選択します。Choose the test name in Test Explorer. 失敗したアサーションが強調表示されます。The failed assertion is highlighted. エラー メッセージは、テスト エクスプ ローラーの [詳細] ウィンドウに表示されます。The failure message is visible in the detail pane of Test Explorer.

    Negativerangetest 失敗NegativeRangeTests failed

  3. テストが失敗した理由を表示するには、関数をステップ実行します。To see why the test fails, step through the function:

    1. SquareRoot 関数の先頭にブレークポイントを設定します。Set a breakpoint at the start of the SquareRoot function.

    2. 失敗したテストのショートカット メニューで [選択したテストのデバッグ]をクリックします。On the shortcut menu of the failed test, choose Debug Selected Tests.

      実行がブレークポイントで停止したら、コードをステップ実行します。When the run stops at the breakpoint, step through the code.

    3. 例外をキャッチするには、RooterLib.cpp にコードを追加します。Add code to RooterLib.cpp to catch the exception:

      #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");  
          }  
      ...  
      
    4. テスト エクスプローラーで [すべて実行] をクリックして、修正されたメソッドをテストし、回帰が生じていないことを確認します。In Test Explorer, choose Run All to test the corrected method and make sure that you haven't introduced a regression.

    今回は、すべてのテストに合格します。All tests now pass.

    すべてのテストの成功All tests pass

テストを変更せずにコードをリファクタリングするRefactor the code without changing tests

  1. SquareRoot 関数の計算全体を簡略化します。Simplify the central calculation in the SquareRoot function:

    // old code  
    //result = result - (result*result - v)/(2*result);  
    // new code  
    result = (result + v/result) / 2.0;  
    
  2. [すべて実行] をクリックして、リファクタリングされたメソッドをテストし、回帰が生じていないことを確認します。Choose Run All to test the refactored method and make sure that you haven't introduced a regression.

    ヒント

    安定した一連の適切な単体テストを実行することで、コードを変更したときにバグが生じていないことを確信できます。A stable set of good unit tests gives confidence that you have not introduced bugs when you change the code.

    常に他の変更とは別にリファクタリングしてください。Keep refactoring separate from other changes.