C代码的单元测试

news2024/11/16 22:43:35

 

C代码中集成gtest单元测试_gtest测试c语言_山河故人~的博客-CSDN博客

Linux安装gtest_gtest安装_山河故人~的博客-CSDN博客

一:安装gtest:

1. 安装gtest
采用源码安装的方式,需确保cmake已经安装。

git clone https://github.com/google/googletest
cd googletest
mkdir build
cd build
cmake ..
make
sudo make install

2. 测试安装成功

新建gtest.c文件,输入如下测试代码:

#include<gtest/gtest.h>
int add(int a,int b){
    return a+b;
}
TEST(testCase,test0){
    EXPECT_EQ(add(2,3),5);
}
int main(int argc,char **argv){
  testing::InitGoogleTest(&argc,argv);
  return RUN_ALL_TESTS();
}

编译可执行文件

g++ gtest.c -lgtest -lpthread -v -o gtest

运行

./gtest


运行结果

[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from testCase
[ RUN      ] testCase.test0
[       OK ] testCase.test0 (0 ms)
[----------] 1 test from testCase (0 ms total)
 
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

以上运行结果代表gtest安装成功

二:C代码中快速集成gtest进行单元测试

1. 原理

将 gtest 框架应用到 C 程序的原理比较简单,主要分 3 步:

1.编译测试框架 googletest 源码得到 libgtest.a 库文件(也可以根据需要编译成动态链接库或共享库文件.so)
2.编译待测试的 C 代码得到一个功能库文件,例如 libfoo.a
3.写一个单元测试文件(如:foo_unitttest.cc), 编译链接上 libgtest.a 和 libfoo.a 生成可执行文件进行测试。

如果代码规模不大,对代码模块化要求也不高的话,可以将第2和第3步合并到一起执行,变成:

1.编译测试框架 googletest 源码得到 libgtest.a 库文件
2.写一个单元测试文件(如:foo_unitttest.cc),和待测试代码一起编译并链接到 libgtest.a 库得到可执行文件进行测试


2. 两步操作快速集成 gtest


2.1 准备 gtest 库文件和头文件

复制下面的代码分别保存为 CMakeLists.txt 和 gtest.mk 文件。

1:CMakeLists.txt

cmake_minimum_required(VERSION 3.14)
project(gtestlib)

# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip
)

# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

2:gtest.mk

.PHONY: all clean

CMAKE   = cmake
MAKE    = make

GTEST             = .

GTEST_BUILD_DIR   = $(GTEST)/build
GTEST_INSTALL_DIR = $(GTEST)/gtest
GTEST_CMAKELIST   = $(GTEST)/CMakeLists.txt

GTEST_LIB         = $(GTEST_INSTALL_DIR)/lib/libgtest.a
GTEST_LIBMAIN     = $(GTEST_INSTALL_DIR)/lib/libgtest_main.a

all:
    if [ ! -e $(GTEST_LIB) ] || [ ! -e $(GTEST_LIBMAIN) ]; then \
        mkdir -p $(GTEST_BUILD_DIR) && \
        $(CMAKE) -S $(GTEST) -B $(GTEST_BUILD_DIR) -DBUILD_GMOCK=OFF -DCMAKE_INSTALL_PREFIX=$(GTEST_INSTALL_DIR) && \
        $(MAKE) -C $(GTEST_BUILD_DIR) && \
        $(MAKE) -C $(GTEST_BUILD_DIR) install; \
    fi

clean:
    if [ -e $(GTEST_BUILD_DIR) ]; then \
        $(MAKE) -C $(GTEST_BUILD_DIR) clean; \
    fi
    rm -rf $(GTEST_BUILD_DIR)
    rm -rf $(GTEST_INSTALL_DIR)

将上面的两个文件存放到同一个文件夹下,然后执行make -f gtest.mk。

代码会自动下载 googletest 最新的 v1.11 版本进行编译,并将生成的库文件和头文件输出到当前文件夹的 gtest 目录下,如下:

