使用dlsym()來mock已經完成code的單元測試中的系統調用------鏈接期墊片(link seam)

最近,在給公司的一些模塊添加單元測試,金主們要求項目中分支的覆蓋率達到80%,經過一段時間的工作,添加了很多的測試用例,但是分支覆蓋率還是不理想,主要原因主要是:

  1. 單元中涉及好多的分支都是對一些系統調用不用返回值的處理,比如下面的幾個例子.我們知道這些系統調用正常來說很少會,也比較困難出現異常,在寫test cases時就比較困難;
int ready = epoll_wait(...);
if(-1 == ready){
	//dosomrthing
}else{
	//dosomrthing
}

int confd = connect(...);
if(-1 == confd){
	//dosomrthing
}else{
	//dosomething
}
  1. 另外一個原因是,代碼已經完成了,我們不可能爲了寫單元測試方便再去修改源碼,或者增加宏開關,不符合開發流程。

那麼如何已完成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.

該函數有兩個參數,一個是句柄,另外一個是符號的字符串名稱,其中,對於第一個參數有兩個特殊的僞句柄,含義如下:

  1. RTLD_DEFAULT:
使用默認共享庫搜索順序查找所需符號的第一個匹配項。
搜索將在可執行文件及其依賴項中包括全局符號,以及帶有RTLD_GLOBAL標誌動態加載的共享庫中的符號。
  1. 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
}

總結:

  1. dlsym以及RTLD_NEXT僞句柄用來保留系統中真真正正的系統調用符號地址;
  2. mock的函數中邏輯可以根據我們測試用例來設計;
  3. 鏈接器在鏈接時優先鏈接當前我們自定義實現的同符號的函數。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章