玩轉Google開源C++單元測試架構Google Test系列(gtest)之四 – 參數化

來源:互聯網
上載者:User
一、前言

在設計測試案例時,經常需要考慮給被測函數傳入不同的值的情況。我們之前的做法通常是寫一個通用方法,然後編寫在測試案例調用它。即使使用了通用方法,這樣的工作也是有很多重複性的,程式員都懶,都希望能夠少寫代碼,多複用代碼。Google的程式員也一樣,他們考慮到了這個問題,並且提供了一個靈活的參數化測試的方案。

二、舊的方案

為了對比,我還是把舊的方案提一下。首先我先把被測函數IsPrime帖過來(在gtest的example1.cc中),這個函數是用來判斷傳入的數值是否為質數的。

// Returns true iff n is a prime number.
bool IsPrime(int n)
{
    // Trivial case 1: small numbers
    if (n <= 1) return false;

    // Trivial case 2: even numbers
    if (n % 2 == 0) return n == 2;

    // Now, we have that n is odd and n >= 3.

    // Try to divide n by every odd number i, starting from 3
    for (int i = 3; ; i += 2) {
        // We only have to try i up to the squre root of n
        if (i > n/i) break;

        // Now, we have i <= n/i < n.
        // If n is divisible by i, n is not prime.
        if (n % i == 0) return false;
    }
    // n has no integer factor in the range (1, n), and thus is prime.
    return true;
}

 

假如我要編寫判斷結果為True的測試案例,我需要傳入一系列數值讓函數IsPrime去判斷是否為True(當然,即使傳入再多值也無法確保函數正確,呵呵),因此我需要這樣編寫如下的測試案例:

TEST(IsPrimeTest, HandleTrueReturn)
{
    EXPECT_TRUE(IsPrime(3));
    EXPECT_TRUE(IsPrime(5));
    EXPECT_TRUE(IsPrime(11));
    EXPECT_TRUE(IsPrime(23));
    EXPECT_TRUE(IsPrime(17));
}

 

我們注意到,在這個測試案例中,我至少複製粘貼了4次,假如參數有50個,100個,怎麼辦?同時,上面的寫法產生的是1個測試案例,裡面有5個檢查點,假如我要把5個檢查變成5個單獨的案例,將會更加累人。

接下來,就來看看gtest是如何為我們解決這些問題的。

三、使用參數化後的方案

1. 告訴gtest你的參數類型是什麼

你必須添加一個類,繼承testing::TestWithParam<T>,其中T就是你需要參數化的參數類型,比如上面的例子,我需要參數化一個int型的參數

class IsPrimeParamTest : public::testing::TestWithParam<int>
{

};

 

2. 告訴gtest你拿到參數的值後,具體做些什麼樣的測試

這裡,我們要使用一個新的宏(嗯,挺興奮的):TEST_P,關於這個"P"的含義,Google給出的答案非常幽默,就是說你可以理解為”parameterized" 或者 "pattern"。我更傾向於 ”parameterized"的解釋,呵呵。在TEST_P宏裡,使用GetParam()擷取當前的參數的具體值。

TEST_P(IsPrimeParamTest, HandleTrueReturn)
{
    int n =  GetParam();
    EXPECT_TRUE(IsPrime(n));
}

 

嗯,非常的簡潔!

3. 告訴gtest你想要測試的參數範圍是什麼

 使用INSTANTIATE_TEST_CASE_P這宏來告訴gtest你要測試的參數範圍:

INSTANTIATE_TEST_CASE_P(TrueReturn, IsPrimeParamTest, testing::Values(3, 5, 11, 23, 17));

 

第一個參數是測試案例的首碼,可以任意取。

第二個參數是測試案例的名稱,需要和之前定義的參數化類別的名稱相同,如:IsPrimeParamTest

第三個參數是可以理解為參數產生器,上面的例子使用test::Values表示使用括弧內的參數。Google提供了一系列的參數產生的函數:

Range(begin, end[, step]) 範圍在begin~end之間,步長為step,不包括end
Values(v1, v2, ..., vN) v1,v2到vN的值
ValuesIn(container) and ValuesIn(begin, end) 從一個C類型的數組或是STL容器,或是迭代器中取值
Bool() 取false 和 true 兩個值
Combine(g1, g2, ..., gN)

這個比較強悍,它將g1,g2,...gN進行排列組合,g1,g2,...gN本身是一個參數產生器,每次分別從g1,g2,..gN中各取出一個值,組合成一個元組(Tuple)作為一個參數。

