1. 程式人生 > >Google單元測試工具gtest和gmoke簡介

Google單元測試工具gtest和gmoke簡介

1、安裝gtest

$sudo apt-get install libgtest-dev

$cd /usr/src/gtest

$sudo cmake .

$sudo make

$sudo cp libgtest*.a /usr/local/lib

2、gtest使用例子

#include<iostream>
#include<gtest/gtest.h>

using namespace std;

struct LinkNode {
    int _data;
    LinkNode *_next;
    LinkNode(const int& data)
        :_data(data)
        ,_next(NULL){}
};

class Link
{
public:
    Link():pHead(new LinkNode(0)) {}

    void PushBack(const int& data)
    {
        if(pHead == NULL)
            return ;
        LinkNode *newNode=new LinkNode(data);
        if(pHead->_next == NULL){  //第一次插入結點
            pHead->_next=newNode;
        } else{  //找到最後一個結點直接尾插
            LinkNode *cur=pHead->_next;
            while(cur->_next)
                cur=cur->_next;
            cur->_next=newNode;
        }
    }

    void PopBack()
    {
        if(pHead == NULL)
            return ;
        LinkNode *cur=pHead;
        LinkNode *prev=NULL;
        while(cur->_next) {
            prev=cur;
            cur=cur->_next;
        }
        prev->_next=NULL;
        delete cur;
    }

    LinkNode *FindNode(const int& data)
    {
        if(pHead == NULL)
            return NULL;
        LinkNode *cur=pHead->_next;
        while(cur) {
            if(cur->_data == data)
                return cur;
            cur=cur->_next;
        }
        return NULL;
    }

    bool Delete(int data)
    {
        LinkNode *pos=FindNode(data);
        if(pos == NULL)
            return false;
        LinkNode *cur=pHead->_next;
        while(cur->_next != pos)
            cur=cur->_next;
        cur->_next=pos->_next;
        delete pos;
        return true;
    }

    void Destroy()
    {
        if(pHead == NULL)
            return;
        LinkNode *cur=pHead->_next;
        while(cur) {
            LinkNode *del=cur;
            cur=cur->_next;
            delete del;
            del=NULL;
        }
        delete pHead;  //刪除頭結點
    }
    LinkNode *pHead;
};

class TestLink: public testing::Test
{
public:
    virtual void SetUp()
    {
        cout<<"SetUp"<<endl;
        for(int i=1;i<=5;i++)
            link.PushBack(i);
    }

    virtual void TearDown()
    {
        cout<<"TearDown"<<endl;
        link.Destroy();
    }
    Link link;
};

TEST_F(TestLink,PushBack)
{
    ASSERT_FALSE(link.pHead == NULL);
    link.PushBack(9);
    LinkNode *res=link.FindNode(9);
    ASSERT_FALSE(res == NULL);
}

TEST_F(TestLink,PopBack)
{
    for(int i=1;i<=5;i++)
        link.PopBack();
}

TEST_F(TestLink,FindNode)
{
    ASSERT_TRUE(link.FindNode(3));
    ASSERT_TRUE(link.FindNode(2));
    ASSERT_TRUE(link.FindNode(4));
    ASSERT_TRUE(link.FindNode(5));
    ASSERT_TRUE(link.FindNode(1));
    ASSERT_FALSE(link.FindNode(7));
}

TEST_F(TestLink,Delete12123)
{
    ASSERT_FALSE(link.pHead == NULL);
    ASSERT_TRUE(link.Delete(3) == true);
    ASSERT_TRUE(link.Delete(9) == false);
}

TEST(Test, simple)
{
    ASSERT_EQ(1,1);
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc,argv);
    return RUN_ALL_TESTS(); //開始所有測試
}

編譯:
g++ test.cpp -lgtest -lpthread -o test

執行結果:


說明:

1. TEST(test_case_name, test_name) TEST_F(test_fixture, test_name)
TEST巨集的作用是建立一個簡單測試,它定義了一個測試函式,在這個函式裡可以使用任何C++程式碼並使用提供的斷言來進行檢查。

TEST_F與TEST的區別是,TEST_F提供了一個初始化函式(SetUp)和一個清理函式(TearDown),在TEST_F中使用的變數可以在初始化函式SetUp中初始化,在TearDown中銷燬,並且所有的TEST_F是互相獨立的,都是在初始化以後的狀態開始執行,一個TEST_F不會影響另一個TEST_F所使用的資料

