青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

陳碩的Blog

C++ 工程實踐(6):單元測試如何 mock 系統調用

陳碩 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

陳碩關于 C++ 工程實踐的系列文章: http://blog.csdn.net/Solstice/category/802325.aspx

陳碩博客文章合集下載: http://blog.csdn.net/Solstice/archive/2011/02/24/6206154.aspx

本作品采用“Creative Commons 署名-非商業性使用-禁止演繹 3.0 Unported 許可協議(cc by-nc-nd)”進行許可。http://creativecommons.org/licenses/by-nc-nd/3.0/

摘要:本文討論了在編寫單元測試時 mock 系統調用(以及其他第三方庫)的幾種做法。

本文只考慮 Linux x86/amd64 平臺。

陳碩在《分布式程序的自動化回歸測試》 http://blog.csdn.net/Solstice/archive/2011/04/25/6359748.aspx 一文中曾經談到單元測試在分布式程序開發中的優缺點(好吧,主要是缺點)。但是,在某些情況下,單元測試是很有必要的,在測試 failure 場景的時候尤顯重要,比如:

  • 在開發存儲系統時,模擬 read(2)/write(2) 返回 EIO 錯誤(有可能是磁盤寫滿了,有可能是磁盤出壞道讀不出數據)。
  • 在開發網絡庫的時候,模擬 write(2) 返回 EPIPE 錯誤(對方意外斷開連接)。
  • 在開發網絡庫的時候,模擬自連接 (self-connection),網絡庫應該用 getsockname(2) 和 getpeername(2) 判斷是否是自連接,然后斷開之。
  • 在開發網絡庫的時候,模擬本地 ephemeral port 用完,connect(2) 返回 EAGAIN 臨時錯誤。
  • 讓 gethostbyname(2) 返回我們預設的值,防止單元測試給公司的 DNS server 帶來太大壓力。

這些 test case 恐怕很難用前文提到的 test harness 來測試,該單元測試上場了?,F在的問題是,如何 mock 這些系統函數?或者換句話說,如何把對系統函數的依賴注入到被測程序中?

系統函數的依賴注入

在Michael Feathers 的《修改代碼的藝術 / Working Effectively with Legacy Code》一書第 4.3.2 節中,作者介紹了鏈接期接縫(link seam),正好可以解決我們的問題。另外,在 Stack Overflow 的一個帖子里也總結了幾種做法:http://stackoverflow.com/questions/2924440/advice-on-mocking-system-calls

如果程序(庫)在編寫的時候就考慮了可測試性,那么用不到上面的 hack 手段,我們可以從設計上解決依賴注入的問題。這里提供兩個思路。

其一,采用傳統的面向對象的手法,借助運行期的遲綁定實現注入與替換。自己寫一個 System interface,把程序里用到的 open、close、read、write、connect、bind、listen、accept、gethostname、getpeername、getsockname 等等函數統統用虛函數封裝一層。然后在代碼里不要直接調用 open(),而是調用 System::instance().open()。

這樣代碼主動把控制權交給了 System interface,我們可以在這里動動手腳。在寫單元測試的時候,把這個 singleton instance 替換為我們的 mock object,這樣就能模擬各種 error code。

其二,采用編譯期或鏈接期的遲綁定。注意到在第一種做法中,運行期多態是不必要的,因為程序從生到死只會用到一個 implementation object。為此付出虛函數調用的代價似乎有些不值。(其實,跟系統調用比起來,虛函數這點開銷可忽略不計。)

我們可以寫一個 system namespace 頭文件,在其中聲明 read() 和 write() 等普通函數,然后在 .cc 文件里轉發給對應系統的系統函數 ::read() 和 ::write() 等。

// SocketsOps.h
namespace sockets
{
int connect(int sockfd, const struct sockaddr_in& addr);
}
// SocketsOps.cc
int sockets::connect(int sockfd, const struct sockaddr_in& addr)
{
return ::connect(sockfd, sockaddr_cast(&addr), sizeof addr);
}
此處的代碼來自 muduo 網絡庫

http://code.google.com/p/muduo/source/browse/trunk/muduo/net/SocketsOps.h
http://code.google.com/p/muduo/source/browse/trunk/muduo/net/SocketsOps.cc

有了這么一層間接性,就可以在編寫單元測試的時候動動手腳,鏈接我們的 stub 實現,以達到替換實現的目的:

