天天看點

ceph存儲 C、C++白盒測試打樁小結

什麼是樁

       樁,或稱樁代碼,是指用來代替關聯代碼或者未實作代碼的代碼。如果函數B用B1來代替,那麼,B稱為原函數,B1稱為樁函數。打樁就是編寫或生成樁代碼。

       打樁的目的

       打樁的目的主要有:隔離、補齊、控制。

       隔離是指将測試任務從産品項目中分離出來,使之能夠獨立編譯、連結,并獨立運作。隔離的基本方法就是打樁,将測試任務之外的,并且與測試任務相關的代碼,用樁來代替,進而實作分離測試任務。例如函數A調用了函數B,函數B又調用了函數C和D,如果函數B用樁來代替,函數A就可以完全割斷與函數C和D的關系。

       補齊是指用樁來代替未實作的代碼,例如,函數A調用了函數B,而函數B由其他程式員編寫,且未實作,那麼,可以用樁來代替函數B,使函數A能夠運作并測試。補齊在并行開發中很常用。

       控制是指在測試時,人為設定相關代碼的行為,使之符合測試需求。例如:

externint B();

int A()

{

      int ret = B();

      if(ret == 0)

           ;//do something

    elseif(ret == 1)

           ;//do something

    else

           ;//do something

      return ret;

}

        如果函數B傳回随機數,或者傳回網絡狀态,或者傳回環境溫度,等等,則當調用其實際代碼時,函數A很難測試,這時可以用樁函數B1來代替B,使其傳回測試所需要的資料。

        一個樁函數,可能既具有控制功能,又具有隔離或補齊功能。

        編寫樁

        一般來說,樁函數要具有與原函數完全一緻的原形,僅僅是實作不同,這樣測試代碼才能正确連結到樁函數。

        用于實作隔離和補齊的樁函數一般比較簡單,隻需把原函數的聲明拷過來,加一個空的實作,能通過編譯連結就行了。

       比較複雜的是實作控制功能的樁函數,要根據測試的需要,輸出合适的資料,下面是一個示例:

//擷取環境溫度。溫度由出參pTemperature輸出,傳回值表示擷取溫度是否成功,如果成功,則傳回1,否則傳回0。

int GetTemperature(int* pTemperature)

{

      if(caseNameIs("failed"))

           return 0;

      if(caseNameIs("ok-23"))

      {

           *pTemperature = 23;

           return 1;

      }

      if(caseNameIs("ok-25"))

      {

           *pTemperature = 25;

           return 1;

      }

      if(caseNameIs("ok-28"))

      {

           *pTemperature = 28;

           return 1;

      }

      return 0;

}

       其中,caseNameIs()是由測試工具提供的API,用于判斷用例的名稱。代碼根據用例名稱來決定輸出資料。

自然輸入:自然輸入調用實際代碼,不需要特别解決,跟樁無關。

          不可控:不可控調用的也是實際代碼,并不調用樁代碼,是以也不能解決。另外編寫樁代碼來代替實際代碼行不行?在應該調用實際代碼的時候,要想調用樁代碼可能很麻煩,例如,底層函數位于同一個檔案,或同一個類,通常要用編譯條件來區分實際代碼和樁代碼,不但麻煩,而且污染産品代碼。

難于初始化:也是調用實際代碼。

          靜态輸入:靜态輸入隻涉及到局部靜态變量,沒有調用底層函數,當然也不能用樁來代替。

         中斷輸入:中斷輸入是在不确定位置,中斷調用不确定的代碼形成的,也不能用樁來代替。

         失真:失真是打樁造成的,調用的是樁代碼。在比較簡單的情形下,可以用命名法來控制樁代碼的輸出,即給每個用例命名,樁代碼中判斷用例名來決定輸出,具體方法在前文已經介紹過。如果在同一個用例中,多次調用同一個樁,每次要求輸出不同,命名法就無效了。這種情形是很常見的,例如一個函數多次調用同一個底層函數,或在循環中調用樁代碼。一個被測函數可能調用多個樁,一個樁又可能被多個被測函數調用,這種多對多的關系下,很難維護用例與樁的對應。用例可能很多,還可能要不斷增加和修改,維護用例與樁輸出的關系也很麻煩。

ceph存儲 C、C++白盒測試打樁小結

       總之,在實際的應用中,在測試的時間成本受限的情形下,編寫樁代碼一般隻能解決部分失真,難以适應複雜的應用。

        一、采用自底向上的開發政策,先開發和測試底層代碼,當測試上層代碼時,假設底層代碼是正确的,直接調用底層代碼,進而減少打樁。

        二、在隔離測試任務時将源檔案分為三類:被測檔案、外圍檔案、隔離檔案。被測檔案是指測試任務内的源檔案;外圍檔案是指不測試或由其他人測試,但與被測檔案關系密切的源檔案;其他檔案為隔離檔案。外圍檔案與被測檔案一起進行編譯連結,測試時調用實際代碼,進而減少打樁。

三、使用自動化工具。編寫以隔離和補齊為目的的樁,是一種簡單重複的工作,由工具生成最為合适。至于控制目的的樁代碼,工具無法自動生成,但是,工具可以提供更為先進的方式,如底層模拟。底層模拟可以讓樁輸出像參數一樣,在用例中設定,進而大幅減少單元測試的時間成本。

