共用方式為


對市集應用程式的 Visual C++ DLL 進行單元測試

本主題說明使用 Visual Studio 2012 Express for Windows 8 和 Microsoft Unit Testing Framework for C++,建立 Windows 市集應用程式所使用 C++ DLL 之單元測試的方式。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 Library 中的使用單元測試驗證程式碼

本主題說明如何使用單元測試做為開發工作的第一步。採用這種方式時,您會先撰寫測試方法,用來驗證要測試之系統中的特定行為,然後撰寫通過測試的程式碼。依照下列程序的順序進行變更,您就可以反轉策略,先撰寫要測試的程式碼,再撰寫單元測試。

本主題還會建立單一 Visual Studio 方案,以及用於單元測試和要測試之 DLL 的個別專案。您也可以直接在 DLL 專案中包含單元測試,或是針對單元測試和 .DLL 建立個別方案。如需要使用之結構的提示,請參閱使用測試總管針對現有 C++ 應用程式執行單元測試

本主題內容

本主題會帶領您執行下列工作:

建立方案和單元測試專案

驗證測試總管中執行的測試

將 DLL 專案新增至方案

將測試專案與 dll 專案結合

反覆擴大測試範圍並讓測試成功

對失敗的測試進行偵錯

重構程式碼但不變更測試

建立方案和單元測試專案

  1. 選擇 [檔案] 功能表上的 [新增],然後選擇 [新專案]。

  2. 在 [新增專案] 對話方塊上,展開 [已安裝的],然後展開 [Visual C++],並選擇 [Windows 市集]。接著從專案範本清單中選擇 [單元測試程式庫 (Windows 市集應用程式)]。

    建立 C++ 單元測試程式庫

  3. 將專案命名為 RooterLibTests,指定位置,將方案命名為 RooterLib,並確認已核取 [為方案建立目錄] 核取方塊。

    指定方案和專案的名稱和位置

  4. 在新專案中,開啟 unittest1.cpp

    unittest1.cpp

    請注意:

    • 每一項測試都是使用 TEST_METHOD(YourTestName){...} 定義。

      您不必撰寫傳統的函式簽章。簽章是由巨集 TEST_METHOD 所建立。巨集會產生傳回 void 的執行個體函式。另外還會產生靜態函式,傳回關於測試方法的資訊。這項資訊可以讓測試總管尋找方法。

    • 測試方法是使用 TEST_CLASS(YourClassName){...} 分組到不同類別。

      在測試執行時,會建立每個測試類別的執行個體。測試方法會依照非特定順序呼叫。您可以定義在每個模組、類別或方法之前和之後叫用的特殊方法。如需詳細資訊,請參閱 MSDN Library 中的使用 Microsoft.VisualStudio.TestTools.CppUnitTestFramework

驗證測試總管中執行的測試

  1. 插入一些測試程式碼:

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

    請注意,Assert 類別會提供數種靜態方法,可讓您用來驗證測試方法的結果。

  2. 選擇 [測試] 功能表上的 [執行],然後選擇 [全部執行]。

    測試專案隨即建置並執行。[測試總管] 視窗隨即開啟,而且測試會在 [通過的測試] 底下列出。視窗底部的 [摘要] 窗格會提供有關所選取測試的其他詳細資料。

    測試總管

將 DLL 專案新增至方案

  1. 在 [方案總管] 中選擇方案名稱。從捷徑功能表中選擇 [新增],然後選擇 [加入新的專案]。

    建立 RooterLib 專案

  2. 在 [加入新的專案] 對話方塊中,選擇 [DLL (Windows 市集應用程式)]。

  3. 將下列程式碼新增至 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 評估工具方法。

  4. 將 ROOTERLIB_EXPORTS 符號新增至命令列。

    1. 在 [方案總管] 中選擇 [RooterLib] 專案,然後從捷徑功能表中選擇 [屬性]。

      加入前置處理器符號定義

    2. 在 [RooterLib 屬性頁] 對話方塊中,依序展開 [組態屬性] 和 [C++],然後選擇 [前置處理器]。

    3. 從 [前置處理器定義] 清單中選擇 [<編輯...>],然後在 [前置處理器定義] 對話方塊中新增 ROOTERLIB_EXPORTS。

  5. 新增所宣告函式的最小實作。開啟 RooterLib.cpp 並新增下列程式碼:

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

