測試C++程序:使用Catch和Valgrind

1. 引言

最近寫python用unittest模塊做單元測試,才發(fā)現(xiàn)自己過去寫C++居然都是手工測試。查了一番資料之后稍途,發(fā)現(xiàn)Catch和Valgrind這兩個工具可以很好地滿足需求节猿。

測試C++程序時,我們通常會在意兩件事:

  1. 運行結(jié)果是否正確吹零?
  2. 是否發(fā)生了內(nèi)存泄漏罩抗?

第一件事所有編程語言都需要在意,通常是給程序各種輸入灿椅,檢驗輸出的正確性套蒂,Catch是一個輕巧的單元測試框架,學(xué)習(xí)起來非常容易茫蛹;
第二件事應(yīng)該是C/C++獨有的操刀,需要跟蹤運行時動態(tài)分配的內(nèi)存,雖然可以自行重載new/delete運算符做到這一點婴洼,但Valgrind可以為我們檢測絕大多數(shù)內(nèi)存相關(guān)問題(包括內(nèi)存泄漏骨坑、數(shù)組越界、使用未初始化變量等)。

2 準(zhǔn)備工作

2.1 環(huán)境

我用的系統(tǒng)是Ubuntu 16.04欢唾,之所以推薦這兩款工具且警,是因為它們安裝和使用都太容易了,完全不折騰礁遣。

首先不妨觀摩下怎樣搭建Gtest環(huán)境斑芜,然后我們來安裝Catch。

第一步祟霍,下載Catch的單一頭文件Catch.hpp杏头;
第二步,把Catch.hpp放到工程目錄下(確保能正確include即可)沸呐。

結(jié)束了醇王,比把大象裝到冰箱里還少一步。

然后安裝Valgrind只要一步:

apt-get install valgrind
“我們不用很麻煩很累就可以測試”

2.2 編寫Trie

正好刷Leetcode寫到了Trie崭添,就修改一下拿它做例子寓娩,不感興趣可以直接跳到第三節(jié)Catch的使用方法。

Trie又叫字典樹滥朱、前綴樹(prefix tree)根暑,是一種用來實現(xiàn)快速檢索的多叉樹。簡單地講徙邻,從根節(jié)點出發(fā)經(jīng)過的路徑確定了一個字符串排嫌,每個節(jié)點有標(biāo)記當(dāng)前字符串是否為有效單詞。比如記當(dāng)前字符串為s缰犁,走ten那條路徑的話:

  1. 選擇"t"浪箭,s從“”變成“t”豺憔,不是有效單詞衡怀;
  2. 選擇“e”银伟,s從“t”變成“te”,不是有效單詞并徘;
  3. 選擇“n”遣钳,s從“te”變成“ten”,是有效單詞麦乞。
Trie示意圖蕴茴,盜自wiki

首先來看Trie中節(jié)點TrieNode的定義:

typedef struct TrieNode {
    bool completed;
    std::map<char, TrieNode *> children;
    TrieNode() : completed(false) {};
} TrieNode;

TrieNode用bool值completed標(biāo)記當(dāng)前字符串是否為有效單詞,用children實現(xiàn)字符到后繼TrieNode的映射姐直。比如上圖的根節(jié)點倦淀,children里就會有“t”、“A”声畏、“i”三項撞叽。

然后來看Trie的定義:

class Trie {
    public:
        Trie(void);
        ~Trie(void);
        void insert(std::string word);
        bool search(std::string word);
    private:
        TrieNode *root;
};

含義非常清楚,除去構(gòu)造和析構(gòu)函數(shù)外,insert用于把單詞加入Trie愿棋,search用于查找單詞是否在Trie中科展。
Trie的具體實現(xiàn)放在我github上的DSAF里,這里直奔主題不再贅述初斑。

3. 使用Catch

話不多說辛润,直接看使用Catch的測試文件test.cpp:

#include <iostream>
#include <cstdlib>

#include "Trie.h"

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

using namespace std;

TEST_CASE("Testing Trie") {
    // set up
    Trie *t = new Trie();

    // different sections
    SECTION("Search an existent word.") {
        string word = "abandon";
        t->insert(word);
        REQUIRE(t->search(word) == true);
    }
    SECTION("Search a nonexistent word.") {
        string word = "abandon";
        REQUIRE(t->search(word) == false);
    }

    // tear down
    delete t;
}

除去trivial的#include "catch.hpp"外膨处,使用#define CATCH_CONFIG_MAIN表示讓Catch自動提供main函數(shù)见秤,運行在TEST_CASE中設(shè)計的測試。

簡單地講真椿,每個TEST_CASE由三部分組成鹃答,set up、sections和tear down突硝,set up是各個section都需要的準(zhǔn)備工作测摔,tear down是各個section都需要的清理工作,set up和tear down對于每個section都會執(zhí)行一遍解恰。

比如有一個TEST_CASE:

TEST_CASE {
    set up
    case 1
    case 2
    tear down
}

真正執(zhí)行時就是:set up->case 1->tear down->set up->case 2->tear down锋八。

