關於gmock單元測試的使用小白知識,初使用gmock前推薦看

1. 什麼是單元測試?

單元測試,維基百科給出定義:Unit Testing,又稱爲模塊測試,是針對程序模塊(軟件設計的最小單元)進行正確性檢驗的測試工作。

 

2. 什麼是模塊?或者什麼是最小單元?

通俗的說就是函數或者類的方法。

“單元”的定義,其實可以更加寬泛,在面嚮對象語言中,一個單元可以指一個方法,也可以是一個類。單元的選定更多的取決於我們測試的意圖。

 

3. 爲什麼需要單元測試?

我們常說的單元測試,是開發者編寫的一小段代碼,用於檢驗被測代碼的一個很小的、很明確的功能是否正確。簡單來說,一個單元測試就是用於判斷某個特定條件(或者場景)下某個特定函數的行爲。

好處:開發人員可以通過這種方式,測試自己寫的函數是否符合預期,在這個過程中,往往可以發現函數內部邏輯錯誤,帶來優秀的代碼治理和良好的異常處理以及完善錯誤報告。

 

4. 怎麼寫單元測試?或者怎麼纔算好的單元測試?

有明確的預期;

可重複運行;

沒有副作用。

這裏說到的副作用,即業務邏輯從外部依賴中解耦處理,比如說:時間、隨機數、併發性、基礎設置、持久化、網絡等。這就需要我們對代碼做更合理的劃分,函數的職責更加的清晰。

但我們的往往有各種外部依賴,比如需要讀寫數據庫,需要進行網絡通信,需要操作設備等等,這就需要用到測試替身:Stub(樁)。樁的最大作用就是,不包含邏輯,返回固定數據。

 

5. 這裏主要是記錄一下C++的gmock的使用。

雖然gmock的入門看起來很簡單,但是將其應用到具體的工程中,初學者又總會面臨各種各樣的問題,主要原因還是對於gmock是如何運作的存在誤區,下面通過一個例子來看下,究竟gmock是如何替換某些函數方法的。

1)首先需要在C++工程中,設置附加包含目錄和附加庫目錄爲gtest和gmock的include和lib路徑,附加依賴項中添加:gmock.lib和gtest.lib。需要注意的是,所使用的gmock.lib和gtest.lib與你所創建的C++工程,最好是一樣的vs版本,即C++庫版本一樣,否則會出現一些意想不到的錯誤哦。

2)這裏有一個test類,先假定它的作用就是沒啥作用,就是給你看下:

正常來說,我們一般會這樣寫:

test.h:

class test{
public:
    test();
    ~test();
    int add(Dev* dev, int a);
};

假定Dev類是一個需要和設備進行交互的類,獲取設備上的數據:

dev.h:

class Dev{
public:
    Dev();
    ~Dev();
    int getDevNum();
};

其中add方法的實現,假設就是一個加法運算,但是需要從某個設備中讀取值上來與a進行計算:(假定我們這裏的數值都必須是正數。)

test.cpp:

int test:add(Dev* dev, int a){
    if(NULL == dev){
        return -1;
    }
    if(a < 0){
        return -2;
    }

    int sum = dev->getDevNum();//假定沒有設備,函數返回-3
    if(sum < 0){
        return -3;
    }

    sum += a;
    return sum;
}

我們需要對這個add方法做單元測試,但如果測試的環境沒有設備,這個函數不就沒法完全的測試了嗎?

這個時候就需要藉助gmock,來對getDevNum打樁,讓getDevNum可以返回我們想讓它返回的任何數

需要對getDevNum進行打樁,還需要對Dev類的實現做更改,這是因爲gmock的原理實際是依賴於C++的多態機制實現的,所以只有虛函數才能被mock,而非虛函數則無法mock。這一點也就要求我們在實現我們的類和方法時,要預先設計好,把這些依賴外部環境的實現分離開(不要與業務邏輯部分混合在一起,封裝到單獨的函數方法中),不然代碼寫一半再去使用gmock做單元測試,就往往需要對現有代碼結構做很大的更改,造成很大的時間和精力浪費。

Dev類更改如下:

class Dev{
public:
    Dev();
    virtual ~Dev();
    virtual int getDevNum();
};

將析構函數和getDevNum函數都設爲虛函數,這樣getDevNum函數就可以被mock了。

mock一下getDevNum:

首先我們在測試代碼中定義一個MockDev類:

#include “gmock\gmock.h”
#include “dev.h”

class MockDev:public Dev{
public:
    MOCK_METHOD0(getDevNum, int());
};

這樣我們就成功mock了getDevNum函數了。

MOCK_METHOD0:固定寫法,數字0表示mock的函數沒有參數,如果有2個參數,就是MOCK_METHOD2,對應的4個參數就是MOCK_METHOD4;

參數1:就是我們需要mock的函數方法名稱了;

參數2:是我們mock的函數指針類型,格式爲:返回值類型(參數1,參數2...)

如果我們的getDevNum有兩個形參:int getDevNum(int a, string b);那麼這裏的參數2格式爲:int(int a, string b)

 

接下來就是我們的單元測試代碼:

測試代碼include包含我們剛剛創建MockDev的頭文件,以及被測試代碼的cpp:

#include “gtest\gtest.h”
#include “gmock\gmock.h”
#include “MockDev.h”
#include “test.cpp”

using namespace testing;

TEST(TestSuiteTest, add){
    int ret;
    MockDev mdev;
    EXPECT_CALL(mdev, getDevNum()).WillRepeatedly(Return(1));

    test t;
    ret = t.add(3, NULL);
    EXPECT_EQ(4, -1);

    ret = t.add(-1, &mdev);
    EXPECT_EQ(-2, ret);

    ret = t.add(3, &mdev);
    EXPECT_EQ(4, ret);
}

這裏EXPECT_CALL,就是說當調用mdev的getDevNum方法的時候,返回1.

當然還有很多豐富的返回,比如第一次調用返回1,第二次調用返回2等等,其他使用方法可以參考gmock的語法。

 

簡單總結一下:

上面的例子雖然很簡單,但是可以看出,

1)將需要mock的函數方法定義爲虛函數;

2)我們需要在編寫代碼之初,將必要的接口分離,避免依賴外部環境的實現部分與業務邏輯部分混合。這是爲了單元測試的時候,我們可以將依賴外部環境的函數實現進行mock。

 

其他:

實際工程中,我們一個類中可能不僅僅是共有的函數方法,可能還有一些私有的方法,對於這些私有的函數方法,應不應該測試呢?這個目前還沒有統一的定論,需要看開發人員自己的意願,這裏給出私有方法測試的幾種方法:

1)使用#define private public粗暴地將private變成public,需要將define放在#include頭文件之前。如:

#define private public  

#include “myclass.h”

 

2)使用friend。這個會相對友好一點,但是卻需要修改原有的代碼

 

3)將private方法定義爲protected,然後在測試代碼中繼承,自己定義測試的共有方法,再調用父類中的private方法:

class Num{

protected:

    int add(int a, int b);

};

//測試代碼

class TestNum:public Num{

public:
    int testAdd(int a, int b);

};

int TestNum:testAdd(int a, int b){

    return add(a, b);

}

缺點也很明顯,還是需要更改一下原代碼。

本文爲作者原創,如需轉載,請在評論區徵得作者同意,原創鏈接:https://blog.csdn.net/anranjingsi/article/details/106084223

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章