顯示具有 TDD 標籤的文章。 顯示所有文章
顯示具有 TDD 標籤的文章。 顯示所有文章

C++單元測試(13) - OpenCppCoverage指令快速參考

沒有留言:
Command-line 參考

翻譯自
原文最後編譯時間 9月7日的上午04時23分 by OpenCppCoverage, version 15

You can display a quick reference with -h or --help as command line argument:
指令加上 -h 或 --help 參數,可以取得顯示一份快速參考


用法: [options] -- program_to_run optional_arguments:
Command line only:
  -v [ --verbose ]      詳細模式.
  -q [ --quiet ]        安靜模式.
  -h [ --help ]         顯示說明訊息.
  --config_file arg     Filename of a configuration file.

Command line and configuration file:
  --modules arg (=*)        添加要加入的模組路徑。可以多筆。
  --excluded_modules arg    添加要排除的模組路徑。可以多筆。
  --sources arg (=*)        添加要加入的程式碼路徑。可以多筆。
  --excluded_sources arg    添加要排除的模組路徑。可以多筆。
  --input_coverage arg      加上一個export_type=binary的路徑,
                            指定的檔案會合併到目前的覆蓋率輸出檔。可以多筆。
  --export_type arg (=html) 格式: <產出檔案類型>:<產出檔案路徑>.
                            <產出檔案類型> 可以是二進制檔、cobertura, html
                            <產出檔案路徑> (非必要) 存放產出檔案的資料夾路徑。
                            例如: html:MyFolder\MySubFolder 
                            可以多筆。
  --working_dir arg         程式的工作目錄
  --cover_children          Enable code coverage for children processes.
  --no_aggregate_by_file    Do not aggregate coverage for same file path.

C++單元測試(12) - fake, stub, mock object

沒有留言:

書裡怎麼寫

《C++ API 設計》,Ch 10.4.2中有介紹

假物件(fake object)

一個具有功能性行為的物件,但採用更簡單的實作以幫助測試。
例如:在記憶體中的檔案系統,模擬與本機磁碟上的互動

存根物件(stub object)

一個物件返迴預先準備或罐頭的回應。
例如:一個ReadFileAsString()存根可能只是返回一個硬編碼的字串作為檔案內容,而不是讀取磁碟上該檔案名的檔案內容

模仿物件(mock object)

被檢測的物件具有預先程式化的行為,執行一系列方法的呼叫以做驗證。
例如:一個模仿物件(或只是模擬)可指定GetValue()函數被呼叫時前兩次返回10,之後為20。
例如:它也可以驗證函式被呼叫了只有3次或至少5次,或在類別中的函式以一特定的順序被呼叫。

自己覺得

其實,能執行測試就足夠了。XD
依書裡的定義,再簡化其詮釋方式,我自己的介紹如下

  • 假物件(fake object): 簡化物件,以記憶體為存放資料的地方。
  • 存根物件(stub object): hard code return value物件,以常數為資料。
  • 模仿物件(mock object): 程式化行為物件,以一些邏輯決定取出資料。

其實,一個物件的各種function也都可以,也都不一定是哪一種物件。不過,我自己的實作也沒有很確切的去定這是什麼物件。也許在這一點妥協,不好,但是我還沒遇到吃虧的地方,總之,先讓測試跑起來吧!

C++單元測試(11) - OpenCppCoverage可視化單元測試的覆蓋率, Jenkins顯示Report

沒有留言:
這一次,我們來討論一下,如何將unit test的成果可視化,也就是常見的覆蓋率(Coverage rate)。

我們採用的環境如下
Visual Studio + git + CppUnit + Jenkins + OpenCppCoverage

這樣一來,在Visual Studio寫好的code提交到git後,Jenkins會輪詢git,若改版就抓一版過來;li 呼叫MSBuild編譯,編譯好就編譯CppUnit,然後執行CppUnit編好的執行檔,產生單元測試的Report,之後再執行cppcheck,產生Report,最後執行這一次的主角OpenCppCoverage,產生Report。

條列式的呈現,如下

  1. Visual Studio寫好的code提交到git後
  2. Jenkins會輪詢git,若改版就抓一版過來
  3. 呼叫MSBuild編譯主程式+編譯CppUnit
  4. 執行CppUnit編好的執行檔,產生單元測試的Report
  5. 執行cppcheck,產生Report
  6. 執行OpenCppCoverage,產生Report。

第六步,是我們今天討論的內容。

下載軟體

官方網站下載OpenCppCoverage。
安裝到Jenkins的主機上(可以先安裝在自己的電腦先測試,執行成功再裝在Jenkins的主機上)

準備Command

參考官網的文件,上面的訊息就足夠了。
自己本機測試,可以使用這樣的command
OpenCppCoverage --sources <souce code的路徑> -- <你的單元測試程式.exe> 
可以參考這篇,Jenkins上,則要這樣寫(加上--export_type=cobertura)
OpenCppCoverage --sources <souce code的路徑> --export_type=cobertura -- <你的單元測試程式.exe>

安裝Jenkins外掛

安裝Cobertura plugin

可能會遇到的問題

為什麼報表都沒有值?(Xml是空的?)

因為,需要.pdb檔。
建議使用debug模式編譯,因為除了.pdb檔本來就應該在debug模式產生之外,最好不要將code最佳化,造成無法辨別.pdb內容的情況。

所以,我在Jenkins上的設定,分別設定兩個job,一個是release編譯,一個是debug編譯。等debug編好,就執行unit test、coverage、static code analysis...,通通成功了,再編譯release。

實例

