GoogleTest測試最大子數組和的C++程序

簡介:

全文分紅三部分:第一部分介紹GoogleTest框架;第二部分是測試;第三部分是我的感想。node

繼續吐槽博客園的Markdown。。不如其餘博客的好用,格式很容易亂。。。。c++

終於調整好格式了。。。。。。博客園的markdown 好low low low啊git

GoogleTest測試框架基本使用方法:

首先,給出官方文檔的連接。如下內容主要是我本身翻譯的官方文檔並結合本身的理解,總結出的基本要點。若是想要對技術更深刻的瞭解,建議仍是去看官方文檔。最後說明一下,這裏的內容僅僅在Linux系統的上執行過,其餘的系統應該也是按照相似的步驟進行。Linux下關於如何安裝GoogleTest框架,請參考我在CSDN的這篇博客github

基本概念

使用GoogleTest要先從學習寫斷言開始,斷言用於檢測一個測試條件是否爲真。斷言的結果有三種狀況:success, nonfatal failure, fatal failure。若是 fatal failure出現,它將會打斷當前的函數;不然程序會正常運行。
一個測試實例能夠包含多個測試,咱們須要把這些測試組織成合理的結構。當多個測試實例須要共享公共對象和或者子程序,咱們能夠把他們組織到一個測試類中。算法

斷言

GoogleTest的斷言是一種相似於函數調用的斷言機制。咱們能夠在GoogleTest自己的斷言消息後面定義本身的測試失敗信息。下面說明幾種不一樣斷言方式:數組

  1. ASSERT_*產生fatal failures,直接終止當前函數的運行
  2. EXPECT_*: 產生nonfatal failures,不會終止當前函數運行
  3. EXPECT_*: 最經常使用的一種方式,能夠容許報告產生一個或者多個failer

爲了提供自定義的失敗信息,可使用C++的stream流操做把字符輸入到斷言中,藉助於<<操做符號。
好比:markdown

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

注意,任何能夠寫入流ostream的,均可被寫入斷言宏,好比C的字符串、string對象,甚至能夠是廣義上的字符(流):Windows的Unicode下的wchar_t、 TCHAR或者C++的std::string。全部流的輸入都會轉化成UTF-8的格式。框架

基礎斷言斷定:

Name Academy score
ASSERT_TRUE(condition) EXPECT_TRUE(condition) condition is true
ASSERT_FALSE(condition) EXPECT_FALSE(condition) condition is false

二元比較運算符:

Fatal assertion Nonfatal assertion Verifies
ASSERT_EQ(val1,val2); EXPECT_EQ(val1,val2); val1 == val2
ASSERT_NE(val1,val2); EXPECT_NE(val1,val2); val1 != val2
ASSERT_LT(val1,val2); EXPECT_LT(val1,val2); val1 < val2
ASSERT_LE(val1,val2); EXPECT_LE(val1,val2); val1 <= val2
ASSERT_GT(val1,val2); EXPECT_GT(val1,val2); val1 > val2
ASSERT_GE(val1,val2); EXPECT_GE(val1,val2); val1 >= val2

若是上述的一個測試失敗,那麼會打印出val1val2的值。ide

Value參數必須與斷言比較運算符兼容,不然有編譯錯誤。這些斷言機制可使用用戶自定義的結構,可是必須進行運算符重載 。若是使用了自定義結構,咱們最好使用ASSERT_*(),這樣不只會輸出比較結果,並且會輸出操做數。函數

ASSERT_EQ()會進行指針比較, 若是使用C風格字符串,那麼比較的是地址!!若是要比較值的話,使用ASSERT_STREQ(), 若是判斷C字符串是不是NULL,使用ASSERT_STREQ(NULL, c_string)。 若是比較string,那麼使用ASSERT_EQ

字符串比較:

在這裏,比較的是C風格的字符串,若是想要比較string對象,請使用EXPECT_EQEXPECT_NE等,而不是下面的。

Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ(str1,str2); EXPECT_STREQ(str1,str2); the two C strings have the same content
ASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); the two C strings have different content
ASSERT_STRCASEEQ(str1,str2); EXPECT_STRCASEEQ(str1,str2); the two C strings have the same content, ignoring case
ASSERT_STRCASENE(str1,str2); EXPECT_STRCASENE(str1,str2); the two C strings have different content, ignoring case

簡單的測試實例:

建立一個test:

  1. 使用TEST()宏定義和命名一個特是函數,這是一個普通的無返回值的C++函數。
  2. 在函數內部,能夠包含任何咱們想要添加的C++條件,使用GoogleTest斷言去檢查相關的值。
  3. 測試的結果取決於內部的斷言機制。若是有任何的測試失敗(不論是fatally仍是non-fatally),或者測試中斷,那麼整個測試失敗;不然測試成功。
TEST(testCaseName, testName) {
 ... test body ...
}

testCaseName是 test case的名字,testName是test case內部測試的名稱。二者的名稱必須是合法的C++標識符,不容許有下劃線( _ ) 。不一樣的test case的內部測試能夠有相同的獨立的名字。

int Factorial(int n); // Returns the factorial of n

// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(1, Factorial(0));
}

// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

GoogleTest經過test case組織測試結果,所以邏輯相關的測試必須在一個test case中;換句話說,TEST()的第一個參數必須相同。上面例子的HandlesZeroInputHandlesPositiveInput都屬於FactorialTest這個測試實例。

Test Fixtures: 對不一樣的數據使用相同的測試數據配置

Test Fixture容許咱們使用相同對象配置進行不一樣的測試。
具體步驟:

  1. ::testing::Tes派生一個類。使用是public:或者protected:,由於咱們想要在子類中獲取fixture members
  2. 在派生類的內部,聲明咱們想使用的任何對象
  3. 若是有必要, 經過使用默認構造函數或者SetUp()函數爲每一個測試準備測試對象。
  4. 若是有必要, 使用一個析構函數或者TearDown()函數來釋放構造函數或者SetUp()所申請的資源。 想要理解在何時使用SetUp()TearDown()函數, 閱讀這篇文章.
  5. 若是必要的話,能夠定義子程序,讓測試之間共享。

當使用一個fixture時,應該使用TEST_F()而不是TEST(),由於前者會讓咱們獲取一個test fixture的對象或者子程序。好比:

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

TEST()相似,第一個參數是test case的名字。可是第二參數必須是test fixture類的名字。

C++的宏系統不容許咱們建立一個單獨的宏來處理各類類型的test。這樣作會有編譯錯誤。

對於每一個使用TEST_F()定義的測試,Google Test會:

  1. 在運行期間建立一個新的test fixture
  2. 經過SetUp()馬上進行初始化
  3. 運行test
  4. 經過TearDown()函數清理
  5. 刪除這個test fixture。注意:一個test中的不一樣test擁有不一樣的test fixture對象,Google Test老是在建立下一個test fixture以前刪除當前的test fixture。對於多個test,Google Test不會重複使用相同的test fixture。一個test的任何更改都不會影響其餘的test。

好比,如今編寫一個FIFO隊列的測試,隊列的實現方式:

template <typename E> // E is the element type.
class Queue {
 public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue(); // Returns NULL if the queue is empty.
  size_t size() const;
  ...
};

首先,定義一個fixture類。假設該類的名稱爲Foo, 按照慣例,咱們應該給fixture命名爲FooTest

class QueueTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  // virtual void TearDown() {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

本例子中,沒用TearDown()函數,由於沒有資源須要釋放。

如今 ,使用TEST_F()測試這個fixture:

// 用於測試是不是空隊列
TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(0, q0_.size());
}

// 測試出隊的工做狀態
TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(NULL, n);  // 判斷相等的狀況

  n = q1_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0, q1_.size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1, q2_.size());
  delete n;
}

ASSERT_EXPECT_的區別在前面的文章提到了,這裏不在贅述。

