在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
单元测试概述测试并不只是测试工程师的责任,对于开发工程师,为了保证发布给测试环节的代码具有足够好的质量( 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() 宏声明测试函数1 TEST(GlobalConfigurationTest, configurationDataTest) 2 TEST(GlobalConfigurationTest, noConfigureFileTest) 分别针对同一程序单元 GlobalConfiguration 声明了两个不同的测试(Test)函数,以分别对配置数据进行检查( configurationDataTest ),以及测试没有配置文件的特殊情况( noConfigureFileTest )。 实现单元测试针对同一程序单元设计出不同的测试场景后(即划分出不同的 Test 后),开发者就可以编写单元测试分别实现这些测试场景了。 在 googletest 中实现单元测试,可通过 ASSERT_* 和 EXPECT_* 断言来对程序运行结果进行检查。 ASSERT_* 版本的断言失败时会产生致命失败,并结束当前函数; EXPECT_* 版本的断言失败时产生非致命失败,但不会中止当前函数。因此, ASSERT_* 常常被用于后续测试逻辑强制依赖的处理结果的断言,如创建对象后检查指针是否为空,若为空,则后续对象方法调用会失败;而 EXPECT_* 则用于即使失败也不会影响后续测试逻辑的处理结果的断言,如某个方法返回结果的多个属性的检查。 googletest 中定义了如下的断言: 表 1: googletest 定义的断言( Assert )
下面的实例演示了上面部分断言的使用: 清单 2. 一个较完整的 googletest 单元测试实例1 // Configure.h 2 #pragma once 3 4 #include <string> 5 #include <vector> 6 7 class Configure 8 { 9 private: 10 std::vector<std::string> vItems; 11 12 public: 13 int addItem(std::string str); 14 15 std::string getItem(int index); 16 17 int getSize(); 18 }; 19 20 // Configure.cpp 21 #include "Configure.h" 22 23 #include <algorithm> 24 25 /** 26 * @brief Add an item to configuration store. Duplicate item will be ignored 27 * @param str item to be stored 28 * @return the index of added configuration item 29 */ 30 int Configure::addItem(std::string str) 31 { 32 std::vector<std::string>::const_iterator vi=std::find(vItems.begin(), vItems.end(), str); 33 if (vi != vItems.end()) 34 return vi - vItems.begin(); 35 36 vItems.push_back(str); 37 return vItems.size() - 1; 38 } 39 40 /** 41 * @brief Return the configure item at specified index. 42 * If the index is out of range, "" will be returned 43 * @param index the index of item 44 * @return the item at specified index 45 */ 46 std::string Configure::getItem(int index) 47 { 48 if (index >= vItems.size()) 49 return ""; 50 else 51 return vItems.at(index); 52 } 53 54 /// Retrieve the information about how many configuration items we have had 55 int Configure::getSize() 56 { 57 return vItems.size(); 58 } 59 60 // ConfigureTest.cpp 61 #include <gtest/gtest.h> 62 63 #include "Configure.h" 64 65 TEST(ConfigureTest, addItem) 66 { 67 // do some initialization 68 Configure* pc = new Configure(); 69 70 // validate the pointer is not null 71 ASSERT_TRUE(pc != NULL); 72 73 // call the method we want to test 74 pc->addItem("A"); 75 pc->addItem("B"); 76 pc->addItem("A"); 77 78 // validate the result after operation 79 EXPECT_EQ(pc->getSize(), 2); 80 EXPECT_STREQ(pc->getItem(0).c_str(), "A"); 81 EXPECT_STREQ(pc->getItem(1).c_str(), "B"); 82 EXPECT_STREQ(pc->getItem(10).c_str(), ""); 83 84 delete pc; 85 }
运行单元测试在实现完单元测试的测试逻辑后,可以通过 RUN_ALL_TESTS() 来运行它们,如果所有测试成功,该函数返回 0,否则会返回 1 。 RUN_ALL_TESTS() 会运行你链接到的所有测试――它们可以来自不同的测试案例,甚至是来自不同的文件。 因此,运行 googletest 编写的单元测试的一种比较简单可行的方法是:
清单 3. 初始化 googletest 并运行所有测试1 #include <gtest/gtest.h> 2 3 int main(int argc, char** argv) { 4 testing::InitGoogleTest(&argc, argv); 5 6 // Runs all tests using Google Test. 7 return RUN_ALL_TESTS(); 8 }
此外,在运行可执行目标程序时,可以使用 --gtest_filter 来指定要执行的测试用例,如:
应用 googlemock 编写 Mock Objects
1 <UserMacro 2 Name="BoostDir" 3 Value="$(BOOST_DIR)" 4 />
清单 4. 使用 MOCK_METHODn 声明 Mock 方法 1 #include <gmock/gmock.h> // Brings in Google Mock. 2 3 class MockTurtle : public Turtle { 4 MOCK_METHOD0(PenUp, void()); 5 MOCK_METHOD0(PenDown, void()); 6 MOCK_METHOD1(Forward, void(int distance)); 7 MOCK_METHOD1(Turn, void(int degrees)); 8 MOCK_METHOD2(GoTo, void(int x, int y)); 9 MOCK_CONST_METHOD0(GetX, int()); 10 MOCK_CONST_METHOD0(GetY, int()); 11 };
清单 5. 使用 ON_CALL 及 EXPECT_CALL 宏 1 using testing::Return; // #1,必要的声明 2 3 TEST(BarTest, DoesThis) { 4 MockFoo foo; // #2,创建 Mock 对象 5 6 ON_CALL(foo, GetSize()) // #3,设定 Mock 对象默认的行为(可选) 7 .WillByDefault(Return(1)); 8 // ... other default actions ... 9 10 EXPECT_CALL(foo, Describe(5)) // #4,设定期望对象被访问的方式及其响应 11 .Times(3) 12 .WillRepeatedly(Return("Category 5")); 13 // ... other expectations ... 14 15 EXPECT_EQ("good", MyProductionFunction(&foo)); 16 // #5,操作 Mock 对象并使用 googletest 提供的断言验证处理结果 17 } 18 // #6,当 Mock 对象被析构时, googlemock 会对结果进行验证以判断其行为是否与所有设定的预期一致
清单 6. 使用 NiceMock 模板 1 testing::NiceMock<MockFoo> nice_foo;
清单 7. 初始化 googlemock 并运行所有测试 1 #include <gtest/gtest.h> 2 #include <gmock/gmock.h> 3 4 int main(int argc, char** argv) { 5 testing::InitGoogleMock(&argc, argv); 6 7 // Runs all tests using Google Test. 8 return RUN_ALL_TESTS(); 9 }
清单 8. 待测试的程序逻辑 1 // Account.h 2 // basic application data class 3 #pragma once 4 5 #include <string> 6 7 class Account 8 { 9 private: 10 std::string accountId; 11 12 long balance; 13 14 public: 15 Account(); 16 17 Account(const std::string& accountId, long initialBalance); 18 19 void debit(long amount); 20 21 void credit(long amount); 22 23 long getBalance() const; 24 25 std::string getAccountId() const; 26 }; 27 28 // Account.cpp 29 #include "Account.h" 30 31 Account::Account() 32 { 33 } 34 35 Account::Account(const std::string& accountId, long initialBalance) 36 { 37 this->accountId = accountId; 38 this->balance = initialBalance; 39 } 40 41 void Account::debit(long amount) 42 { 43 this->balance -= amount; 44 } 45 46 void Account::credit(long amount) 47 { 48 this->balance += amount; 49 } 50 51 long Account::getBalance() const 52 { 53 return this->balance; 54 } 55 56 std::string Account::getAccountId() const 57 { 58 return accountId; 59 } 60 61 // AccountManager.h 62 // the interface of external services which should be mocked 63 #pragma once 64 65 #include <string> 66 67 #include "Account.h" 68 69 class AccountManager 70 { 71 public: 72 virtual Account findAccountForUser(const std::string& userId) = 0; 73 74 virtual void updateAccount(const Account& account) = 0; 75 }; 76 77 // AccountService.h 78 // the class to be tested 79 #pragma once 80 81 #include <string> 82 83 #include "Account.h" 84 #include "AccountManager.h" 85 86 class AccountService 87 { 88 private: 89 AccountManager* pAccountManager; 90 91 public: 92 AccountService(); 93 94 void setAccountManager(AccountManager* pManager); 95 void transfer(const std::string& senderId, 96 const std::string& beneficiaryId, long amount); 97 }; 98 99 // AccountService.cpp 100 #include "AccountService.h" 101 102 AccountService::AccountService() 103 { 104 this->pAccountManager = NULL; 105 } 106 107 void AccountService::setAccountManager(AccountManager* pManager) 108 { 109 this->pAccountManager = pManager; 110 } 111 112 void AccountService::transfer(const std::string& senderId, 113 const std::string& beneficiaryId, long amount) 114 { 115 Account sender = this->pAccountManager->findAccountForUser(senderId); 116 117 Account beneficiary = this->pAccountManager->findAccountForUser(beneficiaryId); 118 119 sender.debit(amount); 120 121 beneficiary.credit(amount); 122 123 this->pAccountManager->updateAccount(sender); 124 125 this->pAccountManager->updateAccount(beneficiary); 126 }
清单 9. 相应的单元测试 1 // AccountServiceTest.cpp 2 // code to test AccountService 3 #include <map> 4 #include <string> 5 6 #include <gtest/gtest.h> 7 #include <gmock/gmock.h> 8 9 #include "../Account.h" 10 #include "../AccountService.h" 11 #include "../AccountManager.h" 12 13 // MockAccountManager, mock AccountManager with googlemock 14 class MockAccountManager : public AccountManager 15 { 16 public: 17 MOCK_METHOD1(findAccountForUser, Account(const std::string&)); 18 19 MOCK_METHOD1(updateAccount, void(const Account&)); 20 }; 21 22 // A facility class acts as an external DB 23 class AccountHelper 24 { 25 private: 26 std::map<std::string, Account> mAccount; 27 // an internal map to store all Accounts for test 28 29 public: 30 AccountHelper(std::map<std::string, Account>& mAccount); 31 32 void updateAccount(const Account& account); 33 34 Account findAccountForUser(const std::string& userId); 35 }; 36 37 AccountHelper::AccountHelper(std::map<std::string, Account>& mAccount) 38 { 39 this->mAccount = mAccount; 40 } 41 42 void AccountHelper::updateAccount(const Account& account) 43 { 44 this->mAccount[account.getAccountId()] = account; 45 } 46 47 Account AccountHelper::findAccountForUser(const std::string& userId) 48 { 49 if (this->mAccount.find(userId) != this->mAccount.end()) 50 return this->mAccount[userId]; 51 else 52 return Account(); 53 } 54 55 // Test case to test AccountService 56 TEST(AccountServiceTest, transferTest) 57 { 58 std::map<std::string, Account> mAccount; 59 mAccount["A"] = Account("A", 3000); 60 mAccount["B"] = Account("B", 2000); 61 AccountHelper helper(mAccount); 62 63 MockAccountManager* pManager = new MockAccountManager(); 64 65 // specify the behavior of MockAccountManager 66 // always invoke AccountHelper::findAccountForUser 67 // when AccountManager::findAccountForUser is invoked 68 EXPECT_CALL(*pManager, findAccountForUser(testing::_)).WillRepeatedly( 69 testing::Invoke(&helper, &AccountHelper::findAccountForUser)); 70 71 // |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论