為了方便瀏覽,在此有用斷行,真正使用的指令,無需斷行,但是要加空白。
OpenCppCoverage --sources <產品程式碼路徑>
--excluded_sources <前置編譯路徑>
--excluded_sources <前置編譯.lib的.h檔 路徑>
--excluded_sources <單元測試路徑>
--excluded_sources <使用.dll, .lib檔的.h檔 路徑>
--excluded_sources <其它編譯完, 編譯器幫你用到的檔案路徑>
-- <單元測試達行檔>

C++單元測試(10) - Mock、Stub蓋台的目錄設定

沒有留言:
在撰寫unit test時,有時會需要撰寫假物件。
有了假物件就不想參考真物件。

但是如果參考目錄底下了置了真物件的.h檔,在unit test專案中,又設定了另外的Mock、Stub目錄放置假物件。
要如何才可以強制unit test只參考到假物件,而不是真物件呢?

(參考到不拿來測試的真物件,就是麻煩呀~XD)

在此,提供了一個技巧。(無意間發現的)
在C++中,往往會避免重複宣告,會有#ifndef...#endif的寫在在.h檔上,把class的宣告給夾起來。

那麼,在專案檔的參考目錄設定,就先把Mock、Stub移上面,就可以讓Visual Studio先參考Mock、Stub裡的物件囉。

(這真是一條險路呀~XD)

目前測試使用Visual Studio 2005是成功的。

C++單元測試(9) - CppUnit輸出Xml, Jenkins顯示Report

沒有留言:
這次的標題,下得很「關鍵字」
這次的故事是這樣的,使用CppUnit單元測試框架,使用Jenkins的CI系統+xUnitTest的plug-in。

要讓CppUnit輸出Xml,再讓xUnitTest的plug-in吃到Xml,在Jenkins上顯示。

第一步,就是先看單元測試專案的main怎麼改[1]

int main()
{
    CppUnit::TestResult testresult;
    CppUnit::TestResultCollector collectedresults;
    testresult.addListener (&collectedresults);

    CppUnit::TextUi::TestRunner runner;
    CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
    runner.addTest( registry.makeTest() );
    runner.run(testresult);

    std::ofstream xmlFileOut("CppUnitTestDmServerResults.xml");
    CppUnit::XmlOutputter xmlOut(&collectedresults, xmlFileOut);
    xmlOut.write();

    return 0;
}

主要就是要讓 testresult 放進 runner.run(testresult) 參數中。
這樣Xml輸出就會有東西了。

接下來就是設定Jenkins。

Jenkins加上xUnitTest的plug-in很多文章都有寫。就不多說了。
進入你Job的設定裡
要注意的如下

  1. 建置時要建置單元測試專案
  2. 建置後要執行單元測試的執行檔。(輸出xml)
  3. xUnitTest的plug-in要吃的,也只是xml檔。

建置時要建置單元測試專案

在Visual Studio上,就是要設定.sln檔,在某種Config時,是不是會建置呢?(要把單元測試的專案打勾唷)

建置後要執行單元測試的執行檔。

在Jos的建置,加上一個「執行Windows批次指令」,執行單元測試執行檔。
(當然如果你是Linux系統,就要加一個「執行Shell」)

xUnitTest的plug-in要吃的,也只是xml檔。

在Jos的建置後動作,加上Publish xUnit test result report
在裡面再加上一個CppUnit-1.12.1 (default)
其中Pattern的欄位,填入單元測試專案的xml路徑檔名

剩下的就照說明填囉~


參考資料: [1] Using Hudson for C++/CMake/CppUnit - Posted by volkerkaiser

C++單元測試(8) - 再用VS2005先試一下: 專案檔轉換失敗

沒有留言:
終於又再度繼續了!
接續上...一篇,我們試著使用了VC6和VS2010編譯之後,我們這次用了VS2005編編看。
依之前的經驗,只要將手上的VS升級到最新的版本就可以編譯,但是會寫文章就是「事情沒這麼順利」啦~

這一次,我們從頭來一次

1. 下載 source code

下載CppUnit
我是用clone下來的,所以...資料夾沒有版號。
直接就是cppunit出現在我的硬碟中。(也不用解壓縮唷)

2. 開啟專案檔

打開cppunit\srcCppUnitLibraries.dsw進行專案檔的轉換
VS2005開啟之後,會提醒「要轉換專案檔」格式與目前的VS版本不合。

提醒「要轉換專案檔」格式與目前的VS版本不合
 按下Yes後,出現了第二個對話框,警告你是否要替代已存在的檔案。

警告你是否要替代已存在的檔案
結果竟然給我出現錯誤!
程式設計師不怕警告,只怕錯誤呀!XD
我們來看看它寫什麼...(我把文字貼出來)


上圖內容如下
「The following error has occurred during XML parsing:

File: D:\cppUnitTest\cppunit\src\DllPlugInTester\DllPlugInTester.vcproj
Line: 8
Column: 2
Error Message:
'9.00' 違反 '7.00 7,00 7.10 7,10 8.00 8,00' 的 'enumeration' 條件。
屬性 'Version' 和與值 '9.00' 的剖析失敗。

The file 'D:\cppUnitTest\cppunit\src\DllPlugInTester\DllPlugInTester.vcproj' has failed to load.」
意思是,版本錯誤!

沒關係,我們相信工具的強大!
按下OK之後就會出現第二個專案檔給你再一次相同的問答。

最後,終於按完所有的Yes和OK之後,出現了....不能編譯的專案檔!?

什麼?!><
崩潰!( ▔皿▔) 這?說好的強大工具呢?

好!這篇不是要抱怨Visual Studio,所以就趕快回到正題吧!

修改專案檔

將cppunit/src底下所有的.vcproj找出來
總共有8個。

  1. cppunit.vcproj
  2. cppunit_dll.vcproj
  3. qttestrunner_dll.vcproj
  4. TestPlugInRunner.vcproj
  5. TestRunner.vcproj
  6. DllPlugInTester.vcproj
  7. DllPlugInTesterTest.vcproj
  8. qttestrunner.vcproj

