0
点赞
收藏
分享

微信扫一扫

gtest和gmock在C++中的使用过程


文章目录

  • ​​1. 介绍​​
  • ​​2. 源码编译安装​​
  • ​​3. demo​​
  • ​​3.1. Demo1: 简单的利用gtest给定的一些宏进行测试​​
  • ​​3.2 gtest给的一些宏​​
  • ​​3.3 利用gtest测试方法和类​​
  • ​​a. Demo2: TEST​​
  • ​​b. Demo3: TEST_F​​
  • ​​3.4 Demo4: gmock​​
  • ​​结尾​​

1. 介绍

好的开发必然是一个好的测试.
而业界普遍使用的测试框架是gtest, 测试一个方法A时涉及到了其他的类B, 可以通过gmock来模拟其他类B实现输入输出

​​本文介绍如何使用gtest和gmock, 具体的实验demo已经上传到了国产github, 有需要的点击下载, 如果真的帮助到你, 请给个star​​

​git clone https://gitee.com/nwu_zjq/demo_gtest_gmock.git​

2. 源码编译安装

克隆下给出的demo, 里面有gtest的安装包
需要的环境有 cmake, gcc, 最好gdb也有

# export MYPATH=<指定安装目录如:/home/install>
export MYPATH=/home/install
mkdir -p ${MYPATH}
unzip source/googletest-main.zip
mkdir -p source/googletest-main/build && pushd source/googletest-main/build
cmake .. -DCMAKE_INSTALL_PREFIX=${MYPATH} && make -j4 && sudo make install
echo "export CPLUS_INCLUDE_PATH=CPLUS_INCLUDE_PATH:${MYPATH}/include" >> ~/.bashrc
echo "export C_INCLUDE_PATH=C_INCLUDE_PATH:${MYPATH}/include" >> ~/.bashrc
echo "export OBJC_INCLUDE_PATH=OBJC_INCLUDE_PATH:${MYPATH}/include" >> ~/.bashrc
echo "export LIBRARY_PATH=${MYPATH}/lib:$LIBRARY_PATH" >> ~/.bashrc
echo "export LD_LIBRARY_PATH=${MYPATH}/lib:$LD_LIBRARY_PATH" >> ~/.bashrc
echo "export PKG_CONFIG_PATH=${MYPATH}/lib/pkgconfig:$PKG_CONFIG_PATH" >> ~/.bashrc
pushd
source ~/.bashrc

3. demo

3.1. Demo1: 简单的利用gtest给定的一些宏进行测试

​​Demo1:EXPECT​​

本实例是通过Makefile编译运行测试用例的, 由于上面安装过程中, 已经将gtest的库包含到了系统环境变量中, 因此在代码连接过程中, 直接使用​​-lgtest -lgtest_main​​ 即可, 这里也是我一直犯得错误

  • 1 测试代码

// test_main.cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>

int add(int a, int b){
return a+b;
}

int main(int argc, char const *argv[]) {
EXPECT_EQ(add(1, 2), 3); // pass
EXPECT_EQ(add(1, 2), 1) << "FAILED: EXPECT: 2, but given 1";; // FAILDED
return 0;
}

  • 管理编译运行

# Makefile
# 与test_main.cpp同个文件夹, 执行命令是make
CXX=g++
# LDFLAGS = -g -L/home/zjq/01_software/install/lib
LIBS = -lgtest -lgtest_main -lpthread
# CXXFLAGS = -I/home/zjq/01_software/install/include
test_main: test_main.o
$(CXX) -o test_main test_main.o $(LIBS)
./test_main
test_main.o: test_main.cpp
$(CXX) -c test_main.cpp
clean:
rm -rf *.o test_main

  • 运行结果

# 运行结果
$ ./test_main
test_main.cpp:10: Failure
Expected equality of these values:
add(1, 2)
Which is: 3 # 期待的值
1 # 给定的值
FAILED: EXPECT: 2, but given 1 # 自己添加的提示信息

3.2 gtest给的一些宏

​​开始​​

表1 一元比较

ASSERT

EXPECT

Verifies

​ASSERT_TRUE(condition);​

​EXPECT_TRUE(condition);​

​condition​​ is true

​ASSERT_FALSE(condition)​

​EXPECT_FALSE(condition)​

​condition​​ is false

表2 二元比较

ASSERT

EXPECT

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​

字符串检查

Fatal assertion

Nonfatal assertion

Verifies

​ASSERT_STREQ(expected_str, actual_str);​

​EXPECT_STREQ(expected_str,actual_str);​

​the two C strings have the same content​

​ASSERT_STRNE(str1, str2);​

​EXPECT_STRNE(str1, str2);​

​the two C strings have different content​

​ASSERT_STRCASEEQ(expected_str, actual_str);​

​EXPECT_STRCASEEQ(expected_str, actual_str);​

