什麼是樁
樁,或稱樁代碼,是指用來代替關聯代碼或者未實作代碼的代碼。如果函數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,用于判斷用例的名稱。代碼根據用例名稱來決定輸出資料。
自然輸入:自然輸入調用實際代碼,不需要特别解決,跟樁無關。
不可控:不可控調用的也是實際代碼,并不調用樁代碼,是以也不能解決。另外編寫樁代碼來代替實際代碼行不行?在應該調用實際代碼的時候,要想調用樁代碼可能很麻煩,例如,底層函數位于同一個檔案,或同一個類,通常要用編譯條件來區分實際代碼和樁代碼,不但麻煩,而且污染産品代碼。
難于初始化:也是調用實際代碼。
靜态輸入:靜态輸入隻涉及到局部靜态變量,沒有調用底層函數,當然也不能用樁來代替。
中斷輸入:中斷輸入是在不确定位置,中斷調用不确定的代碼形成的,也不能用樁來代替。
失真:失真是打樁造成的,調用的是樁代碼。在比較簡單的情形下,可以用命名法來控制樁代碼的輸出,即給每個用例命名,樁代碼中判斷用例名來決定輸出,具體方法在前文已經介紹過。如果在同一個用例中,多次調用同一個樁,每次要求輸出不同,命名法就無效了。這種情形是很常見的,例如一個函數多次調用同一個底層函數,或在循環中調用樁代碼。一個被測函數可能調用多個樁,一個樁又可能被多個被測函數調用,這種多對多的關系下,很難維護用例與樁的對應。用例可能很多,還可能要不斷增加和修改,維護用例與樁輸出的關系也很麻煩。

總之,在實際的應用中,在測試的時間成本受限的情形下,編寫樁代碼一般隻能解決部分失真,難以适應複雜的應用。
一、采用自底向上的開發政策,先開發和測試底層代碼,當測試上層代碼時,假設底層代碼是正确的,直接調用底層代碼,進而減少打樁。
二、在隔離測試任務時将源檔案分為三類:被測檔案、外圍檔案、隔離檔案。被測檔案是指測試任務内的源檔案;外圍檔案是指不測試或由其他人測試,但與被測檔案關系密切的源檔案;其他檔案為隔離檔案。外圍檔案與被測檔案一起進行編譯連結,測試時調用實際代碼,進而減少打樁。
三、使用自動化工具。編寫以隔離和補齊為目的的樁,是一種簡單重複的工作,由工具生成最為合适。至于控制目的的樁代碼,工具無法自動生成,但是,工具可以提供更為先進的方式,如底層模拟。底層模拟可以讓樁輸出像參數一樣,在用例中設定,進而大幅減少單元測試的時間成本。
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 ;
}