說明:這個功能只在提供了<tr1/tuple>頭的系統中有效。gtest會自動去判斷是否支援tr/tuple,如果你的系統確實支援,而gtest判斷錯誤的話,你可以重新定義宏GTEST_HAS_TR1_TUPLE=1。

 

四、參數化後的測試案例名

因為使用了參數化的方式執行案例,我非常想知道運行案例時,每個案例名稱是如何命名的。我執行了上面的代碼,輸出如下:

從上面的框框中的案例名稱大概能夠看出案例的命名規則,對於需要瞭解每個案例的名稱的我來說,這非常重要。 命名規則大概為:

prefix/test_case_name.test.name/index

五、型別參數化

gtest還提供了應付各種不同類型的資料時的方案,以及參數化型別的方案。我個人感覺這個方案有些複雜。首先要瞭解一下類型化測試,就用gtest裡的例子了。

首先定義一個模版類,繼承testing::Test:

template <typename T>
class FooTest : public testing::Test {
 public:
  
  typedef std::list<T> List;
  static T shared_;
  T value_;
};

 

接著我們定義需要測試到的具體資料類型,比如下面定義了需要測試char,int和unsigned int :

typedef testing::Types<char, int, unsigned int> MyTypes;
TYPED_TEST_CASE(FooTest, MyTypes);

 

又是一個新的宏,來完成我們的測試案例,在聲明模版的資料類型時,使用TypeParam

TYPED_TEST(FooTest, DoesBlah) {
  // Inside a test, refer to the special name TypeParam to get the type
  // parameter.  Since we are inside a derived class template, C++ requires
  // us to visit the members of FooTest via 'this'.
  TypeParam n = this->value_;

  // To visit static members of the fixture, add the 'TestFixture::'
  // prefix.
  n += TestFixture::shared_;

  // To refer to typedefs in the fixture, add the 'typename TestFixture::'
  // prefix.  The 'typename' is required to satisfy the compiler.
  typename TestFixture::List values;
  values.push_back(n);
  
}

上面的例子看上去也像是類型的參數化,但是還不夠靈活,因為需要事Crowdsourced Security Testing道類型的列表。gtest還提供一種更加靈活的型別參數化的方式,允許你在完成測試的邏輯代碼之後再去考慮需要參數化類別型列表,並且還可以重複的使用這個類型列表。下面也是官方的例子:

template <typename T>
class FooTest : public testing::Test {
  
};

TYPED_TEST_CASE_P(FooTest);

 

接著又是一個新的宏TYPED_TEST_P類完成我們的測試案例:

TYPED_TEST_P(FooTest, DoesBlah) {
  // Inside a test, refer to TypeParam to get the type parameter.
  TypeParam n = 0;
  
}

TYPED_TEST_P(FooTest, HasPropertyA) {  }

接著,我們需要我們上面的案例,使用REGISTER_TYPED_TEST_CASE_P宏,第一個參數是testcase的名稱,後面的參數是test的名稱

REGISTER_TYPED_TEST_CASE_P(FooTest, DoesBlah, HasPropertyA);

接著指定需要的類型列表:

typedef testing::Types<char, int, unsigned int> MyTypes;
INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes);

這種方案相比之前的方案提供更加好的靈活度,當然,架構越靈活,複雜度也會隨之增加。

六、總結

gtest為我們提供的參數化測試的功能給我們的測試帶來了極大的方便,使得我們可以寫更少更優美的代碼,完成多種參數類型的測試案例。

系列連結:

1.玩轉Google開源C++單元測試架構Google Test系列(gtest)之一 - 初識gtest

2.玩轉Google開源C++單元測試架構Google Test系列(gtest)之二 - 斷言

3.玩轉Google開源C++單元測試架構Google Test系列(gtest)之三 - 事件機制

4.玩轉Google開源C++單元測試架構Google Test系列(gtest)之四 - 參數化

5.玩轉Google開源C++單元測試架構Google Test系列(gtest)之五 - 死亡測試

6.玩轉Google開源C++單元測試架構Google Test系列(gtest)之六 - 運行參數

7.玩轉Google開源C++單元測試架構Google Test系列(gtest)之七 - 深入解析gtest

8.玩轉Google開源C++單元測試架構Google Test系列(gtest)之八 - 打造自己的單元測試架構

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.