C++單元測試框架:gtest

C++單元測試框架:gtest

1) 介紹

gtest是Google的C++測試框架,能夠幫助開發者更簡單快捷得寫好C++單元測試。而且不管你是工做在Linux,Windows仍是Mac上。html

若是你曾寫過單元測試,不管用的什麼語言,相信你很快就能上手。若是還不瞭解,那麼就從這裏開始吧。node

若是你想更深刻了解測試框架,能夠看Kent Beck大神的《測試驅動開發》linux

2) 準備

2.1) 目錄樹

gtest_start/
├─build/            # 構建文件
├─lib/              # 第三方庫
├─output/           # 輸出目錄(中間文件,執行文件)
│  ├─gtest/
│  └─primer/
│      ├─Debug
│      │  └─obj/
│      └─Release
├─src/              # 工程代碼
│  └─primer/
├─third_party/      # 第三方庫代碼
│  └─gtest/
└─tools/            # 工具腳本

這樣清理方便些。c++

2.2) gtest

# 準備目錄
mkdir -p gtest_start/third_party/
cd gtest_start/third_party/
# 獲取源碼
svn co http://googletest.googlecode.com/svn/trunk/ gtest

gtest自己提供了多平臺的構建文件,以下:編程

We provide build files for some popular build systems: msvc/ for Visual Studio, xcode/ for Mac Xcode, make/ for GNU make, codegear/ for Borland C++ Builder, and the autotools script (deprecated) and CMakeLists.txt for CMake (recommended) in the Google Test root directory.設計模式

若是你用的不是以上這些,則能夠看make/Makefile或msvc/gtest.sln工程,參考配置。xcode

主要是將gtest-all.cc生成靜態庫。若是帶gtest_main.cc,就省去了寫main函數。框架

3) 簡單測試

3.1) TEST()宏

Step 1: 建立一個簡單函數:ide

bool IsEven(int num) {
    return num % 2 == 0;
}

Step 2: 用TEST()宏定義並命名一個測試方法:svn

TEST(test_case_name, test_name) {
    ... test body ...
}

對於IsEven(),能夠這樣:

TEST(IsEvenTest, TestA) {
    ASSERT_TRUE(IsEven(2));
    ASSERT_TRUE(IsEven(3));
}

Step 3: 工程引入gtest_main靜態庫,運行便可。Linux下記得添加-lpthread

運行結果

Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from IsEvenTest
[ RUN      ] IsEvenTest.TestA
[       OK ] IsEvenTest.TestA (0 ms)
[----------] 1 test from IsEvenTest (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (2 ms total)
[  PASSED  ] 1 test.

3.2) ASSERT_*()宏

ASSERT_*()宏,當失敗時,會生成致命錯誤,退出當前TEST()。

ASSERT_*()宏相應都會有個EXPECT_*()版本,當失敗時,其生成的是非致命錯誤,會繼續當前TEST()。

例如

TEST(IsEvenTest, TestA) {
    cout << "IsEvenTest start" << endl;
    ASSERT_TRUE(IsEven(3));  // failed
    ASSERT_FALSE(IsEven(3));
    cout << "IsEvenTest end" << endl;
}

TEST(PowerTest, TestA) {
    cout << "PowerTest start" << endl;
    EXPECT_EQ(9, Power(2, 3));  // failed, expected: 8
    EXPECT_EQ(27, Power(3, 3));
    cout << "PowerTest end" << endl;
}

運行結果

# ...
[ RUN      ] IsEvenTest.TestA
IsEvenTest start
..\src\primer\simple_unittest.cc(10): error: Value of: IsEven(3)
  Actual: false
Expected: true
[  FAILED  ] IsEvenTest.TestA (3 ms)
# ...
[ RUN      ] PowerTest.TestA
PowerTest start
..\src\primer\simple_unittest.cc(17): error: Value of: Power(2, 3)
  Actual: 8
Expected: 9
PowerTest end
[  FAILED  ] PowerTest.TestA (4 ms)
# ...
[  FAILED  ] 2 tests, listed below:
# ...

注意:沒有輸出"IsEvenTest end",但有輸出"PowerTest end"。