全部都把它用文字編輯器打開。在此我用Sublime Text 2
然後,依照下列兩個原則[1]
  1. Version="9.00"改成Version="8.00" (數字大於8.00,才改成8.00)
  2. TargetFrameworkVersion="**" 刪除(不管數字是什麼!刪!)
這樣一來,你的專案檔開啟就跟新買的一樣滑順囉~(咳!別想歪!)

參考資料

 [1] VS2008工程转成VS2005工程的方法

C++單元測試(7) - Game Programing Game 6 Ch1.7 貳部曲

沒有留言:
貳部曲!!這次準備要介紹:
  1. 如何測試函數的正確性
  2. 如何測試拋出正確的例外處理
假設,我們即將設計一個像這樣的類別
//model.h
typedef int model_type;
typedef int vertex_t;
typedef int tringle_t;
typedef int mesh_t;
typedef int material_t;
typedef int joint_t;


class model
{
public:
 void loadfile(const char* file_path);
 void render();
 void animate(float speed, bool loop = true);
 
 //我們利用一些實用函數來加載模型文件
 void parse_header_section(char* file_text);
 void parse_triangle_section(char* file_text);
 void parse_mesh_section(char* file_text);
 void parse_material_section(char* file_text);
 void parse_animation_section(char* file_text);
 void preare_joints();
 //從文件中加載紋理(支援bmp檔)
 void load_texture(const char* file_path);
 //讀取檔案資訊的函數
 double get_version() const
 { return m_version; }
 model_type get_type() const
 { return m_model_type; }
 const char* get_name() const
 { return m_name; }
 const char* get_author() const
 { return m_author; }
 //讀取資料函數
 size_t get_number_of_vertices() const
 { return m_number_of_vertices; }
 size_t get_number_of_triangles() const
 { return m_number_of_triangles; }
 size_t get_number_of_meshes() const
 { return m_number_of_meshes; }
 size_t get_number_of_materials() const
 { return m_number_of_materials; }
 size_t get_number_of_joints() const
 { return m_number_of_joints; }
private:
 double m_version;
 model_type m_model_type;
 char* m_name;
 char* m_author;
 unsigned short m_number_of_vertices;
 unsigned short m_number_of_triangles;
 unsigned short m_number_of_meshes;
 unsigned short m_number_of_materials;
 unsigned short m_number_of_joints;

 vertex_t* m_vertices;
 tringle_t* m_triangles;
 mesh_t* m_meshes;
 material_t* m_materials;
 joint_t* m_joints;
};

以下,將對一個parse_header_section()做單元測試。
它是一個public的函數,也是一般我們會做單元測試的範圍。
因為「在不更動public函數介面的情況之下,讓private的可讀性提昇」的情況之下重構,就可以在不變更unit test function的情況之下,知道你的程式碼有沒有改壞了。(所以,設計一個有好public function的class,是首要之事呀)

單元測試的內容,包含了正常的測試也包含了非正常情況的測試。
在此,我們都會對parse_header_section()做測試,不過兩種測試都是針對「可預期情況」做測試。

非正常條件的測試
/* 第一個測試例子是一個比較簡單的測試:
 向這個函數傳遞一個非法的參數,我們就假設去傳入一個空指標。
 我們預期的是,如果傳遞給函數的標記語言是非法的,那麼該函數會發出一個model_invalid_header的例外。

 因此,你的測試內容應該向函數parse_animation_section()傳入一個空指標,
 並使用CPPUNIT_ASSERT_THROW巨集,驗證這個函數會拋出model_invalid_header例外。
 */
 void TestInvlidHeaderNullValue()
    {
        model my_model;
  CPPUNIT_ASSERT_THROW(my_model.parse_animation_section(0), model_invalid_header);
    }
/*
 另一個測試是傳入一個有問題的標籤。
 */
 void TestInvalidHeaderIllFormatted()
 {
  model my_model;
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER"
   "<VERSION>1.1</VERSION>"
   "<TYPE>WorldLevel</TYPE>"
   "<NAME>Character Select Gallery</NAME>"
   "<AUTHOR>Blake Madden</AUTHOR>"),
   model_invalid_header);
  /*
  在這標籤測試中,在第一個HEADER的後面少了一個">",並且還缺少了一個結束標記</HEADER>
  */
 }
/* 第三個測試應該去測試這個情境:
 如果標記中缺少了版本標籤"<VERSION>",或缺少了類型標籤"<TYPE>",這個函數會有怎樣的執行結果呢?

 如果沒有找到這些資訊,這個函數就應該相對應的發出model_invalid_version例外和model_invalid_type例外。
 */
 void TestInvalidVersionSection()
 {
  model my_model;
  //版本標籤缺少內容
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION></VERSION>"
    "<TYPE>WorldLevel</TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_version);

  //缺少版本標籤
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<TYPE>WorldLevel</TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_version);
  //版本標籤出錯
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2"
    "<TYPE>WorldLevel</TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_version);
  /*
  在這標籤測試中,在第一個HEADER的後面少了一個">",並且還缺少了一個結束標記</HEADER>
  */
 }
/*
 我們還應該建立另一個類似的測試,驗證type類型。
 */
 void TestInvalidType()
 {
  model my_model;
  //版本標籤缺少內容
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<TYPE></TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_type);

  //缺少版本標籤
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_type);
  //版本標籤出錯
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<TYPE>WorldLevel"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_type);
 }
