C代码中集成gtest单元测试_gtest测试c语言_山河故人~的博客-CSDN博客
Linux安装gtest_gtest安装_山河故人~的博客-CSDN博客
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
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安装成功
将 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 库得到可执行文件进行测试
复制下面的代码分别保存为 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
特别说明:
由于是自动下载代码,所以需要 cmake 在 v3.14 以上;
gtest生成的两个库文件中,只有 libgtest.a 是必须的,另外一个库文件 libgtest_main.a是 gtest 运行的入口,实际上就是一个 main 函数,如果你在自己的单元测试中定义了自己的 main 函数去调用 gtest,那就不需要 libgtest_main.a。
待测试文件
这里的测试文件有两个, 分别是 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
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 函数。
然后使用下面的语句编译上面的代码生成可执行的测试文件:
$ 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
#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测试)。