ユニットテスト中のgtest and googlemock

17362 ワード

ユニットテストの概要


テストはテストエンジニアの責任だけではなく、開発エンジニアにとって、テストの一環に発表されたコードが十分な品質(Quality)を持つことを保証するために、作成した機能コードのために適量のユニットテストを作成する必要がある.
ユニットテスト(Unit Test,モジュールテスト)は、開発者が作成した小さなコードであり、被測定コードの小さな明確な機能が正しいかどうかを検証するために使用され、ユニットテストを作成することで、符号化段階でプログラム符号化エラー、さらにはプログラム設計エラーを発見することができる.
ユニットテストは開発者の完成したコードに対する自信を高めることができるだけでなく、同時に、良いユニットテストの例は往々にしてテストに戻る過程で、前に発生した修正が既存のプログラムロジックを破壊しないことをよく保証することができる.従って,ユニットテストは開発者の負担にならないだけでなく,開発品質を保証する場合に反復開発の過程を加速させることができる.
ユニットテストフレームワークについて、現在最もよく知られているのはJUnitと各言語に対する派生製品であり、C++言語に対応するJUnit系ユニットテストフレームワークはCppUnitである.しかし、CppUnitの設計はJUnitから厳格に継承されているため、C++とJava固有の違いを十分に考慮していない(主にC++に反射メカニズムがないため、これはJUnit設計の基礎である)、C++でCppUnitを用いてユニットテストを行うのは煩雑であり、これはある程度CppUnitの普及を制約している.筆者がここで紹介するのは、Googleが発表したオープンソースユニットテストフレームワーク(Testing Framework):googletestです.
トップに戻る

Googletestを応用してユニットテストコードを作成する


GoogletestはGoogle社が発表し、New BSD License(ビジネス用途として使用可能)のオープンソースプロジェクトに従い、googletestはほとんどのよく知られているプラットフォームをサポートすることができます.CppUnitとは異なり、googletestは定義されたすべてのテストを自動的に記録することができ、ユーザーが列挙してどのテストが実行される必要があるかを指定する必要はありません.

ユニットテストの定義


Googletest作成ユニットテストを適用する場合、TEST()マクロを使用してテスト関数を宣言します.次のようになります.
リスト1.TEST()マクロでテスト関数を宣言する
TEST(GlobalConfigurationTest, configurationDataTest) 
 TEST(GlobalConfigurationTest, noConfigureFileTest)

同じプログラムユニットGlobalConfigurationに対して2つの異なるテスト(Test)関数を宣言し、構成データをそれぞれチェック(configurationDataTest)、および構成ファイルのない特別な状況(noConfigureFileTest)をテストします.

ユニットテストの実装