$ tree gtest
gtest
├── include
│   └── gtest
│       ├── gtest-death-test.h
│       ├── gtest.h
│       ├── gtest-matchers.h
│       ├── gtest-message.h
│       ├── gtest-param-test.h
│       ├── gtest_pred_impl.h
│       ├── gtest-printers.h
│       ├── gtest_prod.h
│       ├── gtest-spi.h
│       ├── gtest-test-part.h
│       ├── gtest-typed-test.h
│       └── internal
│           ├── custom
│           │   ├── gtest.h
│           │   ├── gtest-port.h
│           │   ├── gtest-printers.h
│           │   └── README.md
│           ├── gtest-death-test-internal.h
│           ├── gtest-filepath.h
│           ├── gtest-internal.h
│           ├── gtest-param-util.h
│           ├── gtest-port-arch.h
│           ├── gtest-port.h
│           ├── gtest-string.h
│           └── gtest-type-util.h
└── lib
    ├── cmake
    │   └── GTest
    │       ├── GTestConfig.cmake
    │       ├── GTestConfigVersion.cmake
    │       ├── GTestTargets.cmake
    │       └── GTestTargets-noconfig.cmake
    ├── libgtest.a
    ├── libgtest_main.a
    └── pkgconfig
        ├── gtest_main.pc
        └── gtest.pc
8 directories, 31 files
  • 目录 gtest/inlcude 包含 gtest 的头文件,写测试时需要包含头文件gtest/gtest.h
  • 目录 gtest/lib 包含 gtest 的库文件,链接时需要链接 libgtest.a 和 libgtest_main.a

特别说明:

由于是自动下载代码,所以需要 cmake 在 v3.14 以上;
gtest生成的两个库文件中,只有 libgtest.a 是必须的,另外一个库文件 libgtest_main.a是 gtest 运行的入口,实际上就是一个 main 函数,如果你在自己的单元测试中定义了自己的 main 函数去调用 gtest,那就不需要 libgtest_main.a。


2.2 编写测试代码


待测试文件
这里的测试文件有两个, 分别是 gcd.c 和 factorial.c。foo.h, 头文件,用于函数声明

#ifndef __FOO_H__
#define __FOO_H__
#ifdef __cplusplus
extern "C" 
{
#endif

int gcd(int a, int b);

int factorial(int n);

#ifdef __cplusplus
}
#endif
#endif

gcd.c,用于计算两个数的最大公约数

#include "foo.h"

int gcd(int a, int b)
{
    if (b == 0)
    {
        return a;
    }
    else
    {
        return gcd(b, a % b);
    }
}

factorial.c,用于计算一个数的阶乘

#include "foo.h"

int factorial(int n)
{
    int i, res;

    res = 1;
    for (i=n; i>0; i--)
    {
        res *= i;
    }

    return res;
}

单元测试文件
写一个单元测试文件 foo_unittest.cc,包含两组测试,分别用于测试函数 gcd 和 factorial:
foo_unittest.cc

#include "foo.h"
#include <gtest/gtest.h>

TEST(GCDTest, EvenTest)
{
    EXPECT_EQ(2, gcd(4, 10));
    EXPECT_EQ(6, gcd(30, 18));
    EXPECT_EQ(15, gcd(30, 45));
}

TEST(GCDTest, PrimeTest)
{
    EXPECT_EQ(1, gcd(23, 10));
    EXPECT_EQ(1, gcd(359, 71));
    EXPECT_EQ(1, gcd(47, 83));
}

TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(factorial(0), 1);
}

TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(factorial(1), 1);
  EXPECT_EQ(factorial(2), 2);
  EXPECT_EQ(factorial(3), 6);
  EXPECT_EQ(factorial(8), 40320);
}