ceph存儲 C、C++白盒測試打樁小結

Stub API 源碼位址: https://github.com/coolxv/cpp-stub

說明: 

- 隻适用linux,和windows的x86、x64架構 

- access private function相關方法基于C++11(參考:https://github.com/martong/access_private) 

- replace function相關方法基于C++03 

- windows和linux的用法會稍微不同,原因是擷取不同類型函數位址的方法不同,且調用約定有時不一樣

不可以打樁的情況: 

- 不可以對exit函數打樁,編譯器做了特殊優化 

- 不可以對純虛函數打樁,純虛函數沒有位址 

- static聲明的普通内部函數不能打樁,内部函數位址不可見(解析ELF或許可以獲得函數位址)

普通函數打樁(非static)

//for linux and windows
#include<iostream>
#include "stub.h"
using namespace std;
int foo(int a)
{   
    cout<<"I am foo"<<endl;
    return ;
}
int foo_stub(int a)
{   
    cout<<"I am foo_stub"<<endl;
    return ;
}


int main()
{
    Stub stub;
    stub.set(foo, foo_stub);
    foo();
    return ;
}

           

執行個體成員函數打樁

//for linux,__cdecl
#include<iostream>
#include "stub.h"
using namespace std;
class A{
    int i;
public:
    int foo(int a){
        cout<<"I am A_foo"<<endl;
        return ;
    }
};

int foo_stub(void* obj, int a)
{   
    A* o= (A*)obj;
    cout<<"I am foo_stub"<<endl;
    return ;
}


int main()
{
    Stub stub;
    stub.set(ADDR(A,foo), foo_stub);
    A a;
    a.foo();
    return ;
}

           
//for windows,__thiscall
#include<iostream>
#include "stub.h"
using namespace std;
class A{
    int i;
public:
    int foo(int a){
        cout<<"I am A_foo"<<endl;
        return ;
    }
};


class B{
public:
    int foo_stub(int a){
        cout<<"I am foo_stub"<<endl;
        return ;
    }
};

int main()
{
    Stub stub;
    stub.set(ADDR(A,foo), ADDR(B,foo_stub));
    A a;
    a.foo();
    return ;
}

           

靜态成員函數打樁

//for linux and windows
#include<iostream>
#include "stub.h"
using namespace std;
class A{
    int i;
public:
    static int foo(int a){
        cout<<"I am A_foo"<<endl;
        return ;
    }
};

int foo_stub(int a)
{   
    cout<<"I am foo_stub"<<endl;
    return ;
}


int main()
{
    Stub stub;
    stub.set(ADDR(A,foo), foo_stub);

    A::foo();
    return ;
}

           

模闆函數打樁

//for linux,__cdecl
#include<iostream>
#include "stub.h"
using namespace std;
class A{
public:
   template<typename T>
   int foo(T a)
   {   
        cout<<"I am A_foo"<<endl;
        return ;
   }
};

int foo_stub(void* obj, int x)
{   
    A* o= (A*)obj;
    cout<<"I am foo_stub"<<endl;
    return ;
}


int main()
{
    Stub stub;
    stub.set((int(A::*)(int))ADDR(A,foo), foo_stub);
    A a;
    a.foo();
    return ;
}

           
//for windows,__thiscall
#include<iostream>
#include "stub.h"
using namespace std;
class A{
public:
   template<typename T>
   int foo(T a)
   {   
        cout<<"I am A_foo"<<endl;
        return ;
   }
};


class B {
public:
    int foo_stub(int a) {
        cout << "I am foo_stub" << endl;
        return ;
    }
};


int main()
{
    Stub stub;
    stub.set((int(A::*)(int))ADDR(A,foo), ADDR(B, foo_stub));
    A a;
    a.foo();
    return ;
}
           

重載函數打樁

//for linux,__cdecl
#include<iostream>
#include "stub.h"
using namespace std;
class A{
    int i;
public:
    int foo(int a){
        cout<<"I am A_foo_int"<<endl;
        return ;
    }
    int foo(double a){
        cout<<"I am A_foo-double"<<endl;
        return ;
    }
};

int foo_stub_int(void* obj,int a)
{   
    A* o= (A*)obj;
    cout<<"I am foo_stub_int"<< a << endl;
    return ;
}
int foo_stub_double(void* obj,double a)
{   
    A* o= (A*)obj;
    cout<<"I am foo_stub_double"<< a << endl;
    return ;
}

int main()
{
    Stub stub;
    stub.set((int(A::*)(int))ADDR(A,foo), foo_stub_int);
    stub.set((int(A::*)(double))ADDR(A,foo), foo_stub_double);
    A a;
    a.foo();
    a.foo();
    return ;
}

           
//for windows,__thiscall
#include<iostream>
#include "stub.h"
using namespace std;
class A{
    int i;
public:
    int foo(int a){
        cout<<"I am A_foo_int"<<endl;
        return ;
    }
    int foo(double a){
        cout<<"I am A_foo-double"<<endl;
        return ;
    }
};
class B{
    int i;
public:
    int foo_stub_int(int a)
    {
        cout << "I am foo_stub_int" << a << endl;
        return ;
    }
    int foo_stub_double(double a)
    {
        cout << "I am foo_stub_double" << a << endl;
        return ;
    }
};
int main()
{
    Stub stub;
    stub.set((int(A::*)(int))ADDR(A,foo), ADDR(B, foo_stub_int));
    stub.set((int(A::*)(double))ADDR(A,foo), ADDR(B, foo_stub_double));
    A a;
    a.foo();
    a.foo();
    return ;
}
           

虛函數打樁

//for linux
#include<iostream>
#include "stub.h"
using namespace std;
class A{
public:
    virtual int foo(int a){
        cout<<"I am A_foo"<<endl;
        return ;
    }
};

int foo_stub(void* obj,int a)
{   
    A* o= (A*)obj;
    cout<<"I am foo_stub"<<endl;
    return ;
}


int main()
{
    typedef int (*fptr)(A*,int);
    fptr A_foo = (fptr)(&A::foo);   //擷取虛函數位址
    Stub stub;
    stub.set(A_foo, foo_stub);
    A a;
    a.foo();
    return ;
}

           
//for windows x86(32位)
#include<iostream>
#include "stub.h"
using namespace std;
class A {
public:
    virtual int foo(int a) {
        cout << "I am A_foo" << endl;
        return ;
    }
};

class B {
public:
    int foo_stub(int a)
    {
        cout << "I am foo_stub" << endl;
        return ;
    }
};



int main()
{
    unsigned long addr;
    _asm {mov eax, A::foo}
    _asm {mov addr, eax}
    Stub stub;
    stub.set(addr, ADDR(B, foo_stub));
    A a;
    a.foo();
    return ;
}
           

内聯函數打樁

//for linux
//添加-fno-inline編譯選項,禁止内聯,能擷取到函數位址,打樁參考上面。
           
//for windows
//添加/Ob0禁用内聯展開。
           

第三方庫私有成員函數打樁

//for linux
//被測代碼添加-fno-access-private編譯選項,禁用通路權限控制,成員函數都為公有的
//無源碼的動态庫或靜态庫無法自己編譯,需要特殊技巧擷取函數位址
#include<iostream>
#include "stub.h"
using namespace std;
class A{
    int a;
    int foo(int x){
        cout<<"I am A_foo "<< a << endl;
        return ;
    }
    static int b;
    static int bar(int x){
        cout<<"I am A_bar "<< b << endl;
        return ;
    }
};


ACCESS_PRIVATE_FIELD(A, int, a);
ACCESS_PRIVATE_FUN(A, int(int), foo);
ACCESS_PRIVATE_STATIC_FIELD(A, int, b);
ACCESS_PRIVATE_STATIC_FUN(A, int(int), bar);

int foo_stub(void* obj, int x)
{   
    A* o= (A*)obj;
    cout<<"I am foo_stub"<<endl;
    return ;
}
int bar_stub(int x)
{   
    cout<<"I am bar_stub"<<endl;
    return ;
}
int main()
{
    A a;

    auto &A_a = access_private_field::Aa(a);
    auto &A_b = access_private_static_field::A::Ab();
    A_a = ;
    A_b = ;

    call_private_fun::Afoo(a,);
    call_private_static_fun::A::Abar();

    auto A_foo= get_private_fun::Afoo();
    auto A_bar = get_private_static_fun::A::Abar();

    Stub stub;
    stub.set(A_foo, foo_stub);
    stub.set(A_bar, bar_stub);

    call_private_fun::Afoo(a,);
    call_private_static_fun::A::Abar();
    return ;
}
           
for windows,__thiscall
#include<iostream>
#include "stub.h"
using namespace std;
class A{
    int a;
    int foo(int x){
        cout<<"I am A_foo "<< a << endl;
        return ;
    }
    static int b;
    static int bar(int x){
        cout<<"I am A_bar "<< b << endl;
        return ;
    }
};


ACCESS_PRIVATE_FIELD(A, int, a);
ACCESS_PRIVATE_FUN(A, int(int), foo);
ACCESS_PRIVATE_STATIC_FIELD(A, int, b);
ACCESS_PRIVATE_STATIC_FUN(A, int(int), bar);
class B {
public:
    int foo_stub(int x)
    {
        cout << "I am foo_stub" << endl;
        return ;
    }
};
int bar_stub(int x)
{   
    cout<<"I am bar_stub"<<endl;
    return ;
}


int main()
{
    A a;

    auto &A_a = access_private_field::Aa(a);
    auto &A_b = access_private_static_field::A::Ab();
    A_a = ;
    A_b = ;

    call_private_fun::Afoo(a,);
    call_private_static_fun::A::Abar();

    auto A_foo= get_private_fun::Afoo();
    auto A_bar = get_private_static_fun::A::Abar();

    Stub stub;
    stub.set(A_foo, ADDR(B,foo_stub));
    stub.set(A_bar, bar_stub);

    call_private_fun::Afoo(a,);
    call_private_static_fun::A::Abar();
    return ;
}