同じプログラムユニットに対して異なるテストシーンを設計した後(すなわち異なるTestを区分した後)、開発者はユニットテストを作成してそれぞれこれらのテストシーンを実現することができる.
Googletestでユニットテストを実現し、ASSERT_*を通過できる.とEXPECT_*プログラムの実行結果をチェックするために断言します.ASSERT_* バージョンの断言に失敗した場合、致命的な失敗が発生し、現在の関数を終了します.EXPECT_* バージョンのアサーションに失敗した場合、致命的でない失敗が発生しますが、現在の関数は中止されません.したがって、ASSERT_*オブジェクトの作成後にポインタが空であるかどうかを確認し、空である場合、後続のオブジェクトメソッド呼び出しが失敗するなど、後続のテストロジックに依存する処理結果の断言によく使用されます.EXPECT_*失敗しても後続のテストロジックの処理結果に影響しないという断言に使用され、あるメソッドが結果を返す複数の属性のチェックなどが使用されます.
Googletestでは、次のような断言が定義されています.
表1:googletest定義のアサート(Assert)
基本的な断言
バイナリ比較
文字列の比較
ASSERT_TRUE(condition); EXPECT_TRUE(condition); conditionが真ASSERT_FALSE(condition); EXPECT_FALSE(condition); 条件が偽りである.
ASSERT_EQ(expected,actual); EXPECT_EQ(expected,actual); expected==actual ASSERT_NE(val1,val2); EXPECT_NE(val1,val2); val1!=val2 ASSERT_LT(val1,val2); EXPECT_LT(val1,val2); val1val2 ASSERT_GE(val1,val2); EXPECT_GE(val1,val2); val1>=val2
ASSERT_STREQ(expected_str,actual_str); EXPECT_STREQ(expected_str,actual_str); 2つのC文字列は同じ内容であるASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); 2つのC文字列に異なる内容があるASSERT_STRCASEEQ(expected_str,actual_str); EXPECT_STRCASEEQ(expected_str,actual_str); 2つのC文字列は同じ内容で、大文字と小文字を無視してASSERT_STRCASENE(str1,str2); EXPECT_STRCASENE(str1,str2); 2つのC文字列には異なる内容があり、大文字と小文字は無視されます.
次の例では、上記の部分の断言の使用を示します.
リスト2.より完全なgoogletestユニットテストの例
// Configure.h 
 #pragma once 

 #include <string> 
 #include <vector> 

 class Configure 
 { 
 private: 
    std::vector<std::string> vItems; 

 public: 
    int addItem(std::string str); 

    std::string getItem(int index); 

    int getSize(); 
 }; 

 // Configure.cpp 
 #include "Configure.h" 

 #include <algorithm> 

 /** 
 * @brief Add an item to configuration store. Duplicate item will be ignored 
 * @param str item to be stored 
 * @return the index of added configuration item 
 */ 
 int Configure::addItem(std::string str) 
 { 
std::vector<std::string>::const_iterator vi=std::find(vItems.begin(), vItems.end(), str); 
    if (vi != vItems.end()) 
        return vi - vItems.begin(); 

    vItems.push_back(str); 
    return vItems.size() - 1; 
 } 

 /** 
 * @brief Return the configure item at specified index. 
 * If the index is out of range, "" will be returned 
 * @param index the index of item 
 * @return the item at specified index 
 */ 
 std::string Configure::getItem(int index) 
 { 
    if (index >= vItems.size()) 
        return ""; 
    else 
        return vItems.at(index); 
 } 

 /// Retrieve the information about how many configuration items we have had 
 int Configure::getSize() 
 { 
    return vItems.size(); 
 } 

 // ConfigureTest.cpp 
 #include <gtest/gtest.h> 

 #include "Configure.h" 

 TEST(ConfigureTest, addItem) 
 { 
    // do some initialization 
    Configure* pc = new Configure(); 
    
    // validate the pointer is not null 
    ASSERT_TRUE(pc != NULL); 

    // call the method we want to test 
    pc->addItem("A"); 
    pc->addItem("B"); 
    pc->addItem("A"); 

    // validate the result after operation 
    EXPECT_EQ(pc->getSize(), 2); 
    EXPECT_STREQ(pc->getItem(0).c_str(), "A"); 
    EXPECT_STREQ(pc->getItem(1).c_str(), "B"); 
    EXPECT_STREQ(pc->getItem(10).c_str(), ""); 

    delete pc; 
 }

ユニットテストの実行


ユニットテストのテストロジックを実現した後、RUN_ALL_TESTS()は、すべてのテストが成功した場合、関数は0を返し、そうでない場合は1を返します.RUN_ALL_TESTS()は、リンクされたすべてのテストを実行します.これらのテストは、異なるテストケース、さらには異なるファイルから実行できます.
したがって、googletestが作成したユニットテストを実行する比較的簡単な方法は、次のとおりです.
各テストされたclassに対してそれぞれ1つのテストファイルを作成し、このclassに対するユニットテストをこのファイルに記述する.
Mainを作成します.cppファイルには、次のコードが含まれており、すべてのユニットテストを実行します:リスト3.Googletestを初期化し、すべてのテストを実行
#include <gtest/gtest.h> 

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

    // Runs all tests using Google Test. 
    return RUN_ALL_TESTS(); 
 }

