Google單元測試框架gtest之官方sample筆記1--簡單用例

1.0 通用部分

和常見的測試工具同樣,gtest提供了單體測試常見的工具和組件。好比判斷各類類型的值相等,大於,小於等,管理多個測試的測試組如testsuit下轄testcase,爲了方便處理初始化數據減小重複代碼,提供了setup和teardown函數。編程

官方文檔稱:TEST has two parameters: the test case name and the test name. 第一個是case名稱,第二個是test名稱,這是google的名詞稱呼方法,其實就是通常意義上的testsuit和testcase,前者是組,後者是測試用例,爲了方便測試管理,把幾個相關的測試放到一塊兒統稱爲一個測試組。這是一個編程約定,但若是把不相干的測試放到一個test_case_name下,也不會報錯,只是作測試總結和彙總的時候不方便而已。ide

# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)

關於TEST宏,這是一個一層包一層的宏定義,雖然google編程規範說不建議用宏,可是gtest卻大量的使用宏來建立類和函數,使用宏能夠給用戶更加簡潔的接口,在效率上也有優點,可是讀起來很晦澀。通常的,在基礎工具和底層API中,宏仍是由廣大的應用空間,由於這一部分基本上不怎麼變化,那些寫底層工具的大牛們有能力駕馭這種反常規的寫法。函數

使用宏來實現底層的重複性工做或者封裝複雜的接口,在開源項目中是很常見的方式。工具

  • Sample #1 shows the basic steps of using googletest to test C++ functions.測試

  • Sample #2 shows a more complex unit test for a class with multiple member functions.ui

  • Sample #3 uses a test fixture.this

  • Sample #4 teaches you how to use googletest and googletest.h together to get the best of both libraries.google

  • Sample #5 puts shared testing logic in a base test fixture, and reuses it in derived fixtures.lua

  • Sample #6 demonstrates type-parameterized tests.spa

  • Sample #7 teaches the basics of value-parameterized tests.

  • Sample #8 shows using Combine() in value-parameterized tests.

  • Sample #9 shows use of the listener API to modify Google Test's console output and the use of its reflection API to inspect test results.

  • Sample #10 shows use of the listener API to implement a primitive memory leak checker.

 

1.1 sample1

官方sample1有2個函數,階乘函數int Factorial()和判斷素數函數bool IsPrime(int n)。

測試用例分爲2個testsuit。

FactorialTest包含3個階乘函數的測試用例:

  1. Negative: 輸入負數測試階乘

  2. Zero:輸入爲0測試階乘

  3. Positive:輸入爲正數測試階乘

TEST(FactorialTest, Negative) {
  // This test is named "Negative", and belongs to the "FactorialTest"
  // test case.
  EXPECT_EQ(1, Factorial(-5));
  EXPECT_EQ(1, Factorial(-10));
  EXPECT_GT(Factorial(-10), 0);
}
​
// Tests factorial of 0.
TEST(FactorialTest, Zero) { EXPECT_EQ(1, Factorial(0)); }
​
// Tests factorial of positive numbers.
TEST(FactorialTest, Positive) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

IsPrimeTest包含3個素數檢測函數的測試用例:

  1. Negative:輸入爲負數和極限值INT_MIN

  2. Trivial:輸入爲幾個特殊的值如臨界點的數

  3. Positive:輸入爲正數

// Tests negative input.
TEST(IsPrimeTest, Negative) {
  // This test belongs to the IsPrimeTest test case.
​
  EXPECT_FALSE(IsPrime(-1));
  EXPECT_FALSE(IsPrime(-2));
  EXPECT_FALSE(IsPrime(INT_MIN));
}
​
// Tests some trivial cases.
TEST(IsPrimeTest, Trivial) {
  EXPECT_FALSE(IsPrime(0));
  EXPECT_FALSE(IsPrime(1));
  EXPECT_TRUE(IsPrime(2));
  EXPECT_TRUE(IsPrime(3));
}
​
// Tests positive input.
TEST(IsPrimeTest, Positive) {
  EXPECT_FALSE(IsPrime(4));
  EXPECT_TRUE(IsPrime(5));
  EXPECT_FALSE(IsPrime(6));
  EXPECT_TRUE(IsPrime(23));
}

gtest直接運行便可,代碼中沒有main函數也能夠執行。輸出結果提示使用了gtest_main.cc函數。

輸出顯示來自2個testcase的6個用例被執行。兩個case就是測試組FactorialTest和IsPrimeTest。

能夠加上本身的main函數,調用RUN_ALL_TESTS()執行測試用例。