2.  gtest中,斷言的巨集可以分為兩類,一類是ASSERT系列,一類是EXPECT系列。{ASSERT|EXPECT}_EQ(expected,actual): Tests that expected == actual{ASSERT|EXPECT}_NE(v1,v2):           Tests that v1 != v2{ASSERT|EXPECT}_LT(v1,v2):           Tests that v1 < v2{ASSERT|EXPECT}_LE(v1,v2):           Tests that v1 <= v2{ASSERT|EXPECT}_GT(v1,v2):           Tests that v1 > v2

{ASSERT|EXPECT}_GE(v1,v2):           Tests that v1 >= v2EXPECT_*和ASSERT_*的區別:(1)、EXPECT_*失敗時,案例繼續往下執行;(2)、ASSERT_*失敗時,直接在當前函式中返回,當前函式中ASSERT_*後面的語句將不會執行,退出當前函式,並非退出當前案例。斷言:布林值檢查、數值型資料檢查、字串檢查、顯示成功或失敗、異常檢查、Predicate Assertions、浮點型檢查、Windows HRESULT assertions、型別檢查。

3.  ::testing::InitGoogleTest(&argc,argv)gtest的測試案例允許接收一系列的命令列引數,將命令列引數傳遞給gtest,進行一些初始化操作。gtest的命令列引數非常豐富。命令列引數:(1)、--gtest_list_tests:使用這個引數時,將不會執行裡面的測試案例,而是輸出一個案例的列表;(2)、--gtest_filter:對執行的測試案例進行過濾,支援萬用字元;(3)、--gtest_also_run_disabled_tests:執行案例時,同時也執行被置為無效的測試案例;(4)、--gtest_repeat=[COUNT]:設定案例重複執行次數;(5)、--gtest_color=(yes|no|auto):輸出命令列時是否使用一些五顏六色的顏色,預設是auto;(6)、--gtest_print_time:輸出命令時是否列印每個測試案例的執行時間,預設是不列印的;(7)、--gtest_output=xml[:DIRECTORY_PATH\|:FILE_PATH:將測試結果輸出到一個xml中,如—gtest_output=xml:d:\foo.xml  指定輸出到d:\foo.xml ,如果不是指定了特定的檔案路徑,gtest每次輸出的報告不會覆蓋,而會以數字字尾的方式建立;(8)、--gtest_break_on_failure:除錯模式下,當案例失敗時停止,方便除錯;(9)、--gtest_throw_on_failure:當案例失敗時以C++異常的方式丟擲;(10)、--gtest_catch_exceptions:是否捕捉異常,gtest預設是不捕捉異常的,這個引數只在Windows下有效。

4.  gtest的事件一共有3種(1)、全域性的,所有案例執行前後;(2)、TestSuite級別的,在某一批案例中第一個案例前,最後一個案例執行後;(3)、TestCase級別的,每個TestCase前後。

全域性事件:要實現全域性事件,必須寫一個類,繼承testing::Environment類,實現裡面的SetUp和TearDown方法。SetUp方法在所有案例執行前執行;TearDown方法在所有案例執行後執行。
TestSuite事件:需要寫一個類,繼承testing::Test,然後實現兩個靜態方法:(1)、SetUpTestCase方法在第一個TestCase之前執行;(2)、TearDownTestCase方法在最後一個TestCase之後執行。
TestCase事件:是掛在每個案例執行前後的,需要實現的是SetUp方法和TearDown方法。(1)、SetUp方法在每個TestCase之前執行;(2)、TearDown方法在每個TestCase之後執行。
每個基於gtest的測試過程,是可以分為多個TestSuite級別,而每個TestSuite級別又可以分為多個TestCase級別。這樣分層的結構的好處,是可以針對不同的TestSuite級別或者TestCase級別設定不同的引數、事件機制等,並且可以與實際測試的各個模組層級相互對應,便於管理。

5.  可以通過操作符"<<"將一些自定義的資訊輸出
如在EXPECT_EQ(v1, v2)<< "thisis a error! "

6.  引數化
必須新增一個類,繼承testing::TestWithParam<T>,其中T就是你需要引數化的引數型別。

7.  編寫死亡測試案例
TEST的第一個引數,即test_case_name,請使用DeathTest字尾,原因是gtest會優先執行死亡測試案例,應該是為執行緒安全考慮。

8.  testing::AddGlobalTestEnvironment(newFooEnvironment)
在main函式中建立和註冊全域性環境物件。

3、安裝gmock

$sudo apt-get installgoogle-mock

$cd /usr/src/gmock

$sudo cmake .

$sudo make

$sudo cp lib*.a /usr/local/lib

4、使用gmock例子

Messenger.h

#ifndef SRC_MESSENGER_H_
#define SRC_MESSENGER_H_

#include <string>
using namespace std;

class Messenger
{
public:
    virtual ~Messenger() {}
    virtual string getMessage() = 0;
}; 
#endif /* SRC_MESSENGER_H_ */

MockMessenger.h

#ifndef SRC_MOCKMESSENGER_H_
#define SRC_MOCKMESSENGER_H_

#include "Messenger.h"
#include <string>
#include <gmock/gmock.h>
using namespace std;

class MockMessenger : public Messenger
{
public:
    MOCK_METHOD0(getMessage, string());
};

#endif /* SRC_MOCKMESSENGER_H_ */

HelloWorld.cpp

#include "HelloWorld.h"
#include "Messenger.h"

HelloWorld::HelloWorld()
{
}

HelloWorld::~HelloWorld()
{
}

string HelloWorld::getMessage(Messenger* messenger) const
{
    return messenger->getMessage();
}

HelloWorld.h

#ifndef SRC_HELLOWORLD_H_
#define SRC_HELLOWORLD_H_

#include <string>
#include "MockMessenger.h"
using namespace std;

class HelloWorld
{
public:
    HelloWorld();
    virtual ~HelloWorld();
    string getMessage(Messenger* messenger) const;
};

#endif /* SRC_HELLOWORLD_H_ */
HelloWorld_UnitTest.cpp
#include "HelloWorld.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "MockMessenger.h"
#include <string>
#include <memory>
using namespace testing;

TEST(HelloWorldTest, getMessage)
{
    MockMessenger messenger;
    std::string msg = "Hello World";
    EXPECT_CALL(messenger, getMessage()).WillRepeatedly(Return(ByRef(msg)));

    HelloWorld helloWorld;
    EXPECT_EQ("Hello World", helloWorld.getMessage(&messenger));
    EXPECT_EQ("Hello World", helloWorld.getMessage(&messenger));
    EXPECT_EQ("Hello World", helloWorld.getMessage(&messenger));
}

int main(int argc, char** argv)
{
    testing::InitGoogleMock(&argc, argv);
    return RUN_ALL_TESTS();
}

編譯:

g++ HelloWorld.cpp HelloWorld_UnitTest.cpp -lgtest -lpthread -lgmock -o test

執行結果:


說明:

Google Mock的作用是幫你快速地做出一個介面的仿製品。
如果你的設計依賴其它的類,而這些類還沒有完成或非常昂貴(如資料庫);

如果你要測試你的模組與其它模組是否能正確結合,並想了解其互動過程;那麼Google Mock就能幫助你。

例子中Messenger.h是一個沒有完成功能的介面,由MocMessenger.h模擬完成

幾個巨集的說明:
MOCK_METHOD#1(#2, #3(#4) )
#1表示你要mock的方法共有幾個引數
#2是你要mock的方法名稱
#3表示這個方法的返回值型別
#4是這個方法具體的引數
以上就已經完成了對介面類的mock,但打樁的工作還沒有全部完成,還需要設定某個成員函式執行時能按我們的期望來返回值,我們繼續往下看。
選擇一個要進行單元測試的檔案s_ls_dosomething.cpp,裡面只有一個函式:
再新建一個s_ls_dosomething_ut.cpp作為s_ls_dosomething.cpp這個檔案中函式單元測試檔案,內容如下:
之前在完成interface_mock.cpp的時候說打樁還沒有全部完成,剩下的工作就在這個TEST中了,就是通過ON_CALL/ EXPECT_CALL來設定樁函式的返回值,這2個巨集的具體說明如下:

ON_CALL(#1, #2(#3)).WillByDefault(Return(#4));
#1表示mock物件
#2表示想定義的那個方法名稱。
#3表示定義方法的引數,只有當傳入的引數=#3的值時,才會生效,也可用_,表示匹配任何引數輸入都生效
#4表示呼叫要返回值。
除了ON_CALL以外,還有EXPECT_CALL(#1, #2(#3)),引數含義同ON_CALL,還可以有額外的功能,比如:

EXPECT_CALL(#1, #2(#3)).Times(5).WillOnce(Return(100))
.WillOnce(Return(150))
.WillRepeatedly(Return(200));
表示第一次返回100,第二次呼叫返回150,後面全部返回200,以此類推。
ON_CALL和EXCEPT_CALL相當於設定樁函式的屬性設定,因此應當在樁函式呼叫前進行設定。