將測試專案與 dll 專案結合

  1. 將 RooterLib 新增至 RooterLibTests 專案。

    1. 在 [方案總管] 中選擇 [RooterLibTests] 專案,然後在捷徑功能表上選擇 [參照]。

    2. 在 [RooterLib 專案屬性] 對話方塊中,展開 [一般內容],選擇 [架構和參考]。

    3. 選擇 [加入新參考]。

    4. 在 [加入參考] 對話方塊中,展開 [方案],然後選擇 [專案]。然後選取 [RouterLib] 項目。

  2. unittest1.cpp 中包含 RooterLib 標頭檔。

    1. 開啟 unittest1.cpp

    2. 將這個程式碼新增至 #include "CppUnitTest.h" 這一行下方:

      #include "..\RooterLib\RooterLib.h"
      
  3. 新增使用所匯入函式的測試。將下列程式碼新增至 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. 建置方案。

    新測試會出現在 [測試總管] 的 [未執行的測試] 節點中。

  5. 在 [測試總管] 中選擇 [全部執行]。

    成功的基本測試

您已設定測試和程式碼專案,並且確認可以執行在程式碼專案中執行函式的測試。現在您可以開始撰寫實際的測試和程式碼。

反覆擴大測試範圍並讓測試成功

  1. 加入新的測試:

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

    提示

    建議您不要變更成功的測試。而是加入新的測試,更新程式碼讓測試成功,然後新增另一個測試,依此類推。

    當使用者的需求變更時,停用不再正確的測試。撰寫新測試,並以相同的遞增方式一次讓一項測試成功。

  2. 在 [測試總管] 中選擇 [全部執行]。

  3. 測試失敗。

    RangeTest 失敗

    提示

    確認每一項測試在您撰寫之後立即失敗。這樣有助於避免犯下撰寫永遠不會失敗的測試這種簡單的錯誤。

  4. 透過測試強化程式碼,讓新的測試都成功。新增下列內容至 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. 建置方案,然後在 [測試總管] 中選擇 [全部執行]。

    兩項測試都會成功。

提示

藉由一次新增一項測試的方式開發程式碼。確定在每次反覆運算後,所有測試都成功。

對失敗的測試進行偵錯

  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());
         }
       }
    };
    
  2. 在 [測試總管] 中選擇 [全部執行]。

    測試失敗。在 [測試總管] 中選擇測試名稱。失敗的判斷提示會反白顯示。失敗的訊息會在 [測試總管] 的詳細資料窗格中顯示。

    NegativeRangeTests 失敗

  3. 若要查看測試失敗的原因,請逐步執行函式:

    1. 在 SquareRoot 函式的開頭設定中斷點。

    2. 在失敗的測試之捷徑功能表上,選擇 [偵測選取的測試]。

      執行到中斷點停止時,請逐步執行程式碼。

    3. 將程式碼新增至 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");
          }
      ...
      
    1. 在 [測試總管] 中,選擇 [全部執行] 測試修正過的方法,並確定並未導入回復 (Regression)。

現在所有測試都會成功。

所有測試都成功

重構程式碼但不變更測試

  1. 簡化 SquareRoot 函式的主要計算:

    // old code
    //result = result - (result*result - v)/(2*result);
    // new code
    result = (result + v/result) / 2.0;
    
  2. 選擇 [全部執行] 測試重構的方法,並確定並未導入回復 (Regression)。

    提示

    一組穩定而良好的單元測試,可確認您並未在變更程式碼時引入錯誤。

    將重構與其他變更分開處理。