正常條件的測試
/* 用失敗的條件去測試固然重要,但是用可以正常工作的資料去檢查函數的正確性也是很重要的。
 對於條件測試,我們可以使用CPPUNIT_ASSERT巨集。
 這個和C++標準的assert()幾乎一模一樣。

 在此要叫函數parse_header_section,然後再來驗證其版本訊息和類型訊息是否正確。
 */
 void TestInvalidValidHeader()
 {
  model my_model;
  my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<TYPE>WorldLevel</TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>");
   model_invalid_header);

  CPPUNIT_ASSERT(my_model.get_version() == 1.2);
  CPPUNIT_ASSERT(my_model.get_type() == world_level);
  CPPUNIT_ASSERT(strcmp(my_model.get_name(), "Character Select Gallery") == 0);
  CPPUNIT_ASSERT(strcmp(my_model.get_author(), "Blake Madden") == 0);

  //沒有包含模型的作者和模型名稱
  my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<TYPE>WorldLevel</TYPE>"
   "</HEADER>");
   model_invalid_header);

  CPPUNIT_ASSERT(my_model.get_version() == 1.2);
  CPPUNIT_ASSERT(my_model.get_type() == world_level);
  CPPUNIT_ASSERT(strcmp(my_model.get_name(), "") == 0);
  CPPUNIT_ASSERT(strcmp(my_model.get_author(), "") == 0);
  /*
  我們傳入函數中的參數是完全合法的標記語言,然後再去確認相對應的版本、類型、名稱以及作者。
  我們可以知道哪些成功,至於哪些失敗,CppUnit會把它記錄在Log中。
  */
 }
最後,測試治具類別裡的code像這樣
#ifndef MODEL_CLASS_H
#define MODEL_CLASS_H

#include "CppunitLib.h"
#include "model.h"

class ModelTest : public CppUnit::TestFixture
{
public:
 /* 第一個測試例子是一個比較簡單的測試:
 向這個函數傳遞一個非法的參數,我們就假設去傳入一個空指標。
 我們預期的是,如果傳遞給函數的標記語言是非法的,那麼該函數會發出一個model_invalid_header的例外。

 因此,你的測試內容應該向函數parse_animation_section()傳入一個空指標,
 並使用CPPUNIT_ASSERT_THROW巨集,驗證這個函數會拋出model_invalid_header例外。
 */
 void TestInvlidHeaderNullValue()
    {
        model my_model;
  CPPUNIT_ASSERT_THROW(my_model.parse_animation_section(0), model_invalid_header);
    }

 /*
 另一個測試是傳入一個有問題的標籤。
 */
 void TestInvalidHeaderIllFormatted()
 {
  model my_model;
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER"
   "<VERSION>1.1</VERSION>"
   "<TYPE>WorldLevel</TYPE>"
   "<NAME>Character Select Gallery</NAME>"
   "<AUTHOR>Blake Madden</AUTHOR>"),
   model_invalid_header);
  /*
  在這標籤測試中,在第一個HEADER的後面少了一個">",並且還缺少了一個結束標記</HEADER>
  */
 }

 /* 第三個測試應該去測試這個情境:
 如果標記中缺少了版本標籤"<VERSION>",或缺少了類型標籤"<TYPE>",這個函數會有怎樣的執行結果呢?

 如果沒有找到這些資訊,這個函數就應該相對應的發出model_invalid_version例外和model_invalid_type例外。
 */
 void TestInvalidVersionSection()
 {
  model my_model;
  //版本標籤缺少內容
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION></VERSION>"
    "<TYPE>WorldLevel</TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_version);

  //缺少版本標籤
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<TYPE>WorldLevel</TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_version);
  //版本標籤出錯
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2"
    "<TYPE>WorldLevel</TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_version);
  /*
  在這標籤測試中,在第一個HEADER的後面少了一個">",並且還缺少了一個結束標記</HEADER>
  */
 }

 /*
 我們還應該建立另一個類似的測試,驗證type類型。
 */
 void TestInvalidType()
 {
  model my_model;
  //版本標籤缺少內容
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<TYPE></TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_type);

  //缺少版本標籤
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_type);
  //版本標籤出錯
  CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<TYPE>WorldLevel"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>"),
   model_invalid_type);
 }

 /* 用失敗的條件去測試固然重要,但是用可以正常工作的資料去檢查函數的正確性也是很重要的。
 對於條件測試,我們可以使用CPPUNIT_ASSERT巨集。
 這個和C++標準的assert()幾乎一模一樣。

 在此要叫函數parse_header_section,然後再來驗證其版本訊息和類型訊息是否正確。
 */
 void TestInvalidValidHeader()
 {
  model my_model;
  my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<TYPE>WorldLevel</TYPE>"
    "<NAME>Character Select Gallery</NAME>"
    "<AUTHOR>Blake Madden</AUTHOR>"
   "</HEADER>");
   model_invalid_header);

  CPPUNIT_ASSERT(my_model.get_version() == 1.2);
  CPPUNIT_ASSERT(my_model.get_type() == world_level);
  CPPUNIT_ASSERT(strcmp(my_model.get_name(), "Character Select Gallery") == 0);
  CPPUNIT_ASSERT(strcmp(my_model.get_author(), "Blake Madden") == 0);

  //沒有包含模型的作者和模型名稱
  my_model.parse_header_section(
   "<HEADER>"
    "<VERSION>1.2</VERSION>"
    "<TYPE>WorldLevel</TYPE>"
   "</HEADER>");
   model_invalid_header);

  CPPUNIT_ASSERT(my_model.get_version() == 1.2);
  CPPUNIT_ASSERT(my_model.get_type() == world_level);
  CPPUNIT_ASSERT(strcmp(my_model.get_name(), "") == 0);
  CPPUNIT_ASSERT(strcmp(my_model.get_author(), "") == 0);
  /*
  我們傳入函數中的參數是完全合法的標記語言,然後再去確認相對應的版本、類型、名稱以及作者。
  我們可以知道哪些成功,至於哪些失敗,CppUnit會把它記錄在Log中。
  */
 }