不過只要是失敗,都會被統計進最後的tests失敗列表。

ASSERT_*()基本的一些宏,請見參考1的Assertions

4) 測試套件

當你寫的不少測試都用相似的數據做爲輸入時,這時應當考慮用test fixture

例如

template<typename T = Data>
class Calculator {
public:
    T Plus(T lhs, T rhs) { return lhs + rhs; }
    T Minus(T lhs, T rhs) { return lhs - rhs; }
    T Multiplies(T lhs, T rhs) { return lhs * rhs; }
    T Divides(T lhs, T rhs) { return lhs / rhs; }
};

測試Plus、Minus、Multiplies、Divides時,能夠都用兩個一樣的Data做爲數據。這時,你能夠建立一個測試套件:

Step 1: 繼承::testing::Test。且以protected:public:開始,以使子類能夠訪問。

class CalculatorTest : public ::testing::Test {
protected:
    // ...
};

Step 2: 聲明你要用的數據。這兒是Data:

Data* data_a_;
Data* data_b_;

// 如下爲對ab進行四種操做後的結果:
Data plus_ab;
Data minus_ab;
Data multiplies_ab;
Data divides_ab;

Step 3: 在默認構造函數或SetUp()函數內準備好數據:

CalculatorTest()
    : plus_ab(10),
      minus_ab(-6),
      multiplies_ab(16),
      divides_ab(0) {
}

// Sets up the test fixture.
virtual void SetUp() {
    data_a_ = new Data(2);
    data_b_ = new Data(8);
}

Step 4: 在析構函數或TearDown()函數內釋放數據:

virtual ~CalculatorTest() {
}

// Tears down the test fixture.
virtual void TearDown() {
    delete data_a_;
    data_a_ = nullptr;
    delete data_b_;
    data_b_ = nullptr;
}

Step 5: 用TEST_F()宏來作測試,它容許你訪問套件內成員:

TEST_F(CalculatorTest, Plus) {
    Calculator<> calculator;
    EXPECT_EQ(plus_ab, calculator.Plus(*data_a_, *data_b_));
}

運行結果

Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from CalculatorTest
[ RUN      ] CalculatorTest.Plus
[       OK ] CalculatorTest.Plus (0 ms)
[----------] 1 test from CalculatorTest (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (4 ms total)
[  PASSED  ] 1 test.

但這裏,若是同時測試四個,Calculator<> calculator;豈不是要建立四次。所以咱們能夠考慮將它共享:

Step 1: 定義爲一個static成員:

class CalculatorTest : public ::testing::Test {
protected:
    // ...

    // Shared by all tests.
    static Calculator<>* shared_calculator_;
};

固然,別忘記初始化:

Calculator<>* CalculatorTest::shared_calculator_ = nullptr;

Step 2: 在static SetUpTestCase()函數內準備數據:

// Sets up the stuff shared by all tests in this test case.
static void SetUpTestCase() {
    shared_calculator_ = new Calculator<>;
}

Step 3: 在static TearDownTestCase()函數內釋放數據:

// Tears down the stuff shared by all tests in this test case.
static void TearDownTestCase() {
    delete shared_calculator_;
    shared_calculator_ = nullptr;
}

Step 4: 而後,TEST_F()宏作測試:

TEST_F(CalculatorTest, Plus) {
    EXPECT_EQ(plus_ab, shared_calculator_->Plus(*data_a_, *data_b_));
}

上述是同一個測試用例下共享數據。另外還有全局環境的,見Global Set-Up and Tear-Down

5) 總結

本文僅僅是初步體驗了gtest,另外還有死亡測試,事件監聽,以及重複測試、臨時禁用等選項。具體請見參考2。

除此以外,其餘一些有用的文章也列在了參考裏。

6) 參考

  1. V1_7_Primer
  2. V1_7_AdvancedGuide

以上兩個官方文檔,很是詳細。有必要閱覽一遍,並可做爲手冊。

另外,這個系列也很不錯:玩轉Google開源C++單元測試框架Google Test系列。把官方文檔主要的一些內容都講述了遍。