最後に、すべてのテストコードとMain.cppはターゲットプログラムにコンパイルされリンクされる.
また、実行可能ターゲットプログラムを実行する場合は、--gtest_を使用できます.フィルタは、実行するテストの例を指定します.たとえば、次のようにします../foo_test filter , ; ./foo_test --gtest_filter=* filter * , ; ./foo_test --gtest_filter=FooTest.* FooTest ./foo_test --gtest_filter=*Null*:*Constructor* ( + “ . ” + , GlobalConfigurationTest.noConfigureFileTest "Null" "Constructor" ./foo_test --gtest_filter=FooTest.*-FooTest.Bar FooTest FooTest.Bar

Googlemockを してMock Objectsを する

C++ Mock Objects ( ) , 。 , “ ” “ ” , 。 , , 、 、 、 , , , , , 。 : , “ ” 。 , 。 Mocking Framework , Google 2008 : googlemock 。 googletest , googlemock New BSD License ( ) , googlemock 。 1: Windows googlemock Linux , googlemock ; Windows , Visual Studio tuple ( C++0x TR1 ) , googlemock tuple 。 C++ boost tr1 , , Windows boost googlemock 。 , %GMOCK_DIR%/msvc/gmock_config.vsprops , BoostDir boost , :
<UserMacro Name="BoostDir" Value="$(BOOST_DIR)" />
BOOST_DIR , boost 。 boost , googlemock boost tr1 , boost %GMOCK_DIR% ( , ; boost )。 googlemock Mock , : Mock Class ( class MockTurtle ), Mock ( class Turtle ); Mock virtual , n ; Mock Class , MOCK_METHODn() ( const MOCK_CONST_METHODn() ) Mock , Mock , Mock 。 : 4. MOCK_METHODn Mock
#include <gmock/gmock.h> // Brings in Google Mock. class MockTurtle : public Turtle { MOCK_METHOD0(PenUp, void()); MOCK_METHOD0(PenDown, void()); MOCK_METHOD1(Forward, void(int distance)); MOCK_METHOD1(Turn, void(int degrees)); MOCK_METHOD2(GoTo, void(int x, int y)); MOCK_CONST_METHOD0(GetX, int()); MOCK_CONST_METHOD0(GetY, int()); };
, 。 , ON_CALL Mock , EXPECT_CALL Mock 、 , 。 : 5. ON_CALL EXPECT_CALL
using testing::Return; // #1,  TEST(BarTest, DoesThis) { MockFoo foo; // #2,  Mock   ON_CALL(foo, GetSize()) // #3,  Mock  ( ) .WillByDefault(Return(1)); // ... other default actions ... EXPECT_CALL(foo, Describe(5)) // #4,  .Times(3) .WillRepeatedly(Return("Category 5")); // ... other expectations ... EXPECT_EQ("good", MyProductionFunction(&foo)); // #5,  Mock   googletest   } // #6,  Mock  , googlemock  
, WillByDefault Mock ; Return ; Times ; WillRepeatedly 。 EXPECT_CALL , EXPECT_CALL Mock , googlemock 。 , , Adapter NiceMock 。 : 6. NiceMock
testing::NiceMock<MockFoo> nice_foo;
, , , , NiceMock Mock ( : Mock , Mock abstract class )。 googletest , , : 7. googlemock
#include <gtest/gtest.h> #include <gmock/gmock.h> int main(int argc, char** argv) { testing::InitGoogleMock(&argc, argv); // Runs all tests using Google Test. return RUN_ALL_TESTS(); }
googlemock Mock Objects , AccountService transfer ( ) 。 AccountManager , , , , MockAccountManager AccountManager 。 8.
// Account.h // basic application data class #pragma once #include <string> class Account { private: std::string accountId; long balance; public: Account(); Account(const std::string& accountId, long initialBalance); void debit(long amount); void credit(long amount); long getBalance() const; std::string getAccountId() const; }; // Account.cpp #include "Account.h" Account::Account() { } Account::Account(const std::string& accountId, long initialBalance) { this->accountId = accountId; this->balance = initialBalance; } void Account::debit(long amount) { this->balance -= amount; } void Account::credit(long amount) { this->balance += amount; } long Account::getBalance() const { return this->balance; } std::string Account::getAccountId() const { return accountId; } // AccountManager.h // the interface of external services which should be mocked #pragma once #include <string> #include "Account.h" class AccountManager { public: virtual Account findAccountForUser(const std::string& userId) = 0; virtual void updateAccount(const Account& account) = 0; }; // AccountService.h // the class to be tested #pragma once #include <string> #include "Account.h" #include "AccountManager.h" class AccountService { private: AccountManager* pAccountManager; public: AccountService(); void setAccountManager(AccountManager* pManager); void transfer(const std::string& senderId, const std::string& beneficiaryId, long amount); }; // AccountService.cpp #include "AccountService.h" AccountService::AccountService() { this->pAccountManager = NULL; } void AccountService::setAccountManager(AccountManager* pManager) { this->pAccountManager = pManager; } void AccountService::transfer(const std::string& senderId, const std::string& beneficiaryId, long amount) { Account sender = this->pAccountManager->findAccountForUser(senderId); Account beneficiary = this->pAccountManager->findAccountForUser(beneficiaryId); sender.debit(amount); beneficiary.credit(amount); this->pAccountManager->updateAccount(sender); this->pAccountManager->updateAccount(beneficiary); }
9.
// AccountServiceTest.cpp // code to test AccountService #include <map> #include <string> #include <gtest/gtest.h> #include <gmock/gmock.h> #include "../Account.h" #include "../AccountService.h" #include "../AccountManager.h" // MockAccountManager, mock AccountManager with googlemock class MockAccountManager : public AccountManager { public: MOCK_METHOD1(findAccountForUser, Account(const std::string&)); MOCK_METHOD1(updateAccount, void(const Account&)); }; // A facility class acts as an external DB class AccountHelper { private: std::map<std::string, Account> mAccount; // an internal map to store all Accounts for test public: AccountHelper(std::map<std::string, Account>& mAccount); void updateAccount(const Account& account); Account findAccountForUser(const std::string& userId); }; AccountHelper::AccountHelper(std::map<std::string, Account>& mAccount) { this->mAccount = mAccount; } void AccountHelper::updateAccount(const Account& account) { this->mAccount[account.getAccountId()] = account; } Account AccountHelper::findAccountForUser(const std::string& userId) { if (this->mAccount.find(userId) != this->mAccount.end()) return this->mAccount[userId]; else return Account(); } // Test case to test AccountService TEST(AccountServiceTest, transferTest) { std::map<std::string, Account> mAccount; mAccount["A"] = Account("A", 3000); mAccount["B"] = Account("B", 2000); AccountHelper helper(mAccount); MockAccountManager* pManager = new MockAccountManager(); // specify the behavior of MockAccountManager // always invoke AccountHelper::findAccountForUser // when AccountManager::findAccountForUser is invoked EXPECT_CALL(*pManager, findAccountForUser(testing::_)).WillRepeatedly( testing::Invoke(&helper, &AccountHelper::findAccountForUser)); // always invoke AccountHelper::updateAccount //when AccountManager::updateAccount is invoked EXPECT_CALL(*pManager, updateAccount(testing::_)).WillRepeatedly( testing::Invoke(&helper, &AccountHelper::updateAccount)); AccountService as; // inject the MockAccountManager object into AccountService as.setAccountManager(pManager); // operate AccountService as.transfer("A", "B", 1005); // check the balance of Account("A") and Account("B") to //verify that AccountService has done the right job EXPECT_EQ(1995, helper.findAccountForUser("A").getBalance()); EXPECT_EQ(3005, helper.findAccountForUser("B").getBalance()); delete pManager; } // Main.cpp #include <gtest/gtest.h> #include <gmock/gmock.h> int main(int argc, char** argv) { testing::InitGoogleMock(&argc, argv); // Runs all tests using Google Test. return RUN_ALL_TESTS(); }
2: 。 , GTEST_DIR 、 GMOCK_DIR , googletest 、 googlemock ; Windows , %GMOCK_DIR%/msvc/gmock_config.vsprops View->Property Manager , gmock.lib 。 , googlemock Mock , , 。 , , ( , AccountService AccountManager )。 , , 《 》( developerWorks , 2008 7 )。 3: , , , 。 , , , , , Mock 。

まとめ

Googletest googlemock , C++ , C++ ; , googletest googlemock , , C++ 、 , 。