單元測試期間的一些問題解決

來源:互聯網
上載者:User

    最近在做單元測試的工作,原來也曾經坐過這樣的工作,不過那時是自己寫的函數自己測,所以對函數的邏輯和資料結構很瞭解,但是現在是讀別人的代碼,然後做單元測試,按理說做單元測試的話我應該把精力和重點放在本模組的函數上,對被測函數內部遇到的其他模組的函數應該不需要瞭解其處理細節,只要為其打樁調用一下就好了,可誰知偌大一個公司,這單元測試竟都是各個模組的developer各自組織本模組的測試架構,竟然沒人知道怎麼給函數打樁,這讓我做起來相當的痛苦,但是面對問題還是要解決的,所以現在將自己遇到的問題解決過總結記錄一下吧。

    首先我知道曾經用過的gtest工具是開源的軟體,並且支援函數打樁,所以我先搜集相關資料:

   

gtest剖析(草稿)2010-12-24 00:57
++ G Test
版本:1.5.0
*1)  如何運行測試*

#define RUN_ALL_TESTS()\
    (::testing::UnitTest::GetInstance()->Run())
   ------------------------
    UnitTestImpl::RunAllTests()
     test_cases_.ForEach(TestCase::RunTestCase)
    TestCase::RunTestCase
     static void RunTestCase(TestCase * test_case) { test_case->Run(); }
    TestCase::Run()
     test_info_list_->ForEach(internal::TestInfoImpl::RunTest);
     static void RunTest(TestInfo * test_info) { test_info->impl()->Run();}
    TestInfoImpl::Run()
     test = factory_->CreateTest()
     test->Run();
    Test::Run()
     TestBody();
  ------------------------
1. UnitTest 單例,總管整個測試,包括測試環境資訊,當前執行狀態等等。

2. UnitTestImpl UnitTest內部具體功能的實現者。

3. Test    我們自己編寫的,或通過TEST,TEST_F等宏展開後的Test對象,管理著測試案例的前後事件,具體的執行代碼TestBody。

4. TestCase 測試案例對象,管理著基於TestCase的前後事件,管理內部多個TestInfo。

5. TestInfo  管理著測試案例的基本資料,包括Test對象的建立方法。

6. TestInfoImpl TestInfo內部具體功能的實現者 。

----------------

*2 )如何產生測試*
---------------
TEST(FooTest, Demo)
{
    EXPECT_EQ(1, 1);
}
---------------
展開
---------------
class FooTest_Demo_Test : public ::testing::Test
{
public:
    FooTest_Demo_Test() {}
private:
    virtual void TestBody();
    static ::testing::TestInfo* const test_info_;
    FooTest_Demo_Test(const FooTest_Demo_Test &);
    void operator=(const FooTest_Demo_Test &);
};

::testing::TestInfo* const FooTest_Demo_Test
    ::test_info_ =
        ::testing::internal::MakeAndRegisterTestInfo(
            "FooTest", "Demo", "", "",
            (::testing::internal::GetTestTypeId()),
            ::testing::Test::SetUpTestCase,
            ::testing::Test::TearDownTestCase,
            new ::testing::internal::TestFactoryImpl< FooTest_Demo_Test>);

void FooTest_Demo_Test::TestBody()
{
    switch (0)
    case 0:
        if (const ::testing::AssertionResult
                gtest_ar =
                    (::testing::internal:: EqHelper<(sizeof(::testing::internal::IsNullLiteralHelper(1)) == 1)>::Compare("1", "1", 1, 1)))
            ;
        else
            ::testing::internal::AssertHelper(
                ::testing::TPRT_NONFATAL_FAILURE,
                ".\\gtest_demo.cpp",
                9,
                gtest_ar.failure_message()
                ) = ::testing::Message();
}
展開後,我們觀察到:

1. TEST宏展開後,是一個繼承自testing::Test的類。

2. 我們在TEST宏裡面寫的測試代碼,其實是被放到了類的TestBody方法中。