public:
    CPPUNIT_TEST_SUITE(ModelTest);
  CPPUNIT_TEST(TestInvlidHeaderNullValue);
  CPPUNIT_TEST(TestInvalidHeaderIllFormatted);
  CPPUNIT_TEST(TestInvalidVersionSection);
  CPPUNIT_TEST(TestInvalidType);
  CPPUNIT_TEST(TestInvalidValidHeader);
    CPPUNIT_TEST_SUITE_END();
};

#endif

C++單元測試(6) - Game Programing Game 6 Ch1.7 首部曲

沒有留言:
我一直覺得CppUnit的文件太少了,其實,也許是自己太弱了,所以才要這麼多的文件來教自己用這個厲害的工具。

試過了用VC6編譯cppunitlib用VS2010編譯cppunitlib,我還有另外去試了VS2005,結果也是超順利的編譯完成vs2005會遇到專案檔轉換出錯的問題,排除後就沒有特別要注意的地方了。(所以就沒有另外寫一篇介紹2005的了)
秘訣:更新該版本的VS到最新版
然後小心專案檔的設定。(參考上述兩篇)

還記得,我們在本系列第一篇中有提到到CppUnit的wiki有看到Refreance的段落吧?其實,有一個梗...不是啦!有一篇文章千千萬萬不可以錯過。那就是CppUnit1.13.2的作者在《Game Programming Gems 6》的Ch1.7寫了一篇CppUnit的教學。算是比CppUnit CookBook介紹得還要多一點點的教學文章。

所以,有興趣的朋友可以去下載來看看。

在這裡,我有依照這本書的1.7章做了練習,放在github上。
上面寫了一樣的code(還修改了一個小錯誤),也寫上了註解,很多的註解。
有興趣的朋友可以來這裡下載回去看看。

不過因為它是TDD,所以沒有待測物的class,只有完成了測試程式的部份。不過待測物很好寫啦!可以自己寫一寫....就可以跑囉!


《Game Programming Gems 6》的Ch1.7的內容,在這個部落格會分成三篇介紹。但是最重要的,還是要下載程式碼回去看唷!git就可以可以讓你一步一步的練習呢!

第一部曲

(34fe339~55c32ea)

建立治具類別

#ifndef FIXTURE_CLASS_H
#define FIXTURE_CLASS_H
#include "CppunitLib.h"
/*
在Cppunit中,我們要先建立治具類別。這可以從CppUnit::TestFixture衍生取得。
測試治具類別,就是一個容器類別,用來管理那些用於測試特定函數或類別的所有測試案例。
一個空的測試治具類別大致如下:
*/
class MathTest : public CppUnit::TestFixture
{
public:
    CPPUNIT_TEST_SUITE(MathTest);   //MathTest是TestFixture的衍生Class
    CPPUNIT_TEST_SUITE_END();
};
#endif

在治具類別上面放上待測物,再加上測試的功能內容

#ifndef FIXTURE_CLASS_H
#define FIXTURE_CLASS_H
#include "CppunitLib.h"
/*
在Cppunit中,我們要先建立治具類別。這可以從CppUnit::TestFixture衍生取得。
測試治具類別,就是一個容器類別,用來管理那些用於測試特定函數或類別的所有測試案例。
一個空的測試治具類別大致如下:
*/
class MathTest : public CppUnit::TestFixture
{
    /*
    在其中CPPUNIT_TEST_SUITE的巨集說明中,我們要說明這個測試治具類別執行的所有測試案例。
    例如,假設這個測試治具別要測試一個math類別,
    而你又需要一個特殊測試案例,來測試這個math類取的add函數。
    你可以像下面這段程式碼一樣,建立一個測試用的函數,來完成上述的實作。
    */
public:
    void TestAddFunction()
    {
        math my_math;
        CPPUNIT_ASSERT(my_math.Add(2, 2) == 4);
    }
    /*
    在這個測試案例中,我們呼叫math.add(2, 2),利用CppUnit提供的CPPUNIT_ASSRT巨集來驗證add呼叫的結果是否等於4。
    如果add函數沒有正確的返回結果4,CppUnit會在日誌文件中將這個錯誤記錄下來,並根據你所設置的報告輸出方式
    為你顯示這個錯誤訊息。

    在完成這個測試案例的實作之後,你要用CPPUNIT_TEST_SUITE巨集,將這個測試案例
    添加到測試治具類別中。
    */
public:
    CPPUNIT_TEST_SUITE(MathTest);   //MathTest是TestFixture的衍生Class
    CPPUNIT_TEST(TestAddFunction);  //TestAddFunction是待測的Function
    CPPUNIT_TEST_SUITE_END();
};
#endif

在測試主程式中寫下你要輸出的格式是什麼,並且執行

#include "CppunitLib.h"
#include "FixtureClass.h"
#include <iostream>

/*
既然你已經完成了撰寫測試案例的工作,並將它們添加到一個測試治具類別中進行管理,
現在你就需要建立一個測試執行模組(test harness),它會實際的執行這個測試治具類別(像工廠的機台),
並輸出最後執行結果。

我們可以這樣做:
1. 建立一個CppUnit::TextTestRunner物件,
2. 添加測試治具類別到CppUnit::TextTestRunner物件
3. 設定輸果的輸出格式
4. 執行CppUnit::TextTestRunner物件。

以這個例子來說,你可以將輸出結果重新定到一個xml文件中。
這個xml文件使用了一個樣式表來顯示輸出結果,更方便於瀏覽。
*/

