玩转单元测试之gtest

news/2024/7/10 21:07:53 标签: 单元测试, 开源

引言

程序开发的时候,往往需要编写一些测试样例来完成功能测试,以保证自己的代码在功能上符合预期,能考虑到一些异常边界问题等等。

gtest快速入门

1.引入gtest

# 使用的是1.10版本,其他版本可根据需要选择
git clone -b v1.10.x https://github.com/google/googletest.git
cd googletest
mkdir build && cd build
cmake .. && make -j4
sudo make install
sudo ldconfig

2.编写第一个单测

2.1 待测试文件

#ifndef __HELLO_H__
#define __HELLO_H__

#include <iostream>
#include <string>

class Animal {
public:
    Animal(std::string name) 
        : _name(name)
    {
        
    }
    virtual ~Animal() {}
    virtual bool eat(const std::string& food) = 0;

private:
    std::string _name;
};

class Tigger : public Animal {
public:
    Tigger() : Animal("tigger")
    {

    }
    bool eat(const std::string& food) override
    {
        if (food == "meat") {
            return true;
        }
        return false;
    }
};

class Horse : public Animal {
public:
    Horse() : Animal("Horse")
    {
         
    }
    bool eat(const std::string& food) override
    {
        if (food == "grass") {
            return true;
        }
        return false;
    }
};
#endif

2.2 单测文件

#include "hello.h"
#include "gtest/gtest.h"

using namespace ::testing;

namespace {
TEST(TestTigger, CaseEat) 
{
    Animal *tigger = new Tigger();
 	bool ret = tigger->eat("meat");
    EXPECT_TRUE(ret);

    ret = tigger->eat("grass");
    EXPECT_FALSE(ret);
    delete tigger;
}

TEST(TestHorse, CaseEat)
{
    Animal *horse = new Horse();
    bool ret = horse->eat("grass");
    EXPECT_TRUE(ret);

    ret = horse->eat("meat");
    EXPECT_FALSE(ret);
    delete horse;
}
}

2.3 makefile文件

CXX = g++
CXXFLAGS = -Wall
LIBES = -lgtest -lgtest_main -lpthread
LPATH = -L/tools/googletest/1.11.0/build/lib  # 替换成自己lib路径
HPATH = -I/tools/googletest/1.11.0/googletest/include/ # 替换成自己的include路径

UTEST_OBJD = hello_unit_test

hello_unit_test:hello_unit_test.cpp
	${CXX} -o $@ $+ -I ../ ${HPATH} ${CXXFLAGS} ${LIBES} ${LPATH}

clean:
	rm -rf *_unit_test

make && ./hello_unit_test 编译并执行单测程序,执行结果如下:
在这里插入图片描述

gtest常用宏

1. 各种断言

1.1 Bool断言

致命断言非致命断言含义
ASSERT_TRUE(val)EXPECT_TRUE(val)val == true
ASSERT_FALSE(val)EXPECT_FALSE(val)val == false

1.2 二元值断言(比较大小)

致命断言非致命断言含义
ASSERT_EQ(a, b)EXPECT_EQ(a, b)a == b
ASSERT_NE(a, b)EXPECT_NE(a, b)a != b
ASSERT_LT(a, b)EXPECT_LT(a, b)a < b
ASSERT_LE(a, b)EXPECT_LE(a, b)a <= b
ASSERT_GT(a, b)EXPECT_GT(a, b)a > b
ASSERT_GE(a, b)EXPECT_GE(a, b)a >= b

1.3 字符串断言(字符串比较)

致命断言非致命断言含义
ASSERT_STREQ(a, b)EXPECT_STREQ(a, b)a == b
ASSERT_STRNE(a, b)EXPECT_STRNE(a, b)a != b
ASSERT_STRCASEEQ(a, b)EXPECT_STRCASEEQ(a, b)a == b 忽略大小写
ASSERT_STRCASENE(a, b)EXPECT_STRCASENE(a, b)a != b 忽略大小写

2. TEST、TEST_F和TEST_P

2.1 TEST

TEST是最基本的构造测试case的宏,基本用法:

TEST(param1, prama2)
{
/*测试代码*/
}
  • 参数1:用例名,一般由待测试的类名或函数名组成,如TestAnimal
  • 参数2:测试名,代表测试含义,如CaseEat
  • 测试结果将以"用例名.测试名"来区分不同测试case

2.2 TEST_F

TEST_F和TEST的不同之处在于,其可以使用到初始化函数(SetUp)和一个清理函数(TearDown)。基本用法如下:

class TestAnimal : public ::testing::Test {
protected:
	void SetUp() override
	{
		// 成员变量初始化
		tigger = new Tigger();
	}
	void TearDown() override
	{
		// 资源清理、释放
		delete tigger;
		tigger = NULL;
	}
protected:
	Animal *tigger;
};

TEST_F(TestAnimal, caseEatMeat)
{	
	// 执行之前调用SetUp进行初始化
	EXPECT_TRUE(tigger->eat("meat"));
	// case退出时调用TearDown进行释放
}

TEST_F(TestAnimal, caseEatGrass)
{
	// 执行之前调用SetUp进行初始化
	EXPECT_FALSE(tigger->eat("grass"));
	// case退出时调用TearDown进行释放
}
  • 创建一个继承testing::Test的测试类TestAnimal,并在该类中声明成员变量,做好初始化和清理操作
  • TEST_F宏
    • 参数1:同测试类名(TestAnimal )
    • 参数2:测试名,代表测试含义
  • 每一个测试case都是相互独立的,当每个case需要共同使用某个变量时,可以将该变量放在测试类中,每执行一个TEST_F宏构造的case,都会调用一次SetUp和TearDown,因此case之间对变量的操作不会相互影响