還有IBM developerWorks 中國上的幾篇:

  1. 面向 C++ 的測試驅動開發
    • 介紹了下測試驅動開發,並比較了Gtest,Boost Test,CPPUnit,以及三子棋遊戲的實踐。
  2. 輕鬆編寫 C++ 單元測試
    • googletest與googlemock的介紹和使用。
  3. Google C++ Testing Framework 簡介

附1:樣例工程gtest_start

下載:gtest_start.zip

1) 構建文件

build/gtest_start-gcc.cbp   # for gnu gcc
build/gtest_start-msvc.cbp  # for msvc 2010

.cbp由C::B打開便可。

2) 補充說明

"src/primer/"下爲本文例子,而"src/thoughts/"下是我之前寫的內容:

├─src/
│  ├─primer/                # 本文例子
│  └─thoughts/
│      ├─designpattern/     # C++編程思想設計模式一節例子及其測試
│      ├─gtest/             # gtest原生sample,queue測試例子
│      └─stl/               # stl的測試例子

"designpattern/"下例子,"-Wall"有不少警告,也確實是有些問題的,不用太在乎。

參考

  1. C::B安裝:【筆記】Code::Blocks

附2:升級GCC後,動態庫連接問題

1) 問題現象

Linux下運行程序時,報出"/usr/lib/libstdc++.so.6: version 'GLIBCXX_3.4.14' not found"的問題。

2) 問題緣由

先前我升級GCC到了4.8.2,且只是軟連接到了高版本的gcc,g++。不知道還要對應升級下libstdc++庫。見:【筆記】CentOS上源碼安裝GCC 4.8.2

3) 解決流程

Step 1: 命令檢查libstdc++.so使用的GLIBC版本:

strings /usr/lib/libstdc++.so.6 | grep GLIB

輸出:

GLIBCXX_3.4
GLIBCXX_3.4.1
...
GLIBCXX_3.4.12
GLIBCXX_3.4.13
GLIBC_2.0
...
GLIBC_2.2
GLIBCXX_FORCE_NEW
GLIBCXX_DEBUG_MESSAGE_LENGTH

沒發現'GLIBCXX_3.4.14'。

Step 2: 檢查/usr/lib目錄下的libstdc++的庫文件:

ll /usr/lib/libstdc++*

輸出:

lrwxrwxrwx. 1 root root     19 Mar 18 04:57 /usr/lib/libstdc++.so.6 -> libstdc++.so.6.0.13
-rwxr-xr-x. 1 root root 942040 Nov 21 07:28 /usr/lib/libstdc++.so.6.0.13

檢查下安裝:

yum list libstdc++*
yum install libstdc++

已是最高版本了。

Step 3: 由GCC版本,搜索了下'libstdc++-4.8.2'。

gcc --version
# gcc (GCC) 4.8.2

發現這篇:Installation of Target Libstdc++,看命令源碼目錄下有'libstdc++-v3',確實。

cd /home/join/Env/gcc/gcc-4.8.2/
find ./ -name libstdc++.so*

輸出:

./stage1-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
./stage1-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so
./stage1-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.18
./i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
./i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so
./i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.18
./prev-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
./prev-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so
./prev-i686-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.18

我這邊的環境,有三個路徑下有libstdc++.so.6.0.18,libstdc++.so與libstdc++.so.6只是其軟連接。

查看各路徑下libstdc++.so.6.0.18狀態:

stat libstdc++.so.6.0.18

發現各路徑下libstdc++.so.6.0.18都同樣大小。stage1前綴路徑下最先生成,以後prev前綴的,不帶前綴的最晚。除了生成時間,另有個Inode不一樣,存儲在文件系統的索引節點(必然的)。

不清楚Inode,因此瞭解了下:inode理解inode

Step 4: 替換老版本libstdc++庫:

# 複製到'/usr/lib/'
cd ./i686-pc-linux-gnu/libstdc++-v3/src/.libs/
cp libstdc++.so.6.0.18
cp libstdc++.so.6.0.18 /usr/lib/
# 從新創建軟連接
rm -f libstdc++.so.6
ln -s libstdc++.so.6.0.18 libstdc++.so.6

參考

  1. /usr/lib/libstdc++.so.6: version `GLIBCXX_3.4.15' not found
相關文章
相關標籤/搜索