cpp-stub使用教程(基于Gtest)

一、简介

配合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)
...
...
...

参考

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜欢打篮球的普通人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值