// MockSocketsOps.cc
int sockets::connect(int sockfd, const struct sockaddr_in& addr)
{
errno = EAGAIN;
return -1;
}

C++ 一個程序只能有一個 main() 入口,所以要先把程序做成 library,再用單元測試代碼鏈接這個 library。假設有一個 mynetcat 程序,為了編寫 C++ 單元測試,我們把它拆成兩部分,library 和 main(),源文件分別是 mynetcat.cc 和 main.cc。

在編譯普通程序的時候:

g++ main.cc mynetcat.cc SocketsOps.cc -o mynetcat

在編譯單元測試時這么寫:

g++ test.cc mynetcat.cc MockSocketsOps.cc -o test

以上是最簡單的例子,在實際開發中可以讓 stub 功能更強大一些,比如根據不同的 test case 返回不同的錯誤。這么做無需用到虛函數,代碼寫起來也比較簡潔,只用前綴 sockets:: 即可。例如應用程序的代碼里寫 sockets::connect(fd, addr)。

muduo 目前還沒有單元測試,只是預留了這些 stubs。

namespace 的好處在于它不是封閉的,我們可以隨時打開往里添加新的函數,而不用改動原來的頭文件(該文件的控制權可能不在我們手里)。這也是以 non-member non-friend 函數為接口的優點。


以上兩種做法還有一個好處,即只 mock 我們關心的部分代碼。如果程序用到了 SQLite 或 Berkeley DB 這些會訪問本地文件系統的第三方庫,那么我們的 System interface 或 system namespace 不會攔截這些第三方庫的 open(2)、close(2)、read(2)、write(2) 等系統調用。

鏈接期墊片 (link seams)

如果程序在一開始編碼的時候沒有考慮單元測試,那么又該如何注入 mock 系統調用呢?上面第二種做法已經給出了答案,那就是使用 link seam (鏈接期墊片)。

比方說要 mock connect(2) 函數,那么我們自己在單元測試程序里實現一個 connect() 函數,在鏈接的時候,會優先采用我們自己定義的函數。(這對動態鏈接是成立的,如果是靜態鏈接,會報  multiple definition 錯誤。好在絕大多數情況下 libc 是動態鏈接的。)

typedef int (*connect_func_t)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect_func_t connect_func = dlsym(RTDL_NEXT, "connect");
bool mock_connect;
int mock_connect_errno;
// mock connect
extern "C" int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
if (mock_connect) {
errno = mock_connect_errno;
return errno == 0 ? 0 : -1;
} else {
return connect_func(sockfd, addr, addrlen);
}
}


如果程序真的要調用 connect(2) 怎么辦?在我們自己的 mock connect(2) 里不能再調用 connect() 了,否則會出現無限遞歸。為了防止這種情況,我們用 dlsym(RTDL_NEXT, "connect") 獲得 connect(2) 系統函數的真實地址,然后通過函數指針 connect_func 來調用它。

例子:ZooKeeper 的 C client library

ZooKeeper 的 C client library 正是采用了 link seams 來編寫單元測試,代碼見:

http://svn.apache.org/repos/asf/zookeeper/tags/release-3.3.3/src/c/tests/LibCMocks.h
http://svn.apache.org/repos/asf/zookeeper/tags/release-3.3.3/src/c/tests/LibCMocks.cc

其他手法

Stack Overflow 的帖子里還提到一個做法,可以方便地替換動態庫里的函數,即使用 ld 的 --wrap 參數,
文檔里說得很清楚,這里不再贅述。

       --wrap=symbol
Use a wrapper function for symbol.  Any undefined reference to
symbol will be resolved to "__wrap_symbol".  Any undefined
reference to "__real_symbol" will be resolved to symbol.
This can be used to provide a wrapper for a system function.  The
wrapper function should be called "__wrap_symbol".  If it wishes to
call the system function, it should call "__real_symbol".
Here is a trivial example:
void *
__wrap_malloc (size_t c)
{
printf ("malloc called with %zu\n", c);
return __real_malloc (c);
}
If you link other code with this file using --wrap malloc, then all
calls to "malloc" will call the function "__wrap_malloc" instead.
The call to "__real_malloc" in "__wrap_malloc" will call the real
"malloc" function.
You may wish to provide a "__real_malloc" function as well, so that
links without the --wrap option will succeed.  If you do this, you
should not put the definition of "__real_malloc" in the same file
as "__wrap_malloc"; if you do, the assembler may resolve the call
before the linker has a chance to wrap it to "malloc".