3. 通過靜態變數test_info_,調用MakeAndRegisterTestInfo對測試案例進行註冊。

++ Test Info

我們看到,上面建立了一個TestInfo對象,然後通過AddTestInfo註冊了這個對象。TestInfo對象到底是一個什麼樣的東西呢?

TestInfo對象主要用於包含如下資訊:

1. 測試案例名稱(testcase name)

2. 測試名稱(test name)

3. 該案例是否需要執行

4. 執行案例時,用於建立Test對象的函數指標

5. 測試結果

我們還看到,TestInfo的建構函式中,非常重要的一個參數就是工廠對象,它主要負責在運行測試案例時建立出Test對象。我們看到我們上面的例子的factory為:

new ::testing::internal::TestFactoryImpl< FooTest_Demo_Test>
我們明白了,Test對象原來就是TEST宏展開後的那個類的對象(FooTest_Demo_Test),再看看TestFactoryImpl的實現:

template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {
public:
    virtual Test* CreateTest() { return new TestClass; }

};

++ Test Factory Impl

template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {
public:
    virtual Test* CreateTest() { return new TestClass; }
};

 ++ Make And Register Test Info

// 建立一個 TestInfo 對象並註冊到 Google Test;
// 返回建立的TestInfo對象
//
// 參數:
//
//   test_case_name:            測試案例的名稱
//   name:                           測試的名稱
//   test_case_comment:       測試案例的注釋資訊
//   comment:                      測試的注釋資訊
//   fixture_class_id:             test fixture類的ID
//   set_up_tc:                    事件函數SetUpTestCases的函數地址
//   tear_down_tc:               事件函數TearDownTestCases的函數地址
//   factory:                        工廠對象,用於建立測試對象(Test)
TestInfo* MakeAndRegisterTestInfo(
    const char* test_case_name, const char* name,
    const char* test_case_comment, const char* comment,
    TypeId fixture_class_id,
    SetUpTestCaseFunc set_up_tc,
    TearDownTestCaseFunc tear_down_tc,
    TestFactoryBase* factory) {
  TestInfo* const test_info =
      new TestInfo(test_case_name, name, test_case_comment, comment,
                   fixture_class_id, factory);
  GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
  return test_info;
}

--------------------------

*3)如何使用gtest*

++ How To Use Gtest

gtest使用簡介

最近測試過程中使用gtest,簡單易用,是一個非常不錯的單元測試架構,簡單介紹下使用方法,歡迎大家拍磚
一、安裝
官網:http://code.google.com/p/googletest/
1、下載最新版本:gtest-1.4.0.tar.gz
2、解壓後進入gtest-1.4.0
3、  運行./configure  make  make install(需要sudo許可權,也可以自己指定安裝的目錄)
4、  進入根目錄,編輯.bashrc,添加庫檔案路徑export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
(編譯測試程式時可以找到產生的gtest庫檔案)
 
二、調用
1、編寫測試程式時添加#include <gtest/gtest.h>
2、編譯測試程式時添加以下內容-ldl -lgtest -I/usr/local/include(載入gtest標頭檔)
 
代碼:

#include <iostream>

#include <cstdlib>

using namespace std;

#include <gtest/gtest.h>

int Fun(int val)

{

 return val;

}

//測試案例

TEST(Test, TestFun)

{

 //檢查函數的傳回值是否與給定的值相等

 EXPECT_EQ(10, Fun(10));

 EXPECT_EQ(9, Fun(10));

}

int main(int argc, char* argv[])

{

 //初始化

 testing::InitGoogleTest(&argc, argv);

 //運行所有的測試案例

 RUN_ALL_TESTS();

 system("pause");

 return 0;

}

宏與事件

 斷言宏可以理解分為兩類,一類是ASSERT,另外一類是EXPECT系列。當檢查點失敗的時候,ASSERT系列的宏會退出當前函數,EXPECT系列的斷言會繼續往下執行。

 Gtest中定義了多種宏來針對不同的需求,有針對布爾類型的宏,有針對數實值型別進行邏輯檢查的宏,針對字串的宏。針對異常檢查的宏,還有一些針對特殊情況下使用的宏。

