最近,在給公司的一些模塊添加單元測試,金主們要求項目中分支的覆蓋率達到80%,經過一段時間的工作,添加了很多的測試用例,但是分支覆蓋率還是不理想,主要原因主要是:
- 單元中涉及好多的分支都是對一些系統調用不用返回值的處理,比如下面的幾個例子.我們知道這些系統調用正常來說很少會,也比較困難出現異常,在寫test cases時就比較困難;
int ready = epoll_wait(...);
if(-1 == ready){
//dosomrthing
}else{
//dosomrthing
}
int confd = connect(...);
if(-1 == confd){
//dosomrthing
}else{
//dosomething
}
- 另外一個原因是,代碼已經完成了,我們不可能爲了寫單元測試方便再去修改源碼,或者增加宏開關,不符合開發流程。
那麼如何已完成coding的模塊中,既可以使用我們mock的系統調用,也可以使用系統調用呢。
- dlsym()- obtain address of a symbol in a shared object or executable,即獲取動態庫或者可執行程序中的函數地址或者變量的地址。
#include <dlfcn.h>
void *dlsym(void *handle, const char *symbol);
Link with -ldl.
該函數有兩個參數,一個是句柄,另外一個是符號的字符串名稱,其中,對於第一個參數有兩個特殊的僞句柄,含義如下:
- RTLD_DEFAULT:
使用默認共享庫搜索順序查找所需符號的第一個匹配項。
搜索將在可執行文件及其依賴項中包括全局符號,以及帶有RTLD_GLOBAL標誌動態加載的共享庫中的符號。
- RTLD_NEXT:
在當前對象之後的搜索順序中找到所需符號的下一個匹配項。
如果,我當前源文件提供了一個與系統庫同名的函數,比如我源文件中實現了同名同參的int connect(…)函數,鏈接器優先鏈接我自定義實現的這個connect接口,如果沒有,那就是NEXTconnect,當然就是系統中的connect()了。
實例:
static int kEpollWaitCounter = 0;
//與系統調用同類型的函數指針
typedef int(*epoll_wait_func_t)(int epfd, struct epoll_event *events,int maxevents, int timeout);
//使用dlsym,將系統調用epoll_wait的符號地址取到保留下來 句柄爲RTLD_NEXT
epoll_wait_func_t epoll_wait_func = reinterpret_cast<epoll_wait_func_t>(dlsym(RTLD_NEXT,"epoll_wait"));
//這裏是我們mock epoll_wait自定義的一個同符號的函數,寫在我們的單元測試的cpp中
extern "C" int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout){
if(0 == kEpollWaitCounter){
++kEpollWaitCounter;
return -1; //這裏返回我麼期望返回的值即可
}
return epoll_wait_func(epfd,events,maxevents,timeout); //這裏正常調用系統中的epoll_wait
}
總結:
- dlsym以及RTLD_NEXT僞句柄用來保留系統中真真正正的系統調用符號地址;
- mock的函數中邏輯可以根據我們測試用例來設計;
- 鏈接器在鏈接時優先鏈接當前我們自定義實現的同符號的函數。