第三方 C++ 庫

link seam 同樣適用于第三方 C++ 庫

比方說公司某個基礎庫團隊提供了了 File class,但是這個 class 沒有使用虛函數,我們無法通過 sub-classing 的辦法來實現 mock object。

class File : boost::noncopyable
{
public:
File(const char* filename);
~File();
int readn(void* data, int len);
int writen(const void* data, int len);
size_t getSize() const;
private:
};

如果需要為用到 File class 的程序編寫單元測試,那么我們可以自己定義其成員函數的實現,這樣可以注入任何我們想要的結果。

// MockFile.cc
int File::readn(void* data, int len)
{
return -1;
}

(這個做法對動態庫是可行的,靜態庫會報錯。我們要么讓對方提供專供單元測試的動態庫,要么拿過源碼來自己編譯一個。)


Java 也有類似的做法,在 class path 里替換我們自己的 stub jar 文件,以實現 link seam。不過 Java 有動態代理,很少用得著 link seam 來實現依賴注入。

posted on 2011-05-16 00:18 陳碩 閱讀(3579) 評論(4)  編輯 收藏 引用

評論

# re: C++ 工程實踐(6):單元測試如何 mock 系統調用 2011-05-16 09:05 beat by dre

我學的是軟件專業 但現在真的做著很多與軟件無關的事情 我會回來的 我付出三年學習的東西我會改變我自己  回復  更多評論   

# beat by dre 2011-05-16 10:35 beat by dre

I really enjoy my visits to your blog....  回復  更多評論   

# re: C++ 工程實踐(6):單元測試如何 mock 系統調用 2011-05-16 12:29 陳梓瀚(vczh)

直接往一塊軟盤寫,很容易就囧掉的啦。  回復  更多評論   

# re: C++ 工程實踐(6):單元測試如何 mock 系統調用 2011-05-16 17:50 goedkope nike schoenen

When you present your work, you present your self!thank you for the

informative post and keep up the good work  回復  更多評論   

<2011年5月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

導航

統計

常用鏈接

隨筆分類

隨筆檔案