​the two C strings have the same content, ignoring case​​ (忽略大小写)

​ASSERT_STRCASENE(str1, str2);​

​EXPECT_STRCASENE(str1, str2);​

​the two C strings have different content, ignoring case​​ (忽略大小小)

异常检查

Fatal assertion

Nonfatal assertion

Verifies

​ASSERT_THROW(statement, exception_type);​

​EXPECT_THROW(statement, exception_type);​

​statement throws an exception of the given type​

​ASSERT_ANY_THROW(statement);​

​EXPECT_ANY_THROW(statement);​

​statement throws an exception of any type​

​ASSERT_NO_THROW(statement);​

​EXPECT_NO_THROW(statement);​

​statement doesn't throw any exception​

3.3 利用gtest测试方法和类

本实验使用TEST宏和TEST_F宏, 来实现对函数, 类的测试
并且通过CmakeLists.txt代替Makefile来管理自动化编译和运行

a. Demo2: TEST

​​Demo2: TEST​​

  • 编写对函数的测试用例, gtest会按照顺序进行执行测试

// test_main.cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>
// 直接在当前目录运行 make

int Factorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}

return result;
}

// 正数为一组
TEST(FactorialTest, Negative) {
EXPECT_EQ(1, Factorial(-5));
EXPECT_EQ(1, Factorial(-1));
EXPECT_GT(Factorial(-10), 0);
}
// 0
TEST(FactorialTest, Zero) {
EXPECT_EQ(1, Factorial(0));
EXPECT_EQ(3, Factorial(0)); // 故意设定一个错误的
}
// 负数为一组
TEST(FactorialTest, Positive) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}

