一、简介
配合Gtest使用,因为Gtest的所有接口都是基于虚函数的;
此外,由于一些依赖接口不容易Stub或者Mock(或者写Stub test过于麻烦),因此在Gtest的基础上使用cpp-stub进行单元测试用例编写。
二、环境搭建
以ubuntu为例:
apt-get update -y
apt-get install -y libgtest-dev
apt-get install -y libgmock-dev
一个gtest sample:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
int Foo(int a, int b) { return a + b; }
TEST(FooTest, FooAddTest) {
EXPECT_EQ(2, Foo(1, 1));
EXPECT_EQ(6, Foo(2, 4));
}
编译:
root@65147725a2cb:/opt/test_gtest# g++ test.cc -l gtest -lgmock -lgtest_main -lpthread
Stub API 源码地址
将stub源码clone到本地一个位置
git clone git@github.com:coolxv/cpp-stub.git
cpp-stub-ext
cpp-stub-ext克隆到本地(Optional)
git clone git@gitee.com:master-roc/cpp-stub-ext.git
结合方法:
(1)将cpp-stub-ext与cpp-stub一起导入工
(2)修改cpp-stub的stub.h文件,将Stub类的私用函数和变量修改为保护型。
三、Stub cpp使用
1.使用教程
链接:doc_zh.md
例子大多来源于:link
1.1 不能打桩
- 不能对 exit 函数打桩,编译器做优化了
- 不能对纯虚函数打桩, 纯虚函数没有地址
- 不能对 lambda 函数打桩, lambda 函数获取不到地址
- 不能对静态函数打桩, 静态函数地址不可见.(但可以尝试使用 addr_any.h 接口获取地址)
1.2 单元测试编译选项, linux g++可用的
- -fno-access-control,private->public
- -fno-inline,禁用内联函数
- -Wno-pmf-conversions,允许成员函数转为普通函数指针
- -Wl,–allow-multiple-definition,允许重复定义
- -no-pie -fno-stack-protector,禁用栈保护(Stack Protector)功能
- -fprofile-arcs,用于生成代码覆盖率信息。
- -ftest-coverage,编译和链接时启用代码覆盖率分析。
1.3 代码覆盖率, linux g++使用方法
lcov -d build/ -z
lcov -d build/ -b ../../src1 --no-external -rc lcov_branch_coverage=1 -t ut -c -o ut_1.info
lcov -d build/ -b ../../src2 --no-external -rc lcov_branch_coverage=1 -t ut -c -o ut_2.info
lcov -a ut_1.info -a ut_2.info -o ut.info
genhtml -o report/ --prefix=`pwd` --branch-coverage --function-coverage ut.info
1.4普通函数打桩(非static)
// for linux and windows
#include "stub.h"
#include <iostream>
using namespace std;
int foo(int a) {
cout << "I am foo" << endl;
return 0;
}
int foo_stub(int a) {
cout << "I am foo_stub" << endl;
return 0;
}
int main() {
Stub stub;
stub.set(foo, foo_stub);
foo(1);
return 0;
}
编译以及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest# ./a.out
I am foo_stub
1.5 对象成员函数打桩
eg1
// for linux,__cdecl
#include "stub.h"
#include <iostream>
using namespace std;
class A {
int i;
public:
int foo(int a) {
cout << "I am A_foo" << endl;
return 0;
}
};
int foo_stub(void *obj, int a) {
A *o = (A *)obj;
cout << "I am foo_stub" << endl;
return 0;
}
int main() {
Stub stub;
stub.set(ADDR(A, foo), foo_stub);
A a;
a.foo(1);
return 0;
}
编译以及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest# ./a.out
I am foo_stub
eg2
// for windows,__thiscall
#include "stub.h"
#include <iostream>
using namespace std;
class A {
int i;
public:
int foo(int a) {
cout << "I am A_foo" << endl;
return 0;
}
};
class B {
public:
int foo_stub(int a) {
cout << "I am foo_stub" << endl;
return 0;
}
};
int main() {
Stub stub;
stub.set(ADDR(A, foo), ADDR(B, foo_stub));
A a;
a.foo(1);
return 0;
}
编译以及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest# ./a.out
I am foo_stub
1.6 静态成员函数打桩
// for linux and windows
#include "stub.h"
#include <iostream>
using namespace std;
class A {
int i;
public:
static int foo(int a) {
cout << "I am A_foo" << endl;
return 0;
}
};
int foo_stub(int a) {
cout << "I am foo_stub" << endl;
return 0;
}
int main() {
Stub stub;
stub.set(ADDR(A, foo), foo_stub);
A::foo(1);
return 0;
}
编译以及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest# ./a.out
I am foo_stub
1.7模板函数打桩
// for linux,__cdecl
#include "stub.h"
#include <iostream>
using namespace std;
class A {
public:
template <typename T> int foo(T a) {
cout << "I am A_foo" << endl;
return 0;
}
};
int foo_stub(void *obj, int x) {
A *o = (A *)obj;
cout << "I am foo_stub" << endl;
return 0;
}
int main() {
Stub stub;
stub.set((int(A::*)(int))ADDR(A, foo), foo_stub);
A a;
a.foo(5);
return 0;
}
编译及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest# ./a.out
I am foo_stub
// for windows,__thiscall
#include "stub.h"
#include <iostream>
using namespace std;
class A {
public:
template <typename T> int foo(T a) {
cout << "I am A_foo" << endl;
return 0;
}
};
class B {
public:
int foo_stub(int a) {
cout << "I am foo_stub" << endl;
return 0;
}
};
int main() {
Stub stub;
stub.set((int(A::*)(int))ADDR(A, foo), ADDR(B, foo_stub));
A a;
a.foo(5);
return 0;
}
编译及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest# ./a.out
I am foo_stub
1.8重载函数打桩
- 如果是struct是有问题的
// for linux,__cdecl
#include "stub.h"
#include <iostream>
using namespace std;
class A {
int i;
public:
int foo(int a) {
cout << "I am A_foo_int" << endl;
return 0;
}
int foo(double a) {
cout << "I am A_foo-double" << endl;
return 0;
}
};
int foo_stub_int(void *obj, int a) {
A *o = (A *)obj;
cout << "I am foo_stub_int" << a << endl;
return 0;
}
int foo_stub_double(void *obj, double a) {
A *o = (A *)obj;
cout << "I am foo_stub_double" << a << endl;
return 0;
}
int main() {
Stub stub;
stub.set((int(A::*)(int))ADDR(A, foo), foo_stub_int);
stub.set((int(A::*)(double))ADDR(A, foo), foo_stub_double);
A a;
a.foo(5);
a.foo(1.1);
return 0;
}
编译及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest#
root@65147725a2cb:/opt/test_gtest#
root@65147725a2cb:/opt/test_gtest# ./a.out
I am foo_stub_int5
I am foo_stub_double1.1
1.9 虚函数打桩
// for linux
#include "stub.h"
#include <iostream>
using namespace std;
class A {
public:
virtual int foo(int a) {
cout << "I am A_foo" << endl;
return 0;
}
};
int foo_stub(void *obj, int a) {
A *o = (A *)obj;
cout << "I am foo_stub" << endl;
return 0;
}
int main() {
typedef int (*fptr)(A *, int);
using virtual_function_type = int (*)(A *, int);
virtual_function_type A_foo =
(virtual_function_type)(&A::foo); // 获取虚函数地址
Stub stub;
stub.set(A_foo, foo_stub);
A a;
a.foo(1);
return 0;
}
编译及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
test1.cc: In function 'int main()':
test1.cc:25:38: warning: converting from 'int (A::*)(int)' to 'virtual_function_type' {aka 'int (*)(A*, int)'} [-Wpmf-conversions]
25 | (virtual_function_type)(&A::foo); // 获取虚函数地址
| ^
root@65147725a2cb:/opt/test_gtest# ./a.out
I am foo_stub
重载的虚函数
//for linux gcc
#include<iostream>
#include "stub.h"
using namespace std;
class A{
int i;
public:
virtual int foo(int a){
cout<<"I am A_foo"<<endl;
return 0;
}
virtual int foo(double a){
cout<<"I am A_foo"<<endl;
return 0;
}
};
int foo_stub(void* obj, int a)
{
A* o= (A*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
typedef int (*fptr)(A*,int);
fptr A_foo = (fptr)((int(A::*)(int))&A::foo);
Stub stub;
stub.set(A_foo, foo_stub);
A a;
a.foo(1);
return 0;
}
1.10 内联函数打桩
//for linux
//添加-fno-inline编译选项,禁止内联,能获取到函数地址,打桩参考上面。
1.11 第三方库私有成员函数打桩
// for linux
// 被测代码添加-fno-access-private编译选项,-fno-access-private 可以让你绕过访问权限检查(好像并不需要添加该编译选项,该编译选项也不存在?)
// 无源码的动态库或静态库无法自己编译,需要特殊技巧获取函数地址
#include "addr_pri.h"
#include "stub.h"
#include <iostream>
using namespace std;
class A {
int a;
int foo(int x) {
cout << "I am A_foo " << a << endl;
return 0;
}
static int b;
static int bar(int x) {
cout << "I am A_bar " << b << endl;
return 0;
}
};
ACCESS_PRIVATE_FIELD(A, int, a);
ACCESS_PRIVATE_FUN(A, int(int), foo);
ACCESS_PRIVATE_STATIC_FIELD(A, int, b);
ACCESS_PRIVATE_STATIC_FUN(A, int(int), bar);
int foo_stub(void *obj, int x) {
A *o = (A *)obj;
cout << "I am foo_stub" << endl;
return 0;
}
int bar_stub(int x) {
cout << "I am bar_stub" << endl;
return 0;
}
int main() {
A a;
auto &A_a = access_private_field::Aa(a);
auto &A_b = access_private_static_field::A::Ab();
A_a = 1;
A_b = 10;
call_private_fun::Afoo(a, 1);
call_private_static_fun::A::Abar(1);
auto A_foo = get_private_fun::Afoo();
auto A_bar = get_private_static_fun::A::Abar();
Stub stub;
stub.set(A_foo, foo_stub);
stub.set(A_bar, bar_stub);
call_private_fun::Afoo(a, 1);
call_private_static_fun::A::Abar(1);
return 0;
}
编译及运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest# ./a.out
I am A_foo 1
I am A_bar 10
I am foo_stub
I am bar_stub
1.12可变惨函数打桩
#include<iostream>
#include <stdarg.h>
#include "stub.h"
using namespace std;
double average(int num, ...)
{
va_list valist;
double sum = 0.0;
int i;
va_start(valist, num);
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
va_end(valist);
cout<<"I am foo"<<endl;
return sum/num;
}
double average_stub(int num, ...)
{
va_list valist;
double sum = 0.0;
int i;
va_start(valist, num);
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
va_end(valist);
cout<<"I am foo_stub"<<endl;
return sum/num;
}
int main()
{
cout << "Average of 2, 3, 4, 5 = " << average(4, 2,3,4,5) << endl;
Stub stub;
stub.set(average, average_stub);
cout << "Average of 2, 3, 4, 5 = " << average(4, 2,3,4,5) << endl;
}
1.13 仿函数打桩
#include<iostream>
#include "stub.h"
using namespace std;
class Foo
{
public:
void operator() (int a)
{
cout<<"I am foo"<<endl;
}
};
int foo_stub(void* obj, int a)
{
Foo* o= (Foo*)obj;
cout<<"I am foo_stub"<<endl;
return 0;
}
int main()
{
Stub stub;
stub.set(ADDR(Foo,operator()), foo_stub);
Foo foo;
foo(1);
return 0;
}
1.14 C语言标准库函数打桩
#include "stub.h"
#include <cstdio>
#include <iostream>
using namespace std;
int foo(int a) {
puts("I am foo\n");
return 0;
}
int printf_stub(const char *format, ...) {
cout << "I am printf_stub" << endl;
return 0;
}
int main() {
Stub stub;
stub.set(puts, printf_stub);
foo(1);
return 0;
}
1.15 默认析构函数打桩(难)
#include "stub.h"
#include <iostream>
using namespace std;
template <class T> void *get_dtor_addr(bool start = true) {
// the start vairable must be true, or the compiler will optimize out.
if (start)
goto Start;
// This line of code will not be executed.
// The purpose of the code is to allow the compiler to generate the assembly
// code that calls the constructor.
{
T();
Call_dtor:;
;
}
Start:
// The address of the line of code T() obtained by assembly
char *p =
(char
*)&&Call_dtor; // https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
// CALL rel32
void *ret = 0;
char pos;
char call = 0xe8;
do {
pos = *p;
if (pos == call) {
ret = p + 5 + (*(int *)(p + 1));
}
} while (!ret && (--p));
return ret;
}
class A {
public:
A() { cout << "I am A_constructor" << endl; }
~A() {
{ cout << "I am A_dtor" << endl; }
}
};
class B {
public:
B() { cout << "I am B_constructor" << endl; }
~B() { cout << "I am B_dtor" << endl; }
};
int main() {
Stub stub;
auto xa = get_dtor_addr<A>();
auto xb = get_dtor_addr<B>();
stub.set(xa, xb);
A aa;
return 0;
}
1.16 默认构造函数打桩
//for linux
#include<iostream>
#include "stub.h"
using namespace std;
template<class T>
void * get_ctor_addr(bool start = true)
{
//the start vairable must be true, or the compiler will optimize out.
if(start) goto Start;
Call_Constructor:
//This line of code will not be executed.
//The purpose of the code is to allow the compiler to generate the assembly code that calls the constructor.
T();
Start:
//The address of the line of code T() obtained by assembly
char * p = (char*)&&Call_Constructor;//https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
//CALL rel32
void * ret = 0;
char pos;
char call = 0xe8;
do{
pos = *p;
if(pos == call)
{
ret = p + 5 + (*(int*)(p+1));
}
}while(!ret&&(++p));
return ret;
}
class A {
public:
A(){cout << "I am A_constructor" << endl;}
};
class B {
public:
B(){cout << "I am B_constructor" << endl;}
};
int main()
{
Stub stub;
auto xa = get_ctor_addr<A>();
auto xb = get_ctor_addr<B>();
stub.set(xa, xb);
A aa;
return 0;
}
1.17 使用AddrAny对lambda函数打桩和对任意函数打桩暂缺有问题
四、 cpp-stub-ext使用(使用lambda打桩,Optional)
基于cpp-stub的功能扩展模块,提供更加丰富,方便的功能。 更加高效地在单元测试中打桩。
基于Linux 系统与 gcc/g++编译器开发
使用说明:
使用lamda函数作为打桩函数,需调专用接口StubExt::set_lamda。
lamda函数可以使用参数和无参数:
为常规函数打桩,lamda有参数时lamda函数的参数列表必须与被打桩的函数参数一致。
为成员函数打桩,lamda有参数时lamda函数的第一个参数必须为该类的指针,接着是该成员函数的参数列表。
为常规函数打桩,lamda无参数可直接使用。
为成员函数打桩,lamda无参数可直接使用。
五、Stub cpp和Gtest结合使用
// Test framework
#include "gmock/gmock.h"
#include "gtest/gtest.h"
// Stub
#include "stub.h"
#include <string>
class Obj {
public:
Obj() { m_i = 1; }
virtual bool is_valid(std::string str) { return str.find("test") == 0; }
int get_number() { return m_i; }
private:
int m_i;
};
bool check_obj(Obj &obj) {
if (obj.is_valid("Kanotest")) {
std::cout << "invalid" << std::endl;
return false;
}
std::cout << obj.get_number() << std::endl;
return true;
}
// 可以放在mocks/目录下
class MockObj : public Obj {
public:
MOCK_METHOD1(is_valid, bool(std::string));
MOCK_METHOD0(get_number, int());
};
// Add useful testing functions to namespace
using ::testing::_;
using ::testing::AtLeast;
using ::testing::Exactly;
using ::testing::NiceMock;
using ::testing::Return;
// stub function
// 可以放在stub/目录下
int get_number_stub(void *obj) { return 5; }
// ========================可以放在fixtures/目录下
class StringsFixture : public ::testing::TestWithParam<std::string> {
public:
virtual void SetUp() {
// Do some setup
}
virtual void TearDown() {
// Do some tear down
}
};
// 宏接受三个参数:测试套件的名称、测试类的名称和参数值列表
INSTANTIATE_TEST_SUITE_P(StringsFixtureSuite, // Instantiation name
StringsFixture, // Fixture controller
::testing::Values( // Parameters
"Kano", "KanoTest", "Kano123", "123Kano", "^$K@",
"Kano%£("));
// ================================================end
TEST(get_number_test, returns_correct_value) {
Stub stub;
stub.set(ADDR(Obj, get_number), get_number_stub);
Obj obj;
ASSERT_EQ(obj.get_number(), 5);
}
TEST(ObjTest, test_obj_operations_with_mocks) {
NiceMock<MockObj> obj;
EXPECT_CALL(obj, is_valid(_)).Times(Exactly(1)).WillRepeatedly(Return(false));
// get_number并不是虚函数哦
ON_CALL(obj, get_number()).WillByDefault(Return(7));
EXPECT_TRUE(check_obj(obj));
}
TEST_P(StringsFixture, test_string_valid) {
// Retrieve the test parameter from the fixture
auto test_str = GetParam();
std::cout << "test_str=" << test_str << std::endl;
Obj obj;
ASSERT_EQ(obj.is_valid(test_str), test_str.find("Kano") == 0);
}
// Reference: https://github.com/KanoComputing/googletest-sample
编译并运行
root@65147725a2cb:/opt/test_gtest# g++ test1.cc -l gtest -lgmock -lgtest_main -lpthread -I ../cpp-stub/src/
root@65147725a2cb:/opt/test_gtest# ./a.out
Running main() from /build/googletest-j5yxiC/googletest-1.10.0/googletest/src/gtest_main.cc
[==========] Running 8 tests from 3 test suites.
[----------] Global test environment set-up.
[----------] 1 test from get_number_test
[ RUN ] get_number_test.returns_correct_value
[ OK ] get_number_test.returns_correct_value (0 ms)
[----------] 1 test from get_number_test (0 ms total)
...
...
...