7月比较忙,很少更新博客,上旬任务是给公司原来的程序做简单的单元测试。毕业这两年写过很多代码,从来没有注意过单元测试这东西,现在开始认真对待,开始看别人写的文章来学习。这里记录下最近学到的,以及自己给代码测试的方法(不见得是好的办法)。
GTest 使用 官方文档 https://github.com/google/googletest/blob/master/googletest/docs/primer.md
GMock 使用 官方文档 https://github.com/google/googletest/blob/master/googlemock/README.md
QtTest 使用 官方文档 https://doc.qt.io/qt-5/qttest-index.html
QSignalSpy 使用 官方文档 https://doc.qt.io/qt-5/qsignalspy.html
对于结构化的编程语言,程序单元指程序中定义的函数或子程序。单元测试是指对函数或子程序所进行的测试。
对于面向对象的编程语言,程序单元指特定的一个具体的类或相关的多个类。单元测试主要是指对类方法的测试。
角色 | 职责 |
---|---|
测试主管 | 审查单元测试过程,对测试结果进行评估。 |
测试工程师 | 对单元代码进行检查,设计单元测试用例,加载运行测试用例,记录和分析测试结果,填写单元测试Bug清单。 |
开发工程师 | 设计测试需要的驱动程序和桩模块,以及辅助测试工具的开发。 |
配置管理员 | 管理测试需要的资源,包括软硬件环境,版本管理和Bug管理。 |
模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。
windos的我还没编过,有空补上。
git clone --recursive https://github.com/google/googletest.git
cd googletest/
mkdir bulid
cmake ..
cmake-gui ..
make -j4
sudo make install
cd googletest/
mkdir InstallTest
cd InstallTest/
touch main.cpp
g++ -std=c++11 main.cpp -lgtest -lpthread
ls
./a.out
main.cpp 内容
#include
int Add(int a, int b) {
return a + b;
}
TEST(FooTest, HandleNoneZeroInput) {
EXPECT_EQ(7, Add(4, 3));
EXPECT_EQ(18, Add(0, 18));
}
int main(int argc, char *argv[]) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
GMock已经集成进GTest了,编译GTest时默认就编译GMock。
Test Suite、Test Fixtures、Test Case
测试用例和测试名称
做UT(单元测试)时,我们把类\函数分为若干个测试用例,每个测试用例下边有若干个测试,每个测试有一个测试名称。打个比方,我有一个系统备份还原的控制类。下边有两个函数需要测试:备份和还原。我对这个类单元测试时这样命名:
断言
对于每一个测试用例,我们需要对其行为做出断言。为真则通过测试,为假则测试失败。
GTest提供了两个系列的断言宏ASSERT_ 和 EXPECT_。无论安那种断言,只要失败就意味着测试失败。
致命断言 | 非致命断言 | 验证 |
---|---|---|
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition 是真的 |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition 是假的 |
致命断言 | 非致命断言 | 验证 |
---|---|---|
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 |
致命断言 | 非致命断言 | 验证 |
---|---|---|
ASSERT_STREQ(str1,str2); | EXPECT_STREQ(str1,str2); | 这两个字符串具有相同的内容 |
ASSERT_STRNE(str1,str2); | EXPECT_STRNE(str1,str2); | 两个字符串的内容不同 |
ASSERT_STRCASEEQ(str1,str2); | EXPECT_STRCASEEQ(str1,str2); | 两个字符串的内容相同,忽略大小写 |
ASSERT_STRCASENE(str1,str2); | EXPECT_STRCASENE(str1,str2); | 两个字符串的内容不同,忽略大小写 |
每个测试互补干扰,无法使用。
TEST(TestSuiteName, TestName) {
// ... test body ...
}
int Add(int a, int b) {
return a + b;
}
TEST(AddTest, HandleNoneZeroInput) {
EXPECT_EQ(7, Add(4, 3));
EXPECT_EQ(18, Add(0, 18));
}
对多个测试使用相同的数据配置。
TEST_F(TestFixtureName, TestName) {
... test body ...
}
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;
...
};
首先,定义一个夹具类。按照约定,您应该给它命名为FooTest,其中Foo是要测试的类。
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// void TearDown() override {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
在这种情况下,不需要TearDown(),因为除了析构函数已经完成的工作之外,我们无需在每次测试后进行清理。我们将使用TEST_F()和此固定装置编写测试。
TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), 0);
}
TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);
n = q1_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 1);
EXPECT_EQ(q1_.size(), 0);
delete n;
n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 2);
EXPECT_EQ(q2_.size(), 1);
delete n;
}
使用宏 RUN_ALL_TESTS()
注:TEST()自动向TEST_F()隐式注册其测试。
直接看图吧,对于Test Fixtures里每个测试,
先SetUp、运行测试、清理TearDown。
class BackupManagerTest: public ::testing::Test {
public:
BackupManagerTest() {}
public:
virtual void SetUp();
virtual void TearDown();
protected:
};
void BackupManagerTest::SetUp() {
cout << "Begin SetUp ---- ";
cout << " ----End SetUp" << endl;
}
void BackupManagerTest::TearDown() {
cout << "Begin TearDown ---- ";
cout << " ----End TearDown" << endl;
}
/**
* @brief TEST_F
* BackupTest 备份测试
*/
TEST_F(BackupManagerTest, BackupTest) {
cout << "Begin BackupTest ---- ";
cout << " ----End BackupTest" << endl;
}
/**
* @brief TEST_F
* RecoveryTest 还原测试
* 包含两条通路:先备份在还原、无备份就还原
*/
TEST_F(BackupManagerTest, RecoveryTest) {
cout << "Begin RecoveryTest ---- ";
cout << " ----End RecoveryTest" << endl;
}
#include
#include "backupmanagerTest.hpp"
using namespace std;
using ::testing::Return;
int main(int argc, char **argv) {
QApplication app(argc, argv);
::testing::InitGoogleTest(&argc, argv);
::testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}
#ifndef COMMANDLINEMANAGERTEST_HPP
#define COMMANDLINEMANAGERTEST_HPP
#include
#include
#include "engine/commandlinemanager.h"
#include "utils/globalvar.h"
#include
class CommandLineManagerTest: public ::testing::Test {
public:
CommandLineManagerTest() {}
public:
protected:
};
#endif // COMMANDLINEMANAGERTEST_HPP
/**
* @brief TEST_F
* CommandLineTest
* debug模式开启测试 debug模式关闭测试
* test模式开启测试 test模式关闭测试
* 中文模式开启测试 中文模式关闭测试
* 体膜模式开启测试 体膜模式关闭测试
*/
TEST_F(CommandLineManagerTest, CommandLineTest) {
{
int argc = 1;
char *argv[10];
argv[0] = const_cast<char *>("CommandLineManager_Test");
QApplication a(argc, argv);
CommandLineManager::Initial(a);
ASSERT_FALSE(GlobalVar::cmd_option_.debug_mode);
ASSERT_FALSE(GlobalVar::cmd_option_.test_mode);
ASSERT_FALSE(GlobalVar::cmd_option_.phatom_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.zh_ts);
}
{
int argc = 2;
char *argv[10];
argv[0] = const_cast<char *>("CommandLineManager_Test");
argv[1] = const_cast<char *>("-debug");
QApplication a(argc, argv);
CommandLineManager::Initial(a);
ASSERT_TRUE(GlobalVar::cmd_option_.debug_mode);
ASSERT_FALSE(GlobalVar::cmd_option_.test_mode);
ASSERT_FALSE(GlobalVar::cmd_option_.phatom_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.zh_ts);
}
{
int argc = 2;
char *argv[10];
argv[0] = const_cast<char *>("CommandLineManager_Test");
argv[1] = const_cast<char *>("-test");
QApplication a(argc, argv);
CommandLineManager::Initial(a);
ASSERT_TRUE(GlobalVar::cmd_option_.debug_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.test_mode);
ASSERT_FALSE(GlobalVar::cmd_option_.phatom_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.zh_ts);
}
{
int argc = 2;
char *argv[10];
argv[0] = const_cast<char *>("CommandLineManager_Test");
argv[1] = const_cast<char *>("-en");
QApplication a(argc, argv);
CommandLineManager::Initial(a);
ASSERT_TRUE(GlobalVar::cmd_option_.debug_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.test_mode);
ASSERT_FALSE(GlobalVar::cmd_option_.phatom_mode);
ASSERT_FALSE(GlobalVar::cmd_option_.zh_ts);
}
{
int argc = 2;
char *argv[10];
argv[0] = const_cast<char *>("CommandLineManager_Test");
argv[1] = const_cast<char *>("-zh");
QApplication a(argc, argv);
CommandLineManager::Initial(a);
ASSERT_TRUE(GlobalVar::cmd_option_.debug_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.test_mode);
ASSERT_FALSE(GlobalVar::cmd_option_.phatom_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.zh_ts);
}
{
int argc = 2;
char *argv[10];
argv[0] = const_cast<char *>("CommandLineManager_Test");
argv[1] = const_cast<char *>("-phatom");
QApplication a(argc, argv);
CommandLineManager::Initial(a);
ASSERT_TRUE(GlobalVar::cmd_option_.debug_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.test_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.phatom_mode);
ASSERT_TRUE(GlobalVar::cmd_option_.zh_ts);
}
}
void CommandLineManager::Initial(const QApplication &app) {
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
QCommandLineOption debug_option(
"debug", "Open debug mode");
QCommandLineOption test_option(
"test", "Open test dialog");
QCommandLineOption zh_option(
"zh", "Chinese language");
QCommandLineOption en_option(
"en", "English language");
QCommandLineOption phatom_option(
"phatom", "Phatom Test");
parser.addOption(debug_option);
parser.addOption(test_option);
parser.addOption(zh_option);
parser.addOption(en_option);
parser.addOption(phatom_option);
parser.process(app);
if (parser.isSet(debug_option)) {
GlobalVar::cmd_option_.debug_mode = true;
}
if (parser.isSet(test_option)) {
GlobalVar::cmd_option_.test_mode = true;
}
if (parser.isSet(zh_option)) {
GlobalVar::cmd_option_.zh_ts = true;
}
if (parser.isSet(en_option)) {
GlobalVar::cmd_option_.zh_ts = false;
}
if (parser.isSet(phatom_option)) {
GlobalVar::cmd_option_.phatom_mode = true;
}
}
也许不是最优雅的办法,单绝对够最简单高效。直接用友元来实现私有函数的测试。对应在GTest里就是FRIEND_TEST宏。
#include
#include "info/dicominfo.h"
#include "DsaConfig.h"
#if UnitTest or Unit_Test
#include
#endif
/*!
\class DicomDao
\~english @brief The class is a database table management class, which is used to support
the related functions of "addition, deletion, modification and search
of DICOM data".
\~chinese @brief 类是数据库表管理类,用于支持“DICOM数据的增删改查”的相关功能。
\~english @details This entity submodel inherits from QObject and has no other dependencies.
\~chinese @details 类继承自QObject类,无其他依赖。
\~english @author
\~chinese @author
*/
class DicomDao : public QObject {
Q_OBJECT
#if UnitTest or Unit_Test
private:
FRIEND_TEST(DicomDaoTest, InsertDicomTest);
FRIEND_TEST(DicomDaoTest, VerifyDicomByIdTest);
FRIEND_TEST(DicomDaoTest, SelectDicomByIdTest);
FRIEND_TEST(DicomDaoTest, RemoveDicomByIdTest);
FRIEND_TEST(DicomDaoTest, UpdateDicomByIdTest);
FRIEND_TEST(DicomDaoTest, InsertOrUpdateDicomTest);
FRIEND_TEST(DicomDaoTest, SelectDicomListBySessionTest);
FRIEND_TEST(DicomDaoTest, SelectDicomListByUserTest);
FRIEND_TEST(DicomDaoTest, SelectDicomListTest);
#endif
public:
/*!
\~english @brief This function is constructor.
\~chinese @brief 函数是构造函数,用于处理对象的初始化。
\~english @details The parameter interfaces of the current function include parent.
\~chinese @details 函数的参数接口包括parent。
\~english @param parent is the parent window.
\~chinese @param 参数parent是父类窗口。
*/
explicit DicomDao(QObject *parent = nullptr);
关于什么时候写GMock我放在第七节说个人看法。使用GMock来打桩,如果一开始在程序设计时候没有添加,后边补将会是一个很大的工作量。需要把所有调用都加一个纯虚函数接口。
gMock是一个用于创建模拟类并使用它们的库。
class Turtle {
...
virtual ~Turtle() {};
virtual void PenUp() = 0;
virtual void PenDown() = 0;
virtual void Forward(int distance) = 0;
virtual void Turn(int degrees) = 0;
virtual void GoTo(int x, int y) = 0;
virtual int GetX() const = 0;
virtual int GetY() const = 0;
};
class MockTurtle : public Turtle {
public:
...
MOCK_METHOD(void, PenUp, (), (override));
MOCK_METHOD(void, PenDown, (), (override));
MOCK_METHOD(void, Forward, (int distance), (override));
MOCK_METHOD(void, Turn, (int degrees), (override));
MOCK_METHOD(void, GoTo, (int x, int y), (override));
MOCK_METHOD(int, GetX, (), (const, override));
MOCK_METHOD(int, GetY, (), (const, override));
};
MockTurtle turtle;
EXPECT_CALL(turtle, PenDown())
.Times(AtLeast(1));
#ifndef USERDAO_H
#define USERDAO_H
#include
#include "info/userinfo.h"
/*!
\class UserDao
\~english @brief The class is a database table management class, which is used to support
the related functions of "addition, deletion, modification and search
of user data".
\~chinese @brief 类是数据库表管理类,用于支持“用户数据的增删改查”的相关功能。
\~english @details This entity submodel inherits from QObject and has no other dependencies.
\~chinese @details 类继承自QObject类,无其他依赖。
\~english @author
\~chinese @author
*/
class UserDao : public QObject {
Q_OBJECT
public:
/*!
\~english @brief This function is constructor.
\~chinese @brief 函数是构造函数,用于处理对象的初始化。
\~english @details The parameter interfaces of the current function include parent.
\~chinese @details 函数的参数接口包括parent。
\~english @param parent is the parent window.
\~chinese @param 参数parent是父类窗口。
*/
explicit UserDao(QObject *parent = nullptr);
/*!
\~english @brief This function is deconstructor.
\~chinese @brief 函数是析构函数,用于对成员撤销时的一些清理工作。
\~english @details The API with no parameters for the current function.
\~chinese @details 函数无参数接口。
*/
virtual ~UserDao() override;
/*!
\~english @brief This function is used to query the user list.
\~chinese @brief 此函数用于查询用户列表。
\~english @details The parameter interface of the current function includes user_list.
\~chinese @details 函数的参数接口包括user_list。
\~english @param user_list is the set of user information.
\~chinese @param 参数user_list是用户信息的集合。
\~english @return The return value of the function is whether the query was successful.
\~chinese @return 函数的返回值是是否查询成功。
*/
virtual bool SelectUserList(QList<UserInfo> &user_list) const;
};
#endif // USERDAO_H
#ifndef MOCKUSEDSESSIONDAO
#define MOCKUSEDSESSIONDAO
#include "dao/userdao.h"
#include
/**
* @brief The Mock_UsedSessionDao
* 数据库UsedSessionDao的Mock
*/
class MockUserDao : public UserDao {
public:
MockUserDao(QObject *parent = nullptr): UserDao(parent) {}
MOCK_METHOD0(Die, void());
virtual ~MockUserDao();
MOCK_CONST_METHOD1(SelectUserList, bool(QList<UserInfo> &));
};
#endif // MOCKUSEDSESSIONDAO
MockUserDao::~MockUserDao() {
// Die();
}
MockUserDao *mock_dao = new MockUserDao;
EXPECT_CALL(*mock_dao,
SelectUserList(_)).WillRepeatedly(DoAll(
SetArgReferee<0>(QList<UserInfo>({user_list})), Return(1)));
qDebug() << mock_dao->SelectUserList(user_list);
一般情况下GTest就够了,为什么要了解Qt Test
Qt Test优点:
Qt Test缺点:
也许不是最好的办法,但很多人方法和建议是使用GTest模板,配合QSignalSpy和 (Qt)UI模拟 来做测试。
可以看看
https://stackoverflow.com/questions/1524390/what-unit-testing-framework-should-i-use-for-qt
https://stackoverflow.com/questions/4879628/comparing-qtest-with-other-frameworks
QSignalSpy可以连接到任何对象的任何信号并记录其发射。 QSignalSpy本身是QVariant列表的列表。信号的每次发射都将在列表后面添加一个项目,其中包含信号的参数。
QSignalSpy spy(myPushButton, &QPushButton::clicked);
QSignalSpy spy(myPushButton, SIGNAL(clicked(bool)));
函数 | 描述 |
---|---|
QByteArray QSignalSpy::signal() const | 返回当前监听类型 |
bool QSignalSpy::wait(int timeout = 5000) | 启动事件循环,直到接收到给定信号为止。超时返回false |
bool QSignalSpy::isValid() const | 收到有效信号返回真 |
比如:
/**
* @brief TEST_F
* DataBackupTest 备份数据测试
*/
TEST_F(ManageViewTest, DataBackupTest) {
m_widget_->show();
m_widget_->backup_manager_->Set_back_paths_({"./"});
QSignalSpy spy(m_widget_, SIGNAL(SignalStartBackup()));
ASSERT_EQ(0, spy.count());
QTimer::singleShot(100, m_widget_, [ = ] {
m_widget_->yes_button->click();
});
QTest::mouseClick(m_widget_->ui->backup_button, Qt::LeftButton);
ASSERT_EQ(1, spy.count());
}
QSignalSpy spy(myCustomObject, SIGNAL(mySignal(int,QString,double)));
myCustomObject->doSomething(); // trigger emission of the signal
QList<QVariant> arguments = spy.takeFirst();
QVERIFY(arguments.at(0).type() == QVariant::Int);
QVERIFY(arguments.at(1).type() == QVariant::String);
QVERIFY(arguments.at(2).type() == QVariant::double);
QtTest类似GtTest,提供了一系列接口和宏用来做单元/集成测试。提供了几个初始化和析构函数,也有提供了自己的全局测试参数、各种断言以及跟cmaketest (CTest)对接的很多宏。
不过不用研究那么多测试框架,大家都大同小异。无非就是利用友元的特性创建测试函数。c++的测试框架GTest算是比较完善和资料很多的,QtTest虽然也不错但中文资料很少,官网说明也仅仅是使用介绍和基本例子。去github上下载小型开源软件的代码基本都是使用GTest,参考和借鉴别人思路更方便。
当然,QtTest还是要了解的。界面相关测试肯定要模拟鼠标键盘,QtTest就提供了一系列模拟接口方便做界面测试。CTest和QtTest一起用,无疑大大提升开发效率。
QtTest模拟键盘鼠标基本思路是:
函数 | 描述 |
---|---|
QTest::keyPress | 按下按键 |
QTest::keyRelease | 释放按键 |
QTest::keyClick | 点击键盘(按下并释放)。 |
QTest::keyClicks | 连续点击键盘(按下并释放多次)。 |
QTest::keySequence | 连续输入,相当于keyClicks。 |
QTest::Shortcut | 快捷键(如果快捷键一个按键,就相当于keyClick。快捷键是组合键就相当于keyClicks)。 |
函数 | 描述 |
---|---|
QTest::MousePress | 按下鼠标按钮。 |
QTest::MouseRelease | 释放鼠标按钮。 |
QTest::MouseClick | 单击鼠标按钮(按下并释放)。 |
QTest::MouseDClick | 双击鼠标按钮(按下并释放两次)。 |
QTest::MouseMove | 移动鼠标。 |
比如:
/**
* @brief TEST_F
* 增加用户测试
* 可以增加用户
* 两次密码错误
* 两次密码正确
*/
TEST_F(UserEditViewTest, AddUserTest) {
QString use_name = "add";
QSignalSpy spy(m_widget_, SIGNAL(SignalEditUserInfo(
const QString, const QString,
const EditType)));
m_widget_->show();
m_widget_->SetDialog(UserEditView::ADD, use_name);
ASSERT_EQ("", m_widget_->ui->username_edit->text());
// username_edit 可以输入
QTest::keyPress(m_widget_->ui->username_edit, Qt::Key_A);
QTest::keyPress(m_widget_->ui->username_edit, Qt::Key_D);
QTest::keyPress(m_widget_->ui->username_edit, Qt::Key_D);
ASSERT_EQ(use_name, m_widget_->ui->username_edit->text());
// 输入错误密码
QTest::keyPress(m_widget_->ui->password_edit_1, Qt::Key_1);
QTest::keyPress(m_widget_->ui->password_edit_2, Qt::Key_2);
QTest::mouseClick(m_widget_->ui->confirm_button, Qt::LeftButton);
ASSERT_EQ(0, spy.count());
// 输入正确密码
QTest::keyPress(m_widget_->ui->password_edit_2, Qt::Key_Backspace);
QTest::keyPress(m_widget_->ui->password_edit_2, Qt::Key_1);
QTest::mouseClick(m_widget_->ui->confirm_button, Qt::LeftButton);
ASSERT_EQ(1, spy.count());
QList<QVariant> arguments = spy.takeFirst();
ASSERT_EQ(arguments.at(0).toString(), use_name);
ASSERT_EQ(arguments.at(1).toString(), "1");
ASSERT_EQ(arguments.at(2).value<UserEditView::EditType>(), 1);
}
比如:
/**
* @brief TEST_F
* MouseClick 鼠标点击验证
*/
TEST_F(FoldControlViewTest, MouseClick) {
m_widget_->show();
QSignalSpy spy(m_widget_, SIGNAL(SignalsHiddenButtonClicked()));
QTestEventList events;
events.addMouseClick(Qt::LeftButton);
events.addDelay(200);
events.simulate(m_widget_->ui->pushButton);
events.simulate(m_widget_->ui->pushButton);
events.simulate(m_widget_->ui->pushButton);
ASSERT_EQ(spy.count(), 3);
}
我在github上拉一些完整的带测试工程的软件代码。
└── usr
├── 源码
│ │ └── 模块/功能/分类 A
│ │ │ │ └── A 相关源码
│ │ │ │ │ │ └── A 测试代码
│ │ └── 模块/功能/分类 B
│ │ │ │ └── B 相关源码
│ │ │ │ │ │ └── B 测试代码
│ │ └── 模块/功能/分类 C
│ │ │ │ └── C 相关源码
│ │ │ │ │ │ └── C 测试代码
├── 测试数据
│ │ │ │ └── ABC 测试数据
│ │ │ │ └── ABC 测试通用框架
└── usr
├── 源码
│ │ └── 模块/功能/分类 A
│ │ │ │ └── A 相关源码
│ │ └── 模块/功能/分类 B
│ │ │ │ └── B 相关源码
│ │ └── 模块/功能/分类 C
│ │ │ │ └── C 相关源码
├── 测试数据
│ │ │ │ └── ABC 测试数据
│ │ │ │ └── ABC 测试通用框架
│ │ │ │ └── ABC 测试代码
│ │ │ │ │ │ └── A 测试代码
│ │ │ │ │ │ └── B 测试代码
│ │ │ │ │ │ └── C 测试代码
一般有下边两种方法:
用过 gmock会发现当你使用ThirdService、ThirdLibrary时。由于gmock采用的是继承的方式,你需要自己重新实现一个ThirdService。
当你真正想把被测代码隔离开了的时候,才进行Mock。