int main(int argc, char* argv[]) {
  cout << "start gtest demo \r\n" << endl;
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

 

1.2 sample2

官方sample 2,測試一個名爲MyString的類,包括測試構造函數和成員函數。

該類有以下特徵:默認構造函數把成員變量c_string指針初始化爲nullptr,構造函數MyString接收一個char *字符串而後經過Set函數拷貝給c_string_。

class MyString {
 private:
  const char* c_string_;
  const MyString& operator=(const MyString& rhs);
​
 public:
  // Clones a 0-terminated C string, allocating memory using new.
  // 類方法
  static const char* CloneCString(const char* a_c_string);
​
  // The default c'tor constructs a NULL string. 
  // 默認構造函數
  MyString() : c_string_(nullptr) {}
​
  // Constructs a MyString by cloning a 0-terminated C string. 
  // 構造函數,禁止隱式轉換
  explicit MyString(const char* a_c_string) : c_string_(nullptr) {
    Set(a_c_string);
  }
​
  // Copy c'tor
  // 拷貝構造函數
  MyString(const MyString& string) : c_string_(nullptr) {
    Set(string.c_string_);
  }
​
  // D'tor.  MyString is intended to be a final class, so the d'tor
  // doesn't need to be virtual.
  ~MyString() { delete[] c_string_; }
​
  // Gets the 0-terminated C string this MyString object represents.
  const char* c_string() const { return c_string_; }
​
  size_t Length() const { return c_string_ == nullptr ? 0 : strlen(c_string_); }
​
  // Sets the 0-terminated C string this MyString object represents.
  // 成員函數
  void Set(const char* c_string);
};

類方法的實現和Set成員函數的實現。

// Clones a 0-terminated C string, allocating memory using new.
const char* MyString::CloneCString(const char* a_c_string) {
  if (a_c_string == nullptr) return nullptr;
​
  const size_t len = strlen(a_c_string);
  char* const clone = new char[ len + 1 ];
  memcpy(clone, a_c_string, len + 1);
​
  return clone;
}
​
// Sets the 0-terminated C string this MyString object
// represents.
void MyString::Set(const char* a_c_string) {
  // Makes sure this works when c_string == c_string_
  const char* const temp = MyString::CloneCString(a_c_string);
  delete[] c_string_;
  c_string_ = temp;
}

測試用例,構建了一個測試testcase叫作MyString,包含了4個test用例。

第一個用例:TEST(MyString, DefaultConstructor),測試默認構造函數, MyString() : c_string_(nullptr) {}

  const MyString s;
  EXPECT_STREQ(nullptr, s.c_string());
  EXPECT_EQ(0u, s.Length());

第二個用例:TEST(MyString, ConstructorFromCString),測試 MyString(const char* a_c_string) 構造函數,sizeof(kHelloString)-1和 s.Length()相等,是由於這是c類型的字符串,最後結尾是\0,sizeof計算的是分配給這個字符串的空間。

const char kHelloString[] = "Hello, world!";
​
// Tests the c'tor that accepts a C string.
TEST(MyString, ConstructorFromCString) {
  const MyString s(kHelloString);
  EXPECT_EQ(0, strcmp(s.c_string(), kHelloString));
  EXPECT_EQ(sizeof(kHelloString)/sizeof(kHelloString[0]) - 1,
            s.Length());
}

第三個用例:TEST(MyString, CopyConstructor),測試拷貝構造函數。

// Tests the copy c'tor.
TEST(MyString, CopyConstructor) {
  const MyString s1(kHelloString);
  const MyString s2 = s1;
  EXPECT_EQ(0, strcmp(s2.c_string(), kHelloString));
}

第四個用例:TEST(MyString, Set) ,測試Set成員函數。

// Tests the Set method.
TEST(MyString, Set) {
  MyString s;
​
  s.Set(kHelloString);
  EXPECT_EQ(0, strcmp(s.c_string(), kHelloString));
​
  // Set should work when the input pointer is the same as the one
  // already in the MyString object.
  s.Set(s.c_string());
  EXPECT_EQ(0, strcmp(s.c_string(), kHelloString));
​
  // Can we set the MyString to NULL?
  s.Set(nullptr);
  EXPECT_STREQ(nullptr, s.c_string());
}

最終運行結果以下圖,1個test case,4個tests,所有成功。

 

 

1.3 sample3

官方sample 3,展現了測試夾具的概念,爲測試準備環境,每一個test case都使用相同的環境初始化數據等。sample 3測試了一個本身編寫的Queue模板類,這個Q實現了一個單向的鏈表。元素項使用template <typename E> class QueueNode 實現,內部有友元類Queue<E>。隊列Queue類具備默認構造函數和如下成員:

  • SIze() --大小

  • Head() --隊列頭

  • Last() --隊列尾

  • void Enqueue(const E& element) --入隊

  • E* Dequeue() --出隊,返回出隊的元素

  • Queue* Map(F function) const -- 實現隊列拷貝,而且對元素執行function操做,好比測試中就對元素乘以2倍入隊,返回新的隊列的每一個元素都是舊隊列元素的二倍大小。

sample3 例子展現了「test fixture」(測試夾具)的概念,「test fixture」就是實現測試前準備,好比創造一系列共用的函數和數據,每一個測試case運行前均可以引用這些共有的條件。最多見的就是初始化Setup或善後處理TearDown函數,因此使用「test fixture」能夠避免重複的代碼。

test fixture 怎麼寫公共部分

  1. test fixture的類名沒有限制,能夠按照測試需求起名字,這個類須要繼承testing::Test類,class QueueTestSmpl3 : public testing::Test ,在測試夾具類中重寫SetUp和TearDown方法。

  2. 若是使用了測試夾具,那麼測試用例名就不能使用TEST來建立,而是使用TEST_F來建立,在TEST_F宏的第一個參數裏,寫測試夾具類名。

  3. 測試啓動後,每一個測試case執行前都會運行測試夾具類。達到一個準備測試環境的目的。

例如在測試夾具中加入一句打印:

  void SetUp() override {
    std::cout << "this test fixture" << std::endl;
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

三個測試用例,會調用setup3次。

每一個TEST_F都會建立一個類,而且繼承test fixture類。例如TEST_F(QueueTestSmpl3, DefaultConstructor) 會被擴展爲:

class QueueTestSampl3_DefaultConstructor_Test:public QueueTestSmpl3 {}

因此每一個TEST_F運行時,都會調用一次QueueTestSmpl3類。

下面分析下第二個用例:

// Tests Dequeue().
TEST_F(QueueTestSmpl3, Dequeue) {
  int * n = q0_.Dequeue(); // q0 隊列沒有任何元素,setup沒有設置q0,出隊只會是nullptr
  EXPECT_TRUE(n == nullptr);
​
  n = q1_.Dequeue(); // q1 對列有一個元素:1
  ASSERT_TRUE(n != nullptr);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0u, q1_.Size()); // 出隊後,q1隊列沒有元素了
  delete n;
​
  n = q2_.Dequeue(); // q2在setup時候輸入了2,3兩個元素
  ASSERT_TRUE(n != nullptr);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1u, q2_.Size());
  delete n;
}

 

