单元测试环境gtest,mockcpp,LCOV搭建与使用

单元测试环境gtest,mockcpp,LCOV搭建与使用

本文主要用于记录个人学习,同时提供给有同样需求的人作为参考。
例子源码github:https://github.com/904221150/gtest_sample

1. 环境介绍

本文搭建单元测试环境用上的软件工具有:

1.1 gtest

gtest是一个跨平台的(Liunx、Mac OS X、Windows、Cygwin、Windows CE and
Symbian)C++单元测试框架,由google公司发布。gtest是为在不同平台上为编写C++测试而生成的。它提供了丰富的断言、致命和非致命判断、参数化、”死亡测试”等等。

1.2 mockcpp

Mockcpp是一个面向C/C++的mock框架。其指定(或模拟)函数的行为,可以对入参进行校验,对出参进行设定,还可以指定函数的返回值。

1.3 LCOV

lcov 是GCC 测试覆盖率的前端图形展示工具。它通过收集多个源文件的
行、函数和分支的代码覆盖信息(程序执行之后生成gcda、gcno文件,上面的链接有讲) 并且将收集后的信息生成HTML页面。

生成HTML需要使用genhtml命令下文会解释。

2. 环境搭建

2.1 gtest环境搭建

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

2.2 mockcpp环境搭建

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

2.3 LCOV环境搭建

执行命令

git clone https://github.com/linux-test-project/lcov.git

cd lcov/

make install

3. 单元测试例子

这个例子目标给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

你可能感兴趣的:(单元测试)