此處TEST_CASE里的兩個section,第一個section是查找Trie中存在的單詞护盈,第二個section是查找Trie中不存在的單詞挟纱。REQUIRE是Catch提供的宏,相當(dāng)于assert腐宋,檢驗表達式是否成立紊服。

寫好Makefile文件:

HDRS = $(wildcard *.h)
SRCS = $(wildcard *.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SRCS))
DEPS = $(patsubst %.cpp, %.d, $(SRCS))

TARGET = test
CXX = g++

$(TARGET): $(OBJS)
    $(CXX) -g -o $(TARGET) $(OBJS)

-include $(DEPS)

%.o: %.cpp
    $(CXX) -c -MMD -std=c++11 $<

.PHONY: clean

clean:
    -rm *.o
    -rm *.d
    -rm *.gch
    -rm $(TARGET)

make之后運行test,可以看到:

運行輸出

修改search方法胸竞,使得總是返回true欺嗤,那么第一個section仍然正確,第二個section出錯:

search總是返回true

Catch告訴我們在第二個section卫枝,也就是“Search a nonexistent word”時出錯煎饼,失敗的原因是實際search結(jié)果為true。

4. 使用Valgrind

Valgrind其實是一套工具的集合校赤,可以用--tool參數(shù)指定使用哪種工具吆玖,默認(rèn)使用的是內(nèi)存檢測工具Memcheck。Valgrind使用更加簡單痒谴,比如編譯鏈接后的可執(zhí)行文件是test衰伯,那么檢測內(nèi)存泄漏情況只需使用命令:

valgrind leak-check=yes ./test

注意要用./test而不是test。

注釋掉test.cpp里tear down部分积蔚,那么構(gòu)造的Trie不會被釋放意鲸,用Catch測試仍然會通過:

注釋掉tear down部分

用Valgrind檢測會得到很長的報告,這里只看最后的leak summary:

未釋放的leak summary

definitely lost和indirectly lost的含義可以看參考資料,這兩個值不為0表示發(fā)生了內(nèi)存泄漏怎顾。

恢復(fù)tear down部分读慎,再次make后用Valgrind檢測:

釋放后的leak summary

definitely lost和indirectly lost都為0,沒有內(nèi)存泄漏槐雾。

說起來Valgrind真的非常厲害夭委,比如寫了這種毫無違和感的錯誤代碼:

int main() {
    int *d = new int[18];
    delete d;
    return 0;
}

真心不一定能看出錯誤,但用Valgrind檢測一下募强,就會在報告里看到:

mismatched free

提示釋放方法錯誤(mismatched free)株灸,應(yīng)該用delete []。

新技能get√

5. 參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末擎值,一起剝皮案震驚了整個濱河市慌烧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸠儿,老刑警劉巖屹蚊,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異进每,居然都是意外死亡汹粤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門田晚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘱兼,“玉大人,你說我怎么就攤上這事肉瓦≡饩” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵泞莉,是天一觀的道長哪雕。 經(jīng)常有香客問我,道長鲫趁,這世上最難降的妖魔是什么斯嚎? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮挨厚,結(jié)果婚禮上堡僻,老公的妹妹穿的比我還像新娘。我一直安慰自己疫剃,他們只是感情好钉疫,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巢价,像睡著了一般牲阁。 火紅的嫁衣襯著肌膚如雪固阁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天城菊,我揣著相機與錄音备燃,去河邊找鬼。 笑死凌唬,一個胖子當(dāng)著我的面吹牛并齐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播客税,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼况褪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了霎挟?” 一聲冷哼從身側(cè)響起窝剖,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酥夭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脊奋,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡熬北,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诚隙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讶隐。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖久又,靈堂內(nèi)的尸體忽然破棺而出巫延,到底是詐尸還是另有隱情,我是刑警寧澤地消,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布炉峰,位于F島的核電站脉执,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏半夷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一巫橄、第九天 我趴在偏房一處隱蔽的房頂上張望淘邻。 院中可真熱鬧,春花似錦湘换、人聲如沸敬尺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至崎溃,卻和暖如春蜻直,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袁串。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赎瑰,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓餐曼,卻偏偏與公主長得像鲜漩,于是被迫代替她去往敵國和親源譬。 傳聞我的和親對象是個殘疾皇子孕似,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)养渴,斷路器,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 1.1 什么是自動引用計數(shù) 概念:在 LLVM 編譯器中設(shè)置 ARC(Automaitc Reference Co...
    __silhouette閱讀 5,152評論 1 17
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,085評論 25 707
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法中捆,內(nèi)部類的語法,繼承相關(guān)的語法泄伪,異常的語法,線程的語...
    子非魚_t_閱讀 31,625評論 18 399
  • 生產(chǎn)/采購入庫后蟋滴,入庫的數(shù)量會自動加入庫存量。現(xiàn)在咱們回到《訂單和出貨》頁面津函。注意到,某些訂單項經(jīng)過入庫尔苦,其庫存量...
    a85d6aa7027f閱讀 284評論 0 1