#if 0
int main(int argc, char *argv[])
{
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
#endif

在上面的代码中包含了两组一共4个测试:

一组 GCDTest, 用于测试 gcd 函数
一组 FactorialTest 用于测试 factorial 函数。


2.3 编译并运行测试


然后使用下面的语句编译上面的代码生成可执行的测试文件:

$ g++ gcd.c factorial.c foo_unittest.cc -o footest -I. -Igtest/include -Lgtest/lib -lgtest -lgtest_main -lpthread

注意: 这里使用 g++ 指令一口气编译了 gcd.c, factorial.c 和 foo_unittest.cc 一共 3 个文件,生成测试文件 footest。

执行测试:

参考上面的操作,实际上只需要将 g++ 指令中的代码文件替换成你实际的文件就可以了。

2.4 关于测试文件的说明
在 gcd.c, factorial.c 和 foo_unittest.cc 中都没有定义 main 函数。

只在 foo_unittest.cc 中,有一个注释掉的 main 函数:

#if 0
int main(int argc, char *argv[])
{
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
#endif


这个 main 函数是 GoogleTest 的入口,会调用 testing::InitGoogleTest(&argc, argv) 初始化 GoogleTest 框架,随后的 RUN_ALL_TESTS() 会搜集所有的测试函数,并运行。

实际上这也是 libtest_main.a 的所有代码,在 googletest v1.11 的代码中,完整的 gtest_main.cc 的代码如下:

/* file: googletest-src/googletest/src/gtest_main.cc */
#include <cstdio>
#include "gtest/gtest.h"

#if GTEST_OS_ESP8266 || GTEST_OS_ESP32
#if GTEST_OS_ESP8266
extern "C" {
#endif
void setup() {
  testing::InitGoogleTest();
}

void loop() { RUN_ALL_TESTS(); }

#if GTEST_OS_ESP8266
}
#endif

#else

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


在上面的代码中,针对 GTEST_OS_ESP8266 和 GTEST_OS_ESP32 的情形,单独定义了setup和loop函数,其余的代码都走 else 部分的 main 函数。

实际上 setup + loop 函数的功能和 main 函数是一样的,我猜测是 GTEST_OS_ESP8266 和 GTEST_OS_ESP32 默认有了 main 函数,所以这里无法再次定义 main 函数,于是将 googletest 的入口放入到 setup 和 loop 函数中。

可见,foo_unittest.cc的 main 函数 和 gtest_main.cc 的代码是一样的。

如果自己定义了 main 函数,就不再需要链接 libgtest_main.a 了,否则的话就需要向上面那样在编译时链接上 libgtest_main.a

3. 总结
基于前面说得比较繁琐,这里将步骤总结如下:

1:保存文中 2.1 节的 CMakeLists.txt 和 gtest.mk
2:运行 make -f gtest.mk 会自动下载并编译输出 gtest 的库文件和头文件
3:执行如下编译指令编译并链接测试代码和 gtest 库文件
$ g++ gcd.c factorial.c foo_unittest.cc -o footest -I. -Igtest/include -Lgtest/lib -lgtest -lgtest_main -lpthread

命令中的 gcd.c 和 factorial.c 可以根据需要替换成一个或多个 C 代码文件, foo_unittest.cc 替换成自己的单元测试文件(里面包含了多组 TEST 或 TEST_F测试)。


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1150954.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全(黑客)—小白自学路线

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

商业模式画布的9大模块全解读,产品经理不可不知!

“商场如战场”&#xff0c;在当今瞬息万变的商业环境中&#xff0c;创造出独特且创新的商业模式是每个企业家、策略家和决策者的首要任务。为了在激烈的市场竞争中取得优势&#xff0c;我们需要一个强大且直观的工具来帮助我们规划和塑造公司的商业模式&#xff0c;这个经常被…

从零开始实现神经网络(一)_NN神经网络

参考文章&#xff1a;神经网络介绍 一、神经元 这一神经网络的基本单元&#xff0c;神经元接受输入&#xff0c;对它们进行一些数学运算&#xff0c;并产生一个输出。 这里有三步。 首先&#xff0c;将每个输入&#xff08;X1&#xff09;乘以一个权重&#xff1a; 接下来&…

如何备份和恢复微信聊天记录?微信聊天记录1分钟轻松备份和恢复。

微信是一个非常流行的应用程序&#xff0c;不仅在中国&#xff0c;而且在全世界。这个应用程序允许来自其他国家的用户与他们的中国朋友进行群聊、语音消息、视频通话、发送贴纸或 GIF 以及照片。它可以为学生和商人/女性发送重要文件&#xff0c;以及位置共享以防游客在访问中…

内网穿透实现在外远程访问NAS威联通(QNAP)

文章目录 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 前言 购入威联通NAS后&#xff0c;很多用户对于如何在外在公网环境下的远程访问威联通NAS…

系列四十、请谈一下Spring中事务的传播行为

一、概述 事务的传播行为指的是当一个事务方法被另一个事务方法调用时&#xff0c;这个事务方法应该如何进行。事务的传播行为至少发生在两个事务方法的嵌套调用中才会出现。 二、传播行为分类

专门解决数学问题的大模型

01 项目介绍 LLEMMA&#xff1a;一个专门解决数学问题的开源大语言模型&#xff0c;能力超过所有已知的开源模型 LLEMMA由多个大学和Eleuther AI公司共同研发&#xff0c;模型能够理解和生成数学表达式、解决数学问题&#xff0c;并与其他计算工具&#xff08;如Python解释器…

Jenkins Gerrit Trigger插件配置

安装Jenkins 以Jenkins 2.361.1版本为例 docker pull jenkins/jenkins:2.361.1运行容器&#xff0c;将主机的8080端口映射到容器的8080端口&#xff0c;同时将主机的50000端口映射到容器的50000端口&#xff08;用于构建代理&#xff09; docker run -d -p 8080:8080 -p 500…

操作系统(Linux)外壳程序shell 、用户、权限

文章目录 操作系统和shell外壳Linux用户普通用户的创建和删除用户的切换 Linux 权限Linux 权限分类文件访问权限修改文件的权限权限掩码粘滞位 大家好&#xff0c;我是纪宁。 这篇文章将介绍 Linux的shell外壳程序&#xff0c;Linux用户切换机Linux权限的内容。 操作系统和shel…

基于SpringBoot的养老院信息管理系统

基于SpringBoot的养老院信息管理系统&#xff0c;java项目&#xff0c;springboot项目&#xff0c;idea都能打开运行。 推荐环境配置&#xff1a;idea jdk1.8 maven mysql5.5/mysql5.7 主要技术: SpringBoot&#xff0c;MySql&#xff0c;ajax&#xff0c;MyBatis 本系统的主要…

Vue 路由指南:畅游单页应用的地图(Vue Router 和 <router-view>)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

STM32F407的系统定时器

文章目录 系统定时器SysTick滴答定时器寄存器STK_CTRL 控制寄存器STK_LOAD 重载寄存器STK_VAL 当前值寄存器STK_CALRB 校准值寄存器 初始化 Systick 定时器SysTick_InitSysTick_CLKSourceConfig delay_us寄存器delay_us库函数delay_xms短时delay_ms长时SysTick_Config 系统定时…

电阻距离------Resistance distance

原来的解释来自维基百科&#xff1a;https://en.wikipedia.org/wiki/Resistance_distance 在图论中&#xff0c;简单连通图G的两个顶点之间的电阻距离等于电网上两个等效点之间的电阻&#xff0c;电网被构造为与G相对应&#xff0c;每条边被一欧姆的电阻代替。它是图上的度量。…

Jenkins安装(Jenkins 2.429)及安装失败解决(Jenkins 2.222.4)

敏捷开发与持续集成 敏捷开发 敏捷开发以用户的需求进化为核心&#xff0c;采用迭代、循序渐进的方法进行软件开发。在敏捷开发中&#xff0c;软件项目在构建初期被切分成多个子项目&#xff0c;各个子项目的成果都经过测试&#xff0c;具备可视、可集成和可运行使用的特征。…

geatpy-遗传算法

参考: geatpy 官网 关注的点 在实操过程中,主要遇到以下问题: 不等式约束代码里怎么写?几种书写方式之间有何细节差别要注意入门案例一 包含不等式约束 import geatpy as ea import numpy as np# 构建问题 r = 1 # 目标函数需要用到的额外数据 @ea.Problem.single def …

黑豹程序员-架构师学习路线图-百科:PowerDesigner数据库建模的行业标准

PowerDesigner最初由Xiao-Yun Wang&#xff08;王晓昀&#xff09;在SDP Technologies公司开发完成。 目前PowerDesigner是Sybase的企业建模和设计解决方案&#xff0c;采用模型驱动方法&#xff0c;将业务与IT结合起来&#xff0c;可帮助部署有效的企业体系架构&#xff0c;并…

公众号推送消息自动化的简单方法

作为公众号运营者&#xff0c;你是否厌烦了每天都要手动推送内容给用户&#xff1f;现在&#xff0c;有了乔拓云公众号助手工具&#xff0c;你可以告别手动推送的繁琐&#xff0c;实现公众号的自动推送功能。下面&#xff0c;我们来看看如何操作。 第一步&#xff1a;注册并登录…

ThreadLocal 会出现内存泄漏吗?

ThreadLocal ThreadLocal 是一个用来解决线程安全性问题的工具。它相当于让每个线程都开辟一块内存空间&#xff0c;用来存储共享变量的副本。然后每个线程只需要访问和操作自己的共享变量副本即可&#xff0c;从而避免多线程竞争同一个共享资源。它的工作原理很简单&#xff0…

k8s中label标签、deployment控制器、service、ipvs管理简介

目录 一.label管理 1.label的作用和特点 2.标签的查询和筛选 &#xff08;1&#xff09;等式型 &#xff08;2&#xff09;集合型 3.命令行打标签用法示例 &#xff08;1&#xff09;为资源对象添加多个标签 &#xff08;2&#xff09;更该原有标签 &#xff08;3&…