以上面的例子,說明GoogleTest的測試步驟:

  1. Google Test建立一個QueueTest對象, 咱們稱之爲t1
  2. t1.SetUp()初始化t1
  3. t1進行第一個測試IsEmptyInitially
  4. 測試完成後t1.TearDown()清理全部的資源。
  5. t1析構。
  6. 上述的步驟重複的在另外一個QueueTest對象上執行。(DequeueWorks開始執行上述步驟)

創建一個測試:

TEST()TEST_F()會跟隨Google自動進行註冊,若是想要執行,咱們不須要從新列出全部定義的測試。

在定義測試以後,使用RUN_ALL_TEST()。若是測試成功則返回0,不然返回0.,執行這個語句的時候,全部的連接單元都會被測試,它們能夠是不一樣的test case的。
通常步驟:

  1. 保存全部Google Test標誌的狀態。
  2. 爲第一個測試建立一個test feature對象。
  3. 經過SetUp()函數初始化test
  4. fixture test開始在該對象上執行
  5. 測試結束後,經過TearDown()釋放資源
  6. 刪除fixture
  7. 恢復Google Test的全部標誌狀態
  8. 其餘的test重複地執行上述步驟。

注意,若是構造函數在第2步產生了fatal failure,那麼3-5步會自動跳過。一樣的,3產生了fatal failure,第4步跳過。

注意: 咱們必須返回RUN_ALL_TEST()的值,不然gcc會給出編譯錯誤。也就是說,主函數必須返回RUN_ALL_TEST()的值!並且RUN_ALL_TEST()只能執行一次!!

編寫主函數:

Google Test的官方文檔給出了一個測試模板:

#include "this/package/foo.h"
#include "gtest/gtest.h"

namespace {

// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
  protected:
    // You can remove any or all of the following functions if its body
    // is empty.

    FooTest() {
        // You can do set-up work for each test here.
    }

    virtual ~FooTest() {
        // You can do clean-up work that doesn't throw exceptions here.
    }

    // If the constructor and destructor are not enough for setting up
    // and cleaning up each test, you can define the following methods:

    virtual void SetUp() {
        // Code here will be called immediately after the constructor (right
        // before each test).
    }

    virtual void TearDown() {
        // Code here will be called immediately after each test (right
        // before the destructor).
    }

    // Objects declared here can be used by all tests in the test case for Foo.
};

// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
    const string input_filepath = "this/package/testdata/myinputfile.dat";
    const string output_filepath = "this/package/testdata/myoutputfile.dat";
    Foo f;
    EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}

// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
    // Exercises the Xyz feature of Foo.
}

}  // namespace

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

::testing::InitGoogleTest()用於解析Google Test flags的命令行,而且一處全部已經被識別的標誌,具體的用法請參照這個文檔

Visual C++用戶須知!!

由於本人不用vc++,所以各位用VC的dalao就本身看官方文檔吧。。。。。。

測試代碼

以最大子數組線性時間求和問題爲例,介紹GoogleTest的測試框架。給出假期刷題時PAT1007原題目連接,並附上AC的代碼,算法的原理就不在贅述了:

#include <bits/stdc++.h>
using namespace std;
int num[10005], N;

int main() {
    cin >> N;
    bool flag = true;
    for(int i = 0; i < N; ++i) {
        cin >> num[i];
        if(num[i] >= 0) {
            flag = false;
        }
    }
    // 注意sum初始化要小於0
    int a = 0, b = 0, sum = -1, tmp_sum = 0, tmp_a = 0, tmp_b = 0;
    while(tmp_b < N) {
        tmp_sum += num[tmp_b];
        if(tmp_sum > sum) { // 更替區間範圍
            sum = tmp_sum;
            a = tmp_a;
            b = tmp_b;
        }
        if(tmp_sum < 0) {     // 從新開始起點
            tmp_sum = 0;
            tmp_a = tmp_b + 1;
        }
        ++tmp_b;
    }
    if(flag) {
        cout << 0 << " " << num[0] << " " << num[N - 1];
    } else {
        cout << sum << " " << num[a] << " " << num[b];
    }
    return 0;
}