int main(int argc, char **argv) {
printf("Running main() from %s\n", __FILE__);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

  • 编写CMakeLists.txt文件, 完成自动编译运行

# CMakeLists.txt
# 设置变量
set(GTESTLIB gtest gtest_main pthread)
# 生成可执行文件
add_executable(test_main test_main.cpp)
# 指定链接动态库或静态库, 链接导入库
target_link_libraries(test_main ${GTESTLIB})

  • 编译运行

mkdir -p build && cd build && cmake .. && make -j4

# 运行结果
./test_main
Running main() from test_main.cpp
[==========] Running 3 tests from 1 test suite. # 3组测试用例
[----------] Global test environment set-up.
[----------] 3 tests from FactorialTest
[ RUN ] FactorialTest.Negative # Negative 组输出
[ OK ] FactorialTest.Negative (0 ms) # OK 表示 Negative 组全部测试通过
[ RUN ] FactorialTest.Zero # Zero组输出
[ OK ] FactorialTest.Zero (0 ms)
[ RUN ] FactorialTest.Positive # Positive组输出
[ OK ] FactorialTest.Positive (0 ms)
[----------] 3 tests from FactorialTest (0 ms total)

# sample1_unitest 另一个测试案例的输出 ...
[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 3 tests. # 全部测试结果:PASS表示全部通过

b. Demo3: TEST_F

​​Demo3: TEST_F​​

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <iostream>
#include "sample3-inl.h" // 定义并实现了Queue

using namespace std;


// 直接在当前目录运行 make
class QueueTestSmpl3 : public testing::Test { // 继承了 testing::Test
protected:

static void SetUpTestSuite() {
cout<< "TestSuite测试套事件: 在第一个testcase之前执行" << endl;
}

static void TearDownTestSuite() {
cout<< "TestSuite测试套事件: 在第一个testcase之后执行" << endl;
}

virtual void SetUp() override { // 这里初始化对象
cout<< "TestSuite 测试用例 事件: 在每个testcase之前执行" << endl;
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}

virtual void TearDown() override { // 在这里析构对象
cout<< "TestSuite 测试用例 事件: 在每个testcase之后执行" << endl;
}

static int Double(int n) {
return 2*n;
}

void MapTester(const Queue<int> * q) {
const Queue<int> * const new_q = q->Map(Double);

ASSERT_EQ(q->Size(), new_q->Size());

for (const QueueNode<int>*n1 = q->Head(), *n2 = new_q->Head();
n1 != nullptr; n1 = n1->next(), n2 = n2->next()) {
EXPECT_EQ(2 * n1->element(), n2->element());
}

delete new_q;
}

Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};

class FooEnvironment : public testing::Environment {
public:
virtual void SetUp(){
cout<< "FooEnvironment SetUp" << endl;
}

virtual void TearDown(){
cout<< "FooEnvironment TearDown" << endl;

}
};

// in sample3_unittest.cc

/* TEST_F执行流程
1. gtest 构造对象 t1
2. t1.SetUp 初始化t1
3. 下面的TEST_F运行并结束
4. t1.TearDown 运行用于清理工作
5. t1 被析构
*/
// Tests the default c'tor.
TEST_F(QueueTestSmpl3, DefaultConstructor) {
// !!! 在 TEST_F 中可以使用 QueueTestSmpl3 的成员变量、成员函数
EXPECT_EQ(0u, q0_.Size()); // q0_数量是0
}

// Tests Dequeue().
TEST_F(QueueTestSmpl3, Dequeue) {
int * n = q0_.Dequeue(); // q0_数量是0
EXPECT_TRUE(n == nullptr);

n = q1_.Dequeue(); // // q1_ pop出的是1=>*n
ASSERT_TRUE(n != nullptr);
EXPECT_EQ(1, *n);
EXPECT_EQ(0u, q1_.Size()); // q1_ 此时是size=0
delete n;

n = q2_.Dequeue();
ASSERT_TRUE(n != nullptr);
EXPECT_EQ(2, *n);
EXPECT_EQ(1u, q2_.Size());
delete n;
}

// Tests the Queue::Map() function.
TEST_F(QueueTestSmpl3, Map) {
MapTester(&q0_);
MapTester(&q1_);
MapTester(&q2_);
}

/*
看完这个例子, 就能发现其实 QueueTestSmpl3 就是用于构建Queue对象, 并执行一系列初始化操作的
在整个测试用例执行过程中, QueueTestSmpl3一直是活跃状态, 只有等所有的测试用例执行结束, 调用TearDownTestSuite进行数据回收结束
*/
int main(int argc, char **argv) {
// printf("Running main() from %s\n", __FILE__);
// testing::AddGlobalTestEnvironment(new FooEnvironment);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

3.4 Demo4: gmock

​​Demo4: gmock​​

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <iostream>

using namespace std;

class Cargo {
public:
Cargo() {};
~Cargo() {};

virtual bool NeedBuy(const std::string& cargoName) = 0;
virtual bool CostValue() = 0;
virtual int NeedCost(int value) {
return value;
}
};


// 这里相当于TestCargo重新构造了一个跟Cargo一模一样的类 ,如果测试其他类时需要用到Cargo类, 就使用TestCargo类来替换即可
class TestCargo : public Cargo {
public:
MOCK_METHOD1(NeedCost, int(int));
MOCK_METHOD1(NeedBuy, bool(const std::string&));
MOCK_METHOD0(CostValue, bool());
};

TEST(TestCargo, MOCK_OK) {
TestCargo testCargo;

// 先制造函数的输入和指定的输出
// 调用期待一: 调用一次后返回true, 之后调用默认返回false
EXPECT_CALL(testCargo, CostValue()).WillOnce(testing::Return(true)); // 这里就不用管CostValue函数里面的内容具体是啥了, 直接给return即可
// 调用期待二: 第一参数candy, 总是返回true
EXPECT_CALL(testCargo, NeedBuy("candy")).WillRepeatedly(testing::Return(false));
// 调用期待 三: 期待被调用5次, 后续调用返回默认值 WillOnce表示第一次调用返回true, WillRepeatedly表示后面在调用都是返回false
// EXPECT_CALL(testCargo, NeedBuy(_)).Times(5).WillOnce(testing::Return(true)).WillRepeatedly(testing::Return(false));
// 调用期待四: 第一参数_, 总是返回对应的值_
EXPECT_CALL(testCargo, NeedCost(1)).WillRepeatedly(testing::Return(1));


// 测试
EXPECT_TRUE(testCargo.CostValue()); // 第一次是true
EXPECT_TRUE(testCargo.CostValue() != true);

EXPECT_TRUE(!testCargo.NeedBuy("candy")); // 全都是false
EXPECT_TRUE(!testCargo.NeedBuy("candy")); // 全都是false

EXPECT_EQ(1, testCargo.NeedCost(1));
}

/*
制造类方法的方法是 EXPECT_CALL
EXPECT_CALL(mock_object, Method(matchers)) // 调用期待, 说明对象的方法调用执行逻辑, Method是对象的mock方法, 通过match匹配
.With(matchers) // 指定多个参数的匹配方法
.Times(numbers) // 该方法能够被执行几次
.InSequence(sequence) // 指定函数执行顺序
.After(expectation) // 指定某个方法只能在另一个方法之后执行
.WillOnce(action) // 执行一次方法时, 将执行参数action的方法
.WillRepeatedly(action) // 一直调用方法时, 执行参数action的方法
.RetiresOnSaturation(); // 用于保证期待的调用不会被相同的期待覆盖

// 如: EXPECT_CALL(testCargo, NeedCost(1)).WillRepeatedly(testing::Return(1));
*/

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

结尾

本文主要是介绍了一下gtest和gmock的使用流程和细节, 有些项目中是将gtest整个源码包放到项目中,在编译过程中直接使用, 本文是将gtest直接安装到系统中, 这样直接在项目文件中写测试用例即可.

​​抛砖引玉, 具体高阶玩法还得看谷歌的操作手册​​


举报

相关推荐

0 条评论