1.4 sample4

官方sample 4測試了一個Counter類,該類實現了Increment和Decrement兩個函數,一個int類型數值自增,一個自減,值爲0時再也不減直接返回0。

TEST(Counter, Increment) {
  Counter c;
​
  // Test that counter 0 returns 0
  EXPECT_EQ(0, c.Decrement());
​
  // EXPECT_EQ() evaluates its arguments exactly once, so they
  // can have side effects.
  EXPECT_EQ(0, c.Increment());
  EXPECT_EQ(1, c.Increment());
  EXPECT_EQ(2, c.Increment());
​
  EXPECT_EQ(3, c.Decrement());
}

測試很簡單,注意的是第一次c.Increment()調用後,依然爲0,是因爲Incremen函數先返回值寫入臨時變量,而後再執行++操做。3個加執行完,c.counter_ = 3,減方法返回3,而後c.counter_=2.

int Counter::Increment() {
  return counter_++;
}

 

1.5 sample5

sample 3展現了測試夾具的概念,能夠方便的爲每一個測試用例建立共用的部分,好比準備測試環境和數據。可是若是多個測試須要的環境相似,只有細小的差異,那麼就能夠把共用的部分抽出來放到基類--建立一個超級的test fixture,而各自的不一樣的測試夾具用繼承來實現個性化--派生出各自的test fixture。

sample 5先建立了一個超級測試夾具,類名叫QuickTest,繼承testing::Test類,QuickTest計算每一個測試case的執行時間,方式很簡單,SetUp裏記錄start_time,TearDown裏記錄end_time,相減就是執行時間。若是故意在test中Sleep(6),則會超時報錯顯示以下:

測試用例以下,TEST_F使用IntegerFunctionTest類做爲test_fixture名字,而IntegerFunctionTest類繼承於QuickTest,因此也能夠計算時間。

class IntegerFunctionTest : public QuickTest {
  // We don't need any more logic than already in the QuickTest fixture.
  // Therefore the body is empty.
};
TEST_F(IntegerFunctionTest, Factorial) {
    // **** 階乘函數的tests
}
​
TEST_F(IntegerFunctionTest, IsPrime) {
     // **** 判斷素數函數的tests
}

第二個測試case展現了共用測試夾具的方法,如sample 3中,測試Queue時候,須要初始化隊列,那麼能夠在這個test fixture類中初始化對列,而且繼承於QuickTest類,那麼測試case運行時候就能夠執行統計執行時間的功能。

class QueueTest : public QuickTest {
 protected:
  void SetUp() override {
    // First, we need to set up the super fixture (QuickTest).
    QuickTest::SetUp();
​
    // Second, some additional setup for this fixture.
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }
​
  // By default, TearDown() inherits the behavior of
  // QuickTest::TearDown().  As we have no additional cleaning work
  // for QueueTest, we omit it here.
  //
  // virtual void TearDown() {
  //   QuickTest::TearDown();
  // }
​
  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

測試case部分和sample 3同樣,可是因爲test fixture類繼承了QuickTest,這個測試用例能夠統計執行時間,而且執行TearDown函數裏面的超時報錯。

最終的執行結果以下圖。共兩個test case:IntegerFunctionTest和QueueTest,每一個case有2個tests。

相關文章
相關標籤/搜索