可是,上述代碼很差直接測試,所以,把核心功能分離出來寫成函數,同時自定義結構體做爲函數的返回值。改進後的代碼以下:

實際可能出現的狀況有下面幾種:

  1. 全是負數:左右標記分別是區間範圍,子數組之和輸出0
  2. 有最大子數組,且最大子數組惟一:輸出最大子數組的範圍和全部元素之和
  3. 有多個最大子數組:只輸出第一個最大子數組,格式同2

本次測試代碼內容較少,就當作是熟悉環境的練習了。。。。。。。。。。。。。。。

代碼的Github地址:https://github.com/StudentErick/software_ware_homework

#include <gtest/gtest.h>

class Node {
  public:
    Node(): sum(0), l(0), r(0) {} // 初始化
    int sum, l, r;   // 區間和、左右範圍,從0開始
};

Node maxFun(int arr[], int N, bool flag) {
    Node node;
    int a = 0, b = 0, sum = -1, tmp_sum = 0, tmp_a = 0, tmp_b = 0;
    while(tmp_b < N) {
        tmp_sum += arr[tmp_b];
        if(tmp_sum > sum) { // 更替區間範圍
            sum = tmp_sum;
            a = tmp_a;
            b = tmp_b;
        }
        if(tmp_sum < 0) {     // 從新開始起點
            tmp_sum = 0;
            tmp_a = tmp_b + 1;
        }
        ++tmp_b;
    }
    if(flag) {
        node.l = 0;
        node.r = 9;
        node.sum = 0;
    } else {
        node.l = a;
        node.r = b;
        node.sum = sum;
    }

    return node;
}

// 須要全局重載
bool operator==(Node a, Node b) {
    return a.sum == b.sum && a.l == b.l && a.r == b.r;
}

// 全是負數的測試狀況
int num1[10] = { -1, -2, -5, -2, -8, -6, -9, -3, -10, -4};
// 只有一個最大子數組,左右範圍是2 7,和是25
int num2[10] = {1, -14, 5, 6, 8, 3, -1, 4, -10, 4};
// 有多個,在這裏用兩個測試,左右範圍應該是2 4 和是19
int num3[10] = {1, -14, 5, 6, 8, -300, 5, 6, 8, -4};

TEST(MYTEST, IsOk) {
    Node n1, n2, n3;
    n1.l = 0;
    n1.r = 9;
    n1.sum = 0;
    n2.l = 2;
    n2.r = 7;
    n2.sum = 25;
    n3.l = 2;
    n3.r = 4;
    n3.sum = 19;


    ASSERT_EQ(n1, maxFun(num1, 10, true));
    ASSERT_EQ(n2, maxFun(num2, 10, false));
    ASSERT_EQ(n3, maxFun(num3, 10, false));
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

因爲是自定義的返回值,因此根據官方文檔的建議,使用ASSERT_*。測試經過結果以下:

假設更改n3l屬性爲4,那麼測試失敗的結果以下:

這裏寫圖片描述
其中,有相應的不匹配提示。因爲本測試比較簡單,因此沒有使用到test fixture的技術。若是須要,直接套用模板便可。更高級的功能能夠參考

一些感想:

本次測試僅經過官方文檔進行學習。官方的參考文檔是最佳的參考資料。尤爲是對於咱們不熟悉的技術領域,更應該經過閱讀有關文檔進行學習。英文應該做爲技術開發人員的一項基本能力,不只僅是爲了考研或者是所謂的四六級。不少最新的資料或者比較高端的技術或者一些頂級期刊的論文等,幾乎沒有中文版的,所以咱們更應該不斷提升本身的英文水平。同時,要懷着積極的心態去擁抱新的技術和變化,善於利用工具提升開發或者測試效率。

相關文章
相關標籤/搜索