宏的主要作用就是檢查所有測試的函數是否按照我們的預期進行了工作。

事件機制:使我們能在案例之前和之後做一些操作。事件一般分為三種類型:

1. 全域的,所有案例執行前後。

2. TestSuit層級的,在某一批案例中的第一個案例前,最後一個案例執行後。

3. TestCase層級的,每一個TestCase前後。

事件的實現

4. 全域事件 寫一個類,繼承test::Environment類,實現裡面的setup和teardown。Setup在所有案例執行前執行,teardown在所有案例執行後執行。

5. TestSuit 寫一個類繼承testing::Test,然後實現兩個靜態方法,setuptestcase,它在第一個testcase之前執行。Teardowntestcase在最後一個testcase之後執行。然後再編寫測試案例的時候,需要使用TEST_F宏,第一個參數使用,我們衍生類別的名字。

6. TestCase 寫一個類,繼承testing::Test,然後重寫方法setup和teardown。Setup方法在每個testcase之前執行,teardown方法在每個testcase之後執行。編寫測試案例的時候同樣需要使用TEST_F宏。
7.
四、程式部分測試結果
 
Gtest對每個失敗的case給出預期值與實際值的差別以及失敗case的名稱
 
五、gtest斷言
布爾值檢查
Fatal assertion
Nonfatal assertion
Verifies
ASSERT_TRUE(condition);
EXPECT_TRUE(condition);
conditionis true
ASSERT_FALSE(condition);
EXPECT_FALSE(condition);
conditionis false
數值型資料檢查

Fatal assertion
Nonfatal assertion
Verifies
ASSERT_EQ(expected, actual);
EXPECT_EQ(expected, actual);
expected == actual
ASSERT_NE(val1, val2);
EXPECT_NE(val1, val2);
val1 != val2
ASSERT_LT(val1, val2);
EXPECT_LT(val1, val2);
val1 < val2
ASSERT_LE(val1, val2);
EXPECT_LE(val1, val2);
val1 <= val2
ASSERT_GT(val1, val2);
EXPECT_GT(val1, val2);
val1 > val2
ASSERT_GE(val1, val2);
EXPECT_GE(val1, val2);
val1 >= val2
字串檢查

Fatal assertion
Nonfatal assertion
Verifies
ASSERT_STREQ(expected_str, actual_str);
EXPECT_STREQ(expected_str, actual_str);
the two C strings have the same content
ASSERT_STRNE(str1, str2);
EXPECT_STRNE(str1, str2);
the two C strings have different content
ASSERT_STRCASEEQ(expected_str, actual_str);
EXPECT_STRCASEEQ(expected_str, actual_str);
the two C strings have the same content, ignoring case
ASSERT_STRCASENE(str1, str2);
EXPECT_STRCASENE(str1, str2);
the two C strings have different content, ignoring case
     更多詳細資料請參看
官方文檔:http://code.google.com/p/googletest/wiki/GoogleTestPrimer
推薦文檔:http://www.cnblogs.com/coderzh/archive/2009/03/31/1426758.html

Step1. 編譯gtest-all.cc和gtest_main.cc檔案
Step1. 編譯gtest-all.cc和gtest_main.cc檔案

g++ -I${GTEST_DIR}/include -I${GTEST_DIR} -c ${GTEST_DIR}/src/gtest-all.cc

g++ -I${GTEST_DIR}/include -I${GTEST_DIR} -c ${GTEST_DIR}/src/gtest_main.cc

 Step2. 將step1產生的gtest-all.o和gtest_main.o打包成靜態庫libgtest.a

ar -rv libgtest.a gtest-all.o gtest_main.o

 Step3. 編譯要測試的代碼(假設檔案名稱為sample.cpp)

g++ -I${GTEST_DIR}/include -c sample.cpp

 Step4. 編譯單元測試的代碼(假設檔案名稱為test.cpp)

