1. 引言
最近寫python用unittest模塊做單元測試,才發(fā)現(xiàn)自己過去寫C++居然都是手工測試。查了一番資料之后稍途,發(fā)現(xiàn)Catch和Valgrind這兩個工具可以很好地滿足需求节猿。
測試C++程序時,我們通常會在意兩件事:
- 運行結(jié)果是否正確吹零?
- 是否發(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那條路徑的話:
- 選擇"t"浪箭,s從“”變成“t”豺憔,不是有效單詞衡怀;
- 選擇“e”银伟,s從“t”變成“te”,不是有效單詞并徘;
- 選擇“n”遣钳,s從“te”變成“ten”,是有效單詞麦乞。
首先來看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出錯:
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測試仍然會通過:
用Valgrind檢測會得到很長的報告,這里只看最后的leak summary:
definitely lost和indirectly lost的含義可以看參考資料,這兩個值不為0表示發(fā)生了內(nèi)存泄漏怎顾。
恢復(fù)tear down部分读慎,再次make后用Valgrind檢測:
definitely lost和indirectly lost都為0,沒有內(nèi)存泄漏槐雾。
說起來Valgrind真的非常厲害夭委,比如寫了這種毫無違和感的錯誤代碼:
int main() {
int *d = new int[18];
delete d;
return 0;
}
真心不一定能看出錯誤,但用Valgrind檢測一下募强,就會在報告里看到:
提示釋放方法錯誤(mismatched free)株灸,應(yīng)該用delete []。
5. 參考資料
- Catch的官方tutorial:
https://github.com/philsquared/Catch/blob/master/docs/tutorial.md - Valgrind的官方quick start guide:
http://valgrind.org/docs/manual/quick-start.html#quick-start.mcrun - 使用Valgrind memcheck進行C/C++的內(nèi)存泄漏檢測:
http://www.oschina.net/translate/valgrind-memcheck - 應(yīng)用 Valgrind 發(fā)現(xiàn) Linux 程序的內(nèi)存問題:
https://www.ibm.com/developerworks/cn/linux/l-cn-valgrind/