相冊

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲第一页在线| 在线成人av.com| 中文亚洲免费| 艳女tv在线观看国产一区| 欧美日韩国语| 亚洲一区二区三区免费观看| 亚洲深夜av| 国产日韩在线亚洲字幕中文| 看片网站欧美日韩| 欧美国产一区二区三区激情无套| 一本色道久久综合一区| 亚洲一级在线| 黄色精品在线看| 亚洲国产精品久久久久秋霞不卡| 欧美国产视频在线观看| 午夜欧美视频| 久久久精品国产一区二区三区| 亚洲精选在线| 亚洲在线电影| 最新成人av网站| 亚洲综合色在线| 永久91嫩草亚洲精品人人| 91久久在线| 国产亚洲欧美中文| 亚洲日本中文字幕免费在线不卡| 国产精品夜夜夜| 欧美激情第3页| 国产精品入口麻豆原神| 麻豆九一精品爱看视频在线观看免费| 欧美激情综合亚洲一二区| 欧美中文字幕在线观看| 欧美成年人视频| 久久精品二区三区| 欧美日韩成人综合| 久久香蕉国产线看观看网| 欧美日韩国产免费| 鲁大师影院一区二区三区| 国产精品二区在线| 亚洲欧洲在线观看| 亚洲欧美韩国| 中文精品99久久国产香蕉| 久久精品夜色噜噜亚洲aⅴ| 中文精品视频| 欧美韩国日本综合| 美日韩精品视频免费看| 国产精品美女www爽爽爽视频| 欧美黄色视屏| 影音先锋日韩资源| 欧美中在线观看| 亚洲欧美www| 欧美性大战久久久久久久蜜臀| 亚洲福利在线观看| 1024精品一区二区三区| 午夜亚洲一区| 欧美一区二区三区在线免费观看 | 亚洲大胆女人| 精品成人一区二区三区| 欧美影视一区| 久久精品99无色码中文字幕| 欧美性猛交视频| 9色国产精品| 亚洲一区二区三区乱码aⅴ| 欧美人妖在线观看| 最新中文字幕亚洲| 日韩一区二区电影网| 欧美wwwwww| 亚洲第一在线综合网站| 亚洲激精日韩激精欧美精品| 久久一区欧美| 欧美 日韩 国产 一区| 亚洲国产成人不卡| 欧美不卡视频| 亚洲美洲欧洲综合国产一区| 9久re热视频在线精品| 欧美日韩亚洲天堂| 在线综合+亚洲+欧美中文字幕| 夜夜精品视频| 国产精品实拍| 久久精品官网| 亚洲激情在线激情| 99精品免费网| 国产精品视频免费观看www| 亚洲女性裸体视频| 老牛影视一区二区三区| 亚洲国产精品ⅴa在线观看| 欧美电影在线| 亚洲视频免费看| 久久久精品一区二区三区| 在线欧美不卡| 欧美日韩在线观看视频| 亚洲女同精品视频| 欧美大色视频| 亚洲女同性videos| 国产在线乱码一区二区三区| 麻豆精品精品国产自在97香蕉| 亚洲精品乱码久久久久久| 欧美一级欧美一级在线播放| 在线观看福利一区| 欧美日韩性生活视频| 午夜精品视频网站| 亚洲国产清纯| 久久狠狠婷婷| 99国产精品久久久久久久| 国产日韩在线一区| 欧美日韩激情网| 久久久国产精品一区二区三区| 亚洲精品小视频| 一区视频在线看| 欧美日韩在线播放一区| 久久免费高清| 亚洲免费影视第一页| 亚洲国产精品一区二区三区| 欧美伊人久久久久久午夜久久久久 | 欧美激情第一页xxx| 香蕉久久夜色精品| 亚洲欧洲精品一区| 国产一区二区精品久久| 欧美日韩中文另类| 老**午夜毛片一区二区三区| 亚洲一区在线免费| 亚洲日本在线视频观看| 美女精品视频一区| 久久国产精品久久久久久电车| 亚洲最黄网站| 亚洲人成在线观看网站高清| 国产一区二区黄| 国产精品久久久久久久久免费 | 欧美日韩视频专区在线播放| 欧美成年视频| 裸体丰满少妇做受久久99精品| 亚洲一区bb| 一区二区三区www| 亚洲日本成人| 亚洲欧洲日产国码二区| 亚洲二区精品| 亚洲电影专区| 亚洲大片一区二区三区| 欧美国产日韩一区二区| 欧美成年人视频| 免费在线欧美黄色| 麻豆freexxxx性91精品| 久久久久久亚洲精品中文字幕 | 欧美在线视频观看| 亚洲欧美偷拍卡通变态| 亚洲综合丁香| 欧美一级夜夜爽| 久久精品亚洲乱码伦伦中文| 久久国产乱子精品免费女| 久久精品国产成人| 久久精品视频网| 你懂的一区二区| 欧美大香线蕉线伊人久久国产精品| 欧美成人免费在线观看| 欧美激情一区二区久久久| 亚洲高清一区二| 亚洲精品日韩在线| 亚洲一区在线观看视频 | 亚洲自拍偷拍色片视频| 亚洲综合好骚| 久久精品国产亚洲a| 久久人人看视频| 欧美激情中文不卡| 国产精品美腿一区在线看| 国产精品一区二区三区久久久| 国产一本一道久久香蕉| 伊人婷婷欧美激情| 亚洲精品黄色| 亚洲欧美日本另类| 久久婷婷国产综合精品青草| 免费不卡在线视频| 亚洲人成毛片在线播放女女| 国产精品99久久久久久白浆小说| 午夜久久99| 欧美激情综合| 国产综合色一区二区三区| 亚洲欧洲在线免费| 性刺激综合网| 日韩一级免费观看| 久久爱另类一区二区小说| 欧美成人午夜剧场免费观看| 一区二区免费看| 久久蜜桃资源一区二区老牛| 欧美日韩精品系列| 精品成人乱色一区二区| 亚洲在线国产日韩欧美| 欧美99在线视频观看| 亚洲一区二区三区免费视频 | 亚洲精品视频在线观看网站| 欧美一级免费视频| 欧美另类女人| 激情欧美日韩| 欧美一区二区三区四区在线观看地址| 欧美1区2区3区| 午夜综合激情| 欧美色图麻豆| 亚洲精品日日夜夜| 久久久7777| 亚洲一区二区伦理| 欧美精品一区二区三区蜜臀| 在线观看福利一区|