對市集應用程式的 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 都有為單元測試提供額外的功能。
如需詳細資訊,請參閱 MSDN Library 中的使用單元測試驗證程式碼。 |
本主題說明如何使用單元測試做為開發工作的第一步。採用這種方式時,您會先撰寫測試方法,用來驗證要測試之系統中的特定行為,然後撰寫通過測試的程式碼。依照下列程序的順序進行變更,您就可以反轉策略,先撰寫要測試的程式碼,再撰寫單元測試。
本主題還會建立單一 Visual Studio 方案,以及用於單元測試和要測試之 DLL 的個別專案。您也可以直接在 DLL 專案中包含單元測試,或是針對單元測試和 .DLL 建立個別方案。如需要使用之結構的提示,請參閱使用測試總管針對現有 C++ 應用程式執行單元測試。
本主題內容
本主題會帶領您執行下列工作:
建立方案和單元測試專案
驗證測試總管中執行的測試
將 DLL 專案新增至方案
將測試專案與 dll 專案結合
反覆擴大測試範圍並讓測試成功
對失敗的測試進行偵錯
重構程式碼但不變更測試
建立方案和單元測試專案
選擇 [檔案] 功能表上的 [新增],然後選擇 [新專案]。
在 [新增專案] 對話方塊上,展開 [已安裝的],然後展開 [Visual C++],並選擇 [Windows 市集]。接著從專案範本清單中選擇 [單元測試程式庫 (Windows 市集應用程式)]。
將專案命名為 RooterLibTests,指定位置,將方案命名為 RooterLib,並確認已核取 [為方案建立目錄] 核取方塊。
在新專案中,開啟 unittest1.cpp。
請注意:
每一項測試都是使用 TEST_METHOD(YourTestName){...} 定義。
您不必撰寫傳統的函式簽章。簽章是由巨集 TEST_METHOD 所建立。巨集會產生傳回 void 的執行個體函式。另外還會產生靜態函式,傳回關於測試方法的資訊。這項資訊可以讓測試總管尋找方法。
測試方法是使用 TEST_CLASS(YourClassName){...} 分組到不同類別。
在測試執行時,會建立每個測試類別的執行個體。測試方法會依照非特定順序呼叫。您可以定義在每個模組、類別或方法之前和之後叫用的特殊方法。如需詳細資訊,請參閱 MSDN Library 中的使用 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 專案結合
將 RooterLib 新增至 RooterLibTests 專案。
在 [方案總管] 中選擇 [RooterLibTests] 專案,然後在捷徑功能表上選擇 [參照]。
在 [RooterLib 專案屬性] 對話方塊中,展開 [一般內容],選擇 [架構和參考]。
選擇 [加入新參考]。
在 [加入參考] 對話方塊中,展開 [方案],然後選擇 [專案]。然後選取 [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); } };
提示
建議您不要變更成功的測試。而是加入新的測試,更新程式碼讓測試成功,然後新增另一個測試,依此類推。
當使用者的需求變更時,停用不再正確的測試。撰寫新測試,並以相同的遞增方式一次讓一項測試成功。
在 [測試總管] 中選擇 [全部執行]。
測試失敗。
提示
確認每一項測試在您撰寫之後立即失敗。這樣有助於避免犯下撰寫永遠不會失敗的測試這種簡單的錯誤。
透過測試強化程式碼,讓新的測試都成功。新增下列內容至 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; }
建置方案,然後在 [測試總管] 中選擇 [全部執行]。
兩項測試都會成功。
提示
藉由一次新增一項測試的方式開發程式碼。確定在每次反覆運算後,所有測試都成功。
對失敗的測試進行偵錯
新增另一項測試至 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"); } ...
在 [測試總管] 中,選擇 [全部執行] 測試修正過的方法,並確定並未導入回復 (Regression)。
現在所有測試都會成功。
重構程式碼但不變更測試
簡化 SquareRoot 函式的主要計算:
// old code //result = result - (result*result - v)/(2*result); // new code result = (result + v/result) / 2.0;
選擇 [全部執行] 測試重構的方法,並確定並未導入回復 (Regression)。
提示
一組穩定而良好的單元測試,可確認您並未在變更程式碼時引入錯誤。
將重構與其他變更分開處理。