本文主要用于记录个人学习,同时提供给有同样需求的人作为参考。
例子源码github:https://github.com/904221150/gtest_sample
本文搭建单元测试环境用上的软件工具有:
gtest是一个跨平台的(Liunx、Mac OS X、Windows、Cygwin、Windows CE and
Symbian)C++单元测试框架,由google公司发布。gtest是为在不同平台上为编写C++测试而生成的。它提供了丰富的断言、致命和非致命判断、参数化、”死亡测试”等等。
Mockcpp是一个面向C/C++的mock框架。其指定(或模拟)函数的行为,可以对入参进行校验,对出参进行设定,还可以指定函数的返回值。
lcov 是GCC 测试覆盖率的前端图形展示工具。它通过收集多个源文件的
行、函数和分支的代码覆盖信息(程序执行之后生成gcda、gcno文件,上面的链接有讲) 并且将收集后的信息生成HTML页面。
生成HTML需要使用genhtml命令下文会解释。
git clone https://github.com/google/googletest
cd googletest/
mkdir mybuild
cd mybuild
cmake ../
make
sudo cp lib/libg*.a /usr/lib
sudo cp -rf ../googletest/include/gtest /usr/include/gtest
sudo cp -rf ../googlemock/include/gmock /usr/include/gmock
mockcpp下载链接:http://code.google.com/p/mockcpp,以下基于最新版本2.6
修改build_install.sh文件
#原文件
#!/bin/bash
# build and install
install_dir=/home/jelly/Programming/cpp-ut-project/cpp-ut-project/tools/mockcpp
function build() {
mkdir $1 2>/dev/null
cd $1
cmake -DCMAKE_INSTALL_PREFIX=$install_dir $2
make install
}
build ../../build_mockcpp_to_install ../mockcpp/mockcpp
cd ../mockcpp/mockcpp
#修改后
#!/bin/bash
# build and install
mkdir mockcpp
install_dir=../mockcpp
function build() {
mkdir $1 2>/dev/null
cd $1
cmake -DCMAKE_INSTALL_PREFIX=$install_dir $2
make install
}
build build ..
cd ../mockcpp
执行命令
./build_install.sh
cd mockcpp
sudo cp lib/libmockcpp.a /usr/lib
sudo cp -rf include/mockcpp /usr/include/mockcpp
执行命令
git clone https://github.com/linux-test-project/lcov.git
cd lcov/
make install
这个例子目标给fun这个功能做单元测试
文件目录
|--gtest
| |
| +--gtest.cpp
|--inc
| |
| +--fun.h
|--result
| |
| +(覆盖率结果)
|--src
| |
| +--fun.c
|--app.c
|--makefile
app.c 应用主函数
#include
#include"fun.h"
int main()
{
printf("fun return %d\n", fun(1));
return 0;
}
fun.c
#include
#include"fun.h"
int fun(int x)
{
int i = 0;
i = fun_inside(1);
return i > 0;
}
int fun_inside(int x)
{
printf("enter fun_inside\n");
return x - 1;
}
fun.h
#ifndef __FUN_H__
#define __FUN_H__
int fun(int x);
int fun_inside(int x);
#endif
gtest.cpp 单元测试用例
#include
#include
#include
#include"fun.h"
using namespace std;
class TestF : public testing::Test{
public:
virtual void SetUp()
{
printf("fun start test\n");
}
virtual void TearDown()
{
printf("fun end test\n");
}
};
TEST_F(TestF, fun1)
{
int ret = 0;
MOCKER(fun_inside)
.stubs()
.with(any())
.will(returnValue(2));
ret = fun(1);
EXPECT_EQ(1, ret);
}
class FooEnvironment : public testing::Environment{
public:
virtual void SetUp()
{
std::cout << "Foo FooEnvironment SetUP" << std::endl;
}
virtual void TearDown()
{
std::cout << "Foo FooEnvironment TearDown" << std::endl;
}
};
int main(int argc, char** argv)
{
testing::AddGlobalTestEnvironment(new FooEnvironment);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
主要测试在TEST_F(TestF, fun1)函数里,TEST_F为gtest提供的函数,其功能为进行单元测试。第一个参数TestF继承testing::Test,其目标是为该单元测试提供通用初始化调用功能,和通用结束时调用功能;第二个参数为测试名称。其输出结果可看后面输出章节。EXPECT_EQ为gtest提供的比较函数,这里会将fun(1)的输出结果与1进行比较。
有关gtest的详细知识,自行查阅相关资料。
MOCKER为mockcpp提供的打桩函数,这里给fun_inside打桩
.stubs() //不限打桩函数调用次数
.with(any()) //意思为任意输入都可调用该打桩函数
.will(returnValue(2)); //fun_inside被打桩后返回2
更详细的mockcpp使用可自行查询相关资料
makefile (这里的makefile写的不怎么好,临时输出都在./目录下)
objdir = obj
srcdir = src
SRC_PATH = . \
src
OBJ_PATH = obj
GTEST_PATH = gtest
SRC = $(foreach dir, $(SRC_PATH), $(wildcard $(dir)/*.c))
OBJ = $(patsubst $(srcdir)/%.c, $(objdir)/%.o, $(SRC))
SRC_GTEST_PATH = gtest \
src
SRC_GTEST = $(foreach dir, $(SRC_GTEST_PATH), $(wildcard $(dir)/*.c))
SRC_GTEST += $(foreach dir, $(SRC_GTEST_PATH), $(wildcard $(dir)/*.cpp))
CC = gcc
CXX = g++
INCLUDES = -Iinc
LIBS = -lgtest \
-lpthread \
-lgtest_main \
-lmockcpp
CFLAGS = -g -Wall -O0
CTESTFLAGS = -fprofile-arcs \
-ftest-coverage
target = my_gtest
all : $(target)
my_app : $(SRC)
@echo $(SRC)
@echo $(OBJ)
$(CC) $^ -o $@ $(INCLUDES) $(CFLAGS)
my_gtest : $(SRC_GTEST)
@echo $(SRC_GTEST)
$(CXX) $^ -o $@ $(CFLAGS) $(CTESTFLAGS) $(INCLUDES) $(LIBS)
./my_gtest
lcov -d . -t test -o test.info -b . -c
genhtml -o result test.info
$(INCLUDES) $(LIBS)
clean:
rm my_app
clean_test:
rm my_gtest
rm *.gcda
rm *.gcno
rm *.info
rm -rf result/*
gtest运行需要链接-lgtest -lpthread -lgtest_main
mockcpp运行需要链接-lmockcpp ,代码优化为 -O0
lcov远行前需要gcc内置的gcov提供覆盖率信息,需要-fprofile-arcs -ftest-coverage
$(CXX) $^ -o $@ $(CFLAGS) $(CTESTFLAGS) $(INCLUDES) $(LIBS)
编译后会生成my_gtest执行文件,以及.gcno文件
./my_gtest
这里执行了编译好的单元测试,同时生成.gcda文件
lcov -d . -t test -o test.info -b . -c
-d: 存放.gcda、.gcno文件的路径
-t: 目标的名称(随便填)
-o: 生成的覆盖率文件.info
-b: 相对目录的起始位置
-c: capture,采集覆盖率
genhtml -o result test.info
生成覆盖率报告
打开result/index.html即可查看
./my_gtest输出,这里运行了TEST_F(TestF, fun1)
./my_app输出,这里可以对比一下fun函数是如何运行的
lcov覆盖率页面,这里src的覆盖率不高,其原因为fun.c里的fun_inside函数未进行单元测试
附:覆盖率解释
代码覆盖率
在进行单元测试之后,我们当然希望能够直观的看到我们的测试都覆盖了哪些代码。
理论上,如果我们能做到100%的覆盖我们的所有代码,则可以说我们的代码是没有Bug的。
但实际上,100%的覆盖率要比想象得困难。对于大型项目来说,能够达到80% ~ 90%的语句覆盖率就已经很不错了。
覆盖率相关的可参考:https://cloud.tencent.com/developer/article/1552518