g++ -I${GTEST_DIR}/include -c test.cpp

 Step5. 與libgtest.a或其他需要的庫連結、產生可執行程式

g++ -I${GTEST_DIR}/include test.o sample.o libgtest.a -o test

 其他的庫,如pthread庫。

Where, GTEST_DIR=/usr/src/gtest-1.5.0

 編寫的makefile檔案如下。

簡單版本

all:

g++ -I/usr/src/gtest-1.5.0/include -I/usr/src/gtest-1.5.0 -g -c /usr/src/gtest-1.5.0/src/gtest-all.cc

g++ -I/usr/src/gtest-1.5.0/include -I/usr/src/gtest-1.5.0 -g -c /usr/src/gtest-1.5.0/src/gtest_main.cc

ar -rv libgtest.a gtest-all.o gtest_main.o

g++ -I/usr/src/gtest-1.5.0/include -g -c sample.cpp

g++ -I/usr/src/gtest-1.5.0/include -g -c test.cpp

g++ -I/usr/src/gtest-1.5.0/include -lpthread test.o sample.o libgtest.a -g -o test

clean:

rm test libgtest.a *.o

 實際上,其中將gtest-all.o和gtest_main.o壓縮為libgtest.a庫,可以省去,直接使用.o檔案,如下。

all:

g++ -I/usr/src/gtest-1.5.0/include -I/usr/src/gtest-1.5.0 -g -c /usr/src/gtest-1.5.0/src/gtest-all.cc

g++ -I/usr/src/gtest-1.5.0/include -I/usr/src/gtest-1.5.0 -g -c /usr/src/gtest-1.5.0/src/gtest_main.cc

g++ -I/usr/src/gtest-1.5.0/include -g -c sample.cpp

g++ -I/usr/src/gtest-1.5.0/include -g -c test.cpp

g++ -I/usr/src/gtest-1.5.0/include -lpthread test.o sample.o gtest-all.o gtest_main.o -g -o test

clean:

rm test *.o

正式版本

# Google Test directory

GTEST_DIR = /usr/src/gtest-1.5.0

 # Flags passed to the preprocessor.

CPPFLAGS += -I$(GTEST_DIR)/include

 # Flags passed to the C++ compiler.

CXXFLAGS += -g -Wall -Wextra

 # All Google Test headers.  Usually you shouldn't change this definition.

GTEST_HEADERS = $(GTEST_DIR)/include/gtest/*.h $(GTEST_DIR)/include/gtest/internal/*.h

 # All Google Test sources

GTEST_SRCS_ = $(GTEST_DIR)/src/*.cc $(GTEST_DIR)/src/*.h $(GTEST_HEADERS)

 # All tests produced by this Makefile.  Remember to add new tests you created to the list.

TESTS = test

 all : $(TESTS)

 clean :

rm -f $(TESTS) gtest.a gtest_main.a *.o

 gtest-all.o : $(GTEST_DIR)/src/gtest-all.cc          # $(GTEST_SRCS_)

$(CXX) $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c $(GTEST_DIR)/src/gtest-all.cc

 gtest_main.o : $(GTEST_DIR)/src/gtest_main.cc        # $(GTEST_SRCS_)

$(CXX) $(CPPFLAGS) -I$(GTEST_DIR) $(CXXFLAGS) -c $(GTEST_DIR)/src/gtest_main.cc

 gtest_main.a : gtest-all.o gtest_main.o

$(AR) $(ARFLAGS) $@ $^

 sample.o : sample.cpp sample.h $(GTEST_HEADERS)

$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c sample.cpp

 test.o : test.cpp sample.h $(GTEST_HEADERS)

$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c test.cpp

 test : sample.o test.o gtest_main.a

$(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@

$^代表依賴項,$@代表目標。

 Reference

Readme

Makefile of sample
 

本篇文章來源於:開發學院 http://edu.codepub.com   原文連結:http://edu.codepub.com/2011/0116/28837.php

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.