2.3 TEST_P

针对某个待测试的方法,当你需要测试不同的输入,但又不想每个case都写一遍时,就可以使用到TEST_P宏,基本使用如下:

// 多个参数时,使用结构体更方便
struct MyParams {
	std::string food;
	// other params
};
class TestAnimal : public ::testing::Test, public ::testing::WithParamInterface<MyParams>
{
protected:
	void SetUp() override
	{
		// 成员变量初始化
		tigger = new Tigger();
	}
	void TearDown() override
	{
		// 资源清理、释放
		delete tigger;
		tigger = NULL;
	}
protected:
	Animal *tigger;
};

TEST_P(TestAnimal, caseEat) 
{
	std::string food = GetParam().food; // 获取参数
	ASSERT_FALSE(tigger->eat(food));
}

// 构造不同的测试样例
INSTANTIATE_TEST_SUITE_P(TestCaseEatParams, TestAnimal, ::testing::Values(
	MyParams{"grass"},
	MyParams{"leafs"}
));
  • 和TEST_F有相似的功能,使用SetUp、TearDown进行初始化和清理,创建一个继承testing::Test、testing::WithParamInterface的测试类,其中WithParamInterface是一个模板类,用来关联测试参数。
  • TEST_P宏
    • 参数1:测试类名
    • 参数2:测试名,代表测试含义
  • INSTANTIATE_TEST_SUITE_P宏
    • 参数1:能表明测试含义即可
    • 参数2:测试类名
    • 参数3:不同测试样例集合
  • 执行结果如下:
    在这里插入图片描述

总结

  • 好记性不如烂笔头,最近在写单元测试,于是就有了这篇文章。
  • 通过学习gtest的基本语法,已经可以应对一部分测试场景了,然而还有一些场景只通过gtest是无法完成的,比如,在我们的代码中有许多并不是我们自己设计的接口,可能是外部依赖,也可能来自于其他模块,我们没办法设计一个合适的case来让这些接口返回给我们一个预期值,那我们该怎么办呢?于是gmock由此诞生,这个在下一篇中会进行深入学习,篇名我已经想好了(玩转单元测试之GMock)

http://www.niftyadmin.cn/n/4946026.html

相关文章

349. 两个数组的交集 题解

题目描述&#xff1a;349. 两个数组的交集 - 力扣&#xff08;LeetCode&#xff09; 给定两个数组 nums1 和 nums2 &#xff0c;返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 方法一&#xff1a; 解题思路&#xff1a; 我们可以…

浏览器时不时自动弹出“领英企业服务”这个网页怎么办?

最近好像是点过领英推送过来邮件里面的链接&#xff0c;导致Microsoft edge浏览器总是自动弹出“领英企业服务”这个网页&#xff0c;真的恶心。遇到这种情况该怎么办&#xff1f;下面介绍几种方法。 1.使用命令来删除这个app&#xff1a; 管理员权限启动powershell 执行命令…

Python中的私有方法如何使用

在Python中&#xff0c;私有方法是指以双下划线开头的方法。这些方法只能在类的内部使用&#xff0c;不能被外部调用。私有方法的作用是保护类的内部实现细节&#xff0c;防止外部直接访问和修改。 下面是一个示例代码&#xff0c;展示了如何使用私有方法&#xff1a; class M…

空气IT

现代社会中&#xff0c;空气质量成为了人们关注的焦点之一。随着工业化的发展&#xff0c;汽车尾气、工厂排放和燃煤等行为导致城市空气污染日益严重&#xff0c;给人们的健康和生活质量带来了极大的威胁。 首先&#xff0c;空气污染对人体健康造成了严重的危害。空气中的颗粒…

55.C++冒泡排序和选择排序

选择排序&#xff08;Selection Sort&#xff09;和冒泡排序&#xff08;Bubble Sort&#xff09;是两种基础的排序算法&#xff0c;虽然效率相对较低&#xff0c;但在学习排序算法的过程中非常有用。 目录 一、冒泡排序 什么是冒泡排序 示例代码 二、选择排序 什么是选…

干翻Dubbo系列第十篇:Dubbo体系中ProtoBuf序列化方式详解

文章说明 本文内容整理自《孙哥说Dubbo系列视频课程》&#xff0c;孙帅老师课程细致、全面、深入、性价比极高。B站搜孙帅suns可以找到对应的试听视频&#xff0c;或者直接添加老师微信号suns45与他直接联系 一&#xff1a;序列化概念 补充说明&#xff1a; Kyro和Fst这两种…

excel统计函数篇1之average系列

一、excel中的统计函数 1、AVERAGE(number1,number2,...)&#xff1a;返回其参数的平均值 2、AAVERAGEA(value1,value2,...)&#xff1a;返回其参数的平均值&#xff0c;包括数字、文本和逻辑值 可以在括号内手动输入&#xff0c;也可以引用单元格&#xff0c;对序列求平均的…

成形滤波器、CIC补偿滤波器 matlab设计与FPGA实现

成形滤波插5 Matlab Rs 500e3; %符号速率 beta 0.2; %滚降系数(0,1) (实际带宽-理想带宽)/理想带宽 W (1beta)*Rs/2 %所需带宽 span 36; %滤波阶数 sps 5; %单个符号采样数% 滤波器阶数 span*sps% 滤波器系数数 span*sps 1 mfir_i5 rcosdesign(beta,span,sps); fv…