int main()
{
 //第一步,就是建立測試的執行物件,一個CppUnit::TextTestRunner物件
 CppUnit::TextTestRunner runner;
 
 //接下來,建立output stream,指向我們保存輸出結果的xml檔。
 std::ofstream ofs("tests.xml");

 //要建立這個xml檔的handle(是一個CppUnit::XmlOutputter物件)
 //設定它的樣式表
 //設定runner的執行結果由hanle處理
 CppUnit::XmlOutputter *xml =
  new CppUnit::XmlOutputter(&runner.result(), ofs);
 xml->setStyleSheet("report.xsl");
 runner.setOutputter(xml);

 //最後,將所有你要執行的測試治具別(MathTest)加到這個runner物件
 runner.addTest(MathTest::suite());
 runner.run();

 std::cout << "end unti test" << std::endl;
 getchar();

 return 0;
}

C++單元測試(5) - 再用VS2010先試一下: 如何使用CppUnit

沒有留言:
終於繼續了!
接續上一篇,我們試著使用了VC6編譯之後,我們這次用了cppUnit官方建議VS版本第二個 - VS2010。

如同VC6那篇一樣,先介紹成功案例的整個專案的規劃與設定吧!

專案檔與檔案之間的設定

一樣UnitTest是獨立在開發專案外的另一個專案。
(此專案是由空專案開始,加上.cpp和.h)

程式碼

接下來的程式碼與前一篇相同,在此就不綴述。
(一模一樣唷!)

設定專案檔屬性

最後,設定專案檔,如何使用CppUnit的檔案。
因為與VC6的介面大不同,而且從VS2003(有這一版嗎?)之後幾乎就是長這樣(只有些許的不同),在此講一下,適用滿多版本的。

在UnitTest專案檔上按右鍵→屬性
出現專案屬性設定頁面,在此可以點選上一層視窗的專案檔,設定值會跟著載入唷!
所以,看見這個頁面時請確認你的專案檔是否有點在UnitTest的專案上。

.h檔路徑

先設定要#include 的.h檔路徑
在「其他 Include 目錄」中填上.h檔路徑

lib檔的路徑

再設定link時期要找得到的lib檔的路徑
在「其它程式庫目錄」中填入.lib的路徑

lib檔的檔案名稱

再設定link時期要找得到的lib檔的檔案名稱
在此要注意,這一版的VS要用分號隔開唷!VS2005好像就是用空白隔開...
(不確定的話,可以靠tool設定,它會自動幫你加)
在「其他相依性」中填入.lib檔的檔名(這個就不知道翻譯的在幹什麼吃的了)

執行

設定UnitTest為Active Project就可以「以Unit Test為主」
在IDE按下編譯→執行,就可以看見Unit Test的執行結果報告了!^^


可能會遇到的問題

接下來,我們來聊一下一些途中可能會遇到的問題。
這也是為什麼這次拖這麼久才貼這一篇,是有原因的!

如果只是灌好VS2010直接編譯,編譯出這三個項目,會....算是滿順利的了。
cppunit.lib
cppunitd.lib
cppunit_dll.lib

頂多出現warning C4251
不過,官方有說這個不用理它也可以。(實驗之後發現,真的可以耶!)

error LNK1123
再之後就會開始卡關,出現問題。
LINK : fatal error LNK1123: 轉換成 COFF 時失敗: 檔案無效或損毀
這個問題,查了一下
http://stackoverflow.com/questions/10888391/error-link-fatal-error-lnk1123-failure-during-conversion-to-coff-file-inval

發現,建議灌sp1!
vs2010還有sp1唷?微軟是不是什麼都要來個sp1呀?那以後微軟的東西都不用太早買了。(本來就...)
之後就一路順利的編譯到底!


另外,關於這個部份,竟然還有教學影片耶!有興趣的朋友,就跟著它一步一步做吧!(我還是建議升級到sp1唷)

VS2005 msvcprtd.lib(MSVCP80D.dll) : error LNK2005

沒有留言:
編譯完出現下面的訊息

1>msvcprtd.lib(MSVCP80D.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::~basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(void)" (??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ) 已在 cppUnitMain.obj 中定義過了

1>msvcprtd.lib(MSVCP80D.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@ABV01@@Z) 已在 cppUnitMain.obj 中定義過了

1>msvcprtd.lib(MSVCP80D.dll) : error LNK2005: "public: char const * __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::c_str(void)const " (?c_str@?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QBEPBDXZ) 已在 cppUnitMain.obj 中定義過了

1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: __thiscall type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) 已在 LIBCMT.lib(typinfo.obj) 中定義過了

1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) 已在 LIBCMT.lib(typinfo.obj) 中定義過了
1>MSVCRTD.lib(MSVCR80D.dll) : error LNK2005: "public: virtual __thiscall std::exception::~exception(void)" (??1exception@std@@UAE@XZ) 已在 LIBCMT.lib(stdexcpt.obj) 中定義過了

1>MSVCRTD.lib(MSVCR80D.dll) : error LNK2005: "public: __thiscall std::exception::exception(class std::exception const &)" (??0exception@std@@QAE@ABV01@@Z) 已在 LIBCMT.lib(stdexcpt.obj) 中定義過了
1>MSVCRTD.lib(MSVCR80D.dll) : error LNK2005: "public: __thiscall std::exception::exception(void)" (??0exception@std@@QAE@XZ) 已在 LIBCMT.lib(stdexcpt.obj) 中定義過了

1>LINK : warning LNK4098: 預設的程式庫 'MSVCRTD' 與其他使用的程式庫衝突,請使用 /NODEFAULTLIB:library


[1] 只要把編譯的RTTI打開就可以解決了!
[2] vs2005 專案檔設定>C/C++>Code Generation>RunTime Library,選Muti-threaded Debug DLL(參考)

參考資料:
[1] How to resolve linking error?
[2] VS2005中運行時庫不一致導致項目編譯出問題

C++單元測試(4) - 用VC6先試一下: 如何使用CppUnit

沒有留言:
接續前篇,因為暫時還不想換tool寫文章!^^
就繼續VC6的使用說明。

在這裡,我們為了要先測試一下編出來的檔案對不對。
就暫時先以CppUnit CookBook中範例的最終版本當sample code吧!
為了方便之後在VS2010上做測試,就先把VC6可以跑的code貼在這。

由於UnitTest是獨立在開發專案外的另一個專案。
所以,會在下面的圖中,看見兩個專案檔,這是正常,未來上手之後也都是這樣安排的。
(不然就不叫unit test了,就用條件編譯就好啦!)

專案檔與檔案之間的設定

先看專案檔與檔案之間的設定
(此專案檔都是Win32 Console Application)

程式碼

接下來就是貼source code
先複製到各個檔案上面。
main
#include <iostream>
#include "Complex.h"

using namespace std;

int main()
{
    Complex a(1, 3);
    Complex b(1, 3);
    Complex c = a + b;

    cout << c.real << endl;
    return 0;
}
Complex.h
#ifndef COMPLEX_H
#define COMPLEX_H

class Complex
{ 
    friend bool operator==(const Complex& a, const Complex& b);
    friend Complex& operator+(const Complex &a, const Complex &b);
    friend Complex& operator/(const Complex& a, const Complex& b);
public:  //just for test
    double real, imaginary;
public:
    Complex( double r, double i = 0 ): real(r), imaginary(i) { }
};

bool operator==( const Complex &a, const Complex &b )
{ return (a.real == b.real)&&(a.imaginary == b.imaginary); }
Complex& operator+(const Complex &a, const Complex &b )
{ return *(new Complex(a.real + b.real, a.imaginary + b.imaginary)); }
Complex& operator/(const Complex &a, const Complex &b )
{ return *(new Complex(a.real / b.real, a.imaginary / b.imaginary)); }

#endif
cppUnitMain.cpp
#include "cppUnitLib.h"
#include "ComplexNumberTest.h"
#include <iostream>

int main()
{
    CppUnit::TextUi::TestRunner runner;
    CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
    runner.addTest( registry.makeTest() );
    runner.run();

    return 0;
}
ComplexNumberTest.h
#ifndef ComplexNumberTest_H
#define ComplexNumberTest_H
#include "cppUnitLib.h"
#include "../Complex.h"

class ComplexNumberTest : public CppUnit::TestFixture
{
private:
    Complex *m_10_1, *m_1_1, *m_11_2;
public:
    void setUp()
    {
        m_10_1 = new Complex(10, 1);
        m_1_1  = new Complex( 1, 1);
        m_11_2 = new Complex(11, 2);
    }

    void tearDown()
    {
        delete m_10_1;
        delete m_1_1;
        delete m_11_2;
    }

    void testEquality()
    {
        CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
        CPPUNIT_ASSERT( *m_10_1 == *m_11_2 );
    }

    void testAddition()
    {
        CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
    }

    void testDivideByZeroThrows()
    {
        *m_10_1 / Complex(0);
    }
    CPPUNIT_TEST_SUITE( ComplexNumberTest );
    CPPUNIT_TEST( testEquality );
    CPPUNIT_TEST( testAddition );
    CPPUNIT_TEST_EXCEPTION( testDivideByZeroThrows, std::exception );
    CPPUNIT_TEST_SUITE_END();
};
CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumberTest );

#endif
cppUnitLib.h
#ifndef CPPUNIT_LIB
#define CPPUNIT_LIB
#include <cppunit/TestCase.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/XmlOutputter.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/TestResult.h>
#endif

設定專案檔屬性

最後,再來設定專案檔,如何使用CppUnit的檔案。

.h檔路徑

先設定要#include 的.h檔路徑
在Additional include directiories:中填上.h檔路徑

lib檔,檔案名稱及路徑

再設定link時期要找得到的lib檔,檔案名稱及路徑
在Object/library modules: 中填入.lib檔的檔名
在Additional library path: 中填入.lib檔放置的路徑

執行

設定UnitTest為Active Project就可以「以Unit Test為主」
在IDE按下編譯→執行,就可以看見Unit Test的執行結果報告了!^^


C++單元測試(3) - 測試VS2005的C++專案

沒有留言:
前一篇取得的聖杯。

接下來的困難點,卻是VS2005的專案檔設定。

這一次的環境是使用Windows 8.1, VS2005 SP2
CppUnit要修改專案檔

不過!要知道取出哪一個部份

第一步 取得正確的檔案

要知道我們在上一篇編譯結束後,出現的cppunit.lib 和 cppunit_dll.lib要如何正確使用。
除了擁有link時期需要的.lib檔之外,還要取得complier時期需要的.h檔(也就是include會用到的部份)。

.lib檔位於cppunit1.13.2/lib/裡面。
.h檔位於cppunit1.13.2/include/裡面。(通常會編出Debug和Release兩種)

不過,我沒有試過直接拿CppUnit的原始碼加到專案檔裡,這招應該是沒有問題的。
下次來試試看。

第二步,開啟你要做單元測試的專案

在此就是使用VS2005啦。
其實,難還是難在專案檔的設定,因為沒有任何書針對這部份詳細解說(入門書都簡單講一下,深入的書都不提這個區塊,MSDN....你懂的!)

在此,我們使用的專案名稱為「xCppUnit」
並且把cppUnit的檔案另外放在D:\sandbox\cppunitlib (不放在上圖的目錄中)
分別是
cppunitlib\include\cppunit\extensions
cppunitlib\include\cppunit
cppunitlib\include

在專案設定裡找到這個地方,並填上.h檔的路徑(記得用;隔開,設定不包含子目錄吧?)

在專案設定裡找到這個地方,並填上.lib檔的路徑(記得用;隔開)

第三步 貼上練習的code,Build it!!

接下來就完事了。
可以開始寫測試案例了貼上練習的測試案例測試看看囉![1]

只有一個main檔。沒有加上其它的code。貼上測試案例,加上足夠的include檔案。即可編譯成功。

這系列到目前,是我遇到網路上找不到資料的步驟,也許太簡單所以高手寫文章都直接跳過這些,對於我們這種新手要踏進來,對於太多不懂的。所以,就將自己研究的過程寫下來,並且仔細的告訴你,怎麼做,祝大家順利啦!^^。

另外,有一個延伸閱讀。

第一篇的時候,我們找到了維基百科對CppUnit的參考資料
上面有一個Further reading,這是一本教你寫遊戲的書唷!^^
Ch1.7有介紹CppUnit的使用方式,有興趣的人可以把它找出來。[2]


參考資料:
[1] CppUnit CookBook 中文版
[2] 游戏编程精粹6

C++單元測試(2) - 用VC6編譯CppUnit

沒有留言:
接續前一編
「事不疑遲,快到freedesktop下載最新版吧!」

這一版的CppUnit,自帶VC6和VS2010的兩個不同的VS專案檔,這一篇就先介紹使用VC6的路上要突破的重重挫折。

使用環境
Windows 7
Visual C++ 6
  1. 安裝Visual C++ 6,git for windows
  2. clone CppUnit,並且切換到cppunit-1-13的分支。
    master的PlugInManager.cpp檔Line:3會出現「找不到stdint.h」的error
    而這個分支,直接砍掉出問題的這一行.....
    $git clone git://anongit.freedesktop.org/git/libreoffice/cppunit/
    %(git dir)$git checkout cppunit-1-13
  3. 再打開 src/CppUnitLibraries.dsw
  4. 編譯 每一個 專案
    編譯條件有四種,通通都要編一下Win32 Release, Win32 Debug, Win32 Release Unicode, Win32 Debug Unicode 對每一個專案檔進行各別編譯 Build(selection only)
    順序如下試一下就知道了。
    這一步會出現一些error,得慢慢的一個一個解。(下面有一些可以參考的經驗)
  5. 在lib/可以找到編譯好的檔案
    我編出來有23個檔案。

編譯出現error

  • cdxCDynamicDialog.cpp(30) : error C2440 這是timer的參數型別出問題。
    只要把
    void cdxCDynamicDialog::OnTimer(UINT_PTR idEvent)
    改成
    void cdxCDynamicDialog::OnTimer(UINT idEvent)
  • TreeHierarchyDlg.cpp(106) : error C2065
    m_treeTests.SetItemData(...) 參數型別出問題。(改法類似上一點)
  • TreeHierarchyDlg.cpp(188) : error C2065
    Text::data; 型別出問題(改法類似上一點)
這樣就已經取得聖杯了。
剩下的就是怎麼使用它了!下次我們再來介紹吧!

C++單元測試(1) - 下載CppUnit

沒有留言:

找到CppUnit - C++ port of JUnit - SourceForge,還有維基百科條目介紹
雖然前者是Google第一個,照理來說,就是它了,但是如果是他,我也不用特地發文介紹這一段。

先看看SourceForge

如果直接按下載,可以發現

問題一

它下載下來的壓縮檔,裡面的檔案都加了副檔名.v(ex: abc.cpp.v, abc.h.v),如果你想要手動去除的話,實在不建議,因為裡面的檔案超多的。

問題二

Summy的Last Update是2013-04-22
在Files裡的1.12.1版卻是2008-02-20


再來看看維基百科的介紹

有提到 在freedesktop.org 維護的版本是由 Markus Mohrhard 在維護。

那....參考資料呢?



發現它有紀錄最新版是......1.13.2 release
還有這個維護者的部落格文章

提到對Visual Studio,以及Windows x64的支援,對我這種臣服於M$邪惡帝國的子民來說,真是一大福音呀。

事不疑遲,快到freedesktop下載最新版吧!

如果有git建議直接用clone的
git clone git://anongit.freedesktop.org/git/libreoffice/cppunit/

參考資料:

[1] CppUnit - C++ port of JUnit
[2] CppUnit - Wikipedia, the free encyclopedia
[3] Cppunit 1.13.2 released

Visual Studio 2013 的Unit Test....

沒有留言:
發這一篇,就覺得自己又更上一層樓了。(其實是樓梯終於開始爬了而已...)

先前在找「怎麼樣寫,才叫做單元測試」,就找到了 TDD,從TDD又找到了In91的「30天快速上手TDD」[1],心想「有了這一篇+Visual Studio號稱是地球上最邪惡強大的IDE」!一定把TDD給練成~~哈哈。這種心情,就像是看見辟邪劍譜一樣。(不過,還好不是....辟邪劍譜)

開開心心的把VS2013打開,利用雙螢幕把網頁開在另一邊。
30天快速上手!前兩篇都在介紹,第三篇終於看見程式碼了!
  1. 在類別或方法內容中,按滑鼠右鍵,叫出選單。
就在這一步,我發現了真的要自宮?!!!怪怪的....VS的版本不對!這篇教學是使用VS2010。那我們就上網查吧!

查了又查。
不是叫我灌Unit Test General[2]
就是用Team Foundation Server....

一直到這一篇[3].....

才真正的開始30天快速上手的第一個範例!
在邪惡微軟帝國下的碼農們,從這裡和VS2013一起邁向偉大的航道吧!

OS:這是在演哪一齣的??


參考資料:
[1] 30天快速上手 TDD
[2] [Visual Studio] 透過Unit Test Generator快速為專案加入單元測試程式碼
[3] 快速入門:搭配測試總管進行以測試為導向的開發工作