C++类对象单元测试中的Mock使用

前言

  在进行单元测试时,我们想要测试自己缩写函数A,但是函数A却依赖于函数B,当函数B无法满足预期时就无法对函数A进行测试,主要由于下面几个原因:

  • 函数B依赖于硬件设备
  • 真实的函数B的返回值无法满足我们的预期
  • 团队开发中函数B尚未实现

  这时就需要对函数B进行打桩(仿真mock),使其达到我们预期的效果。

但是如同下面这种类型函数时,则需要深入下去,对基层的函数进行打桩:

typedef struct
{
	int num;
	char *ptr;
	char bot[20];
} ST_OBJ;

int fun_A()
{
	fun_B();
	return 0;
}
void fun_B(ST_OBJ *ptObj)
{
	fun_C(ptObj);
	fun_D(ptObj);
	fun_E(ptObj);
}

1. 下载网址

https://github.com/google/googletest

2. 为什么选择Google Mock

  • 首先当然是我们遇到了在前言中所说的问题。
  • 其次Goole的Mock也是代码,也是工程师写的,自己当然也可以去实现,但费时费力,难以避免出错,对于初学者很难。
  • 再有就是Google Mock使用方便,只要#include"gtest / gtest.h"和"gmock / gmock.h",你已经准备好了。
  • 且功能强大。

3. C++简单的例子

  Mock的功能强大,所以也有很多细节,但这些在后面再去了解,首先看一下如何利用它去写一个TEST_F().
  我们的代码中有一个这样的类:

class Machine{ 
public:
	int IsSuccess(char *ptBotle);
	virtual bool Transform(Gadget* g) = 0; 
protected: 
   	virtual void Resume(); 
private: 
   	virtual int GetTimeOut(); 
}; 

  总之就是有很多函数,现在我们想要去对这个类的函数进行Mock

3.1 使用流程

  1. 声明一个Mock类继承自原始类
  2. public部分使用MOCK_METHOD声明Mock函数
class MockMachine : public Machine { 
public: 
/* MOCK_METHOD参数数量(函数名, 返回值类型(参数列表)) */
 	MOCK_METHOD1(IsSuccess, int(char *ptBotle));
    MOCK_METHOD1(Transform, bool(Gadget* g)); 
    MOCK_METHOD0(Resume, void()); 
    MOCK_METHOD0(GetTimeOut, int()); 

  到此就完成了,你并不需要去实现这些函数,函数的预期返回都可以在TEST_F中去指定,是我太小看Mock了,如果函数实现是固定的,那么在不同场景需要不同返回值时你还要去修改这个函数的实现吗?

  1. TEST_F中使用它
/* 步骤: */
	1. 导入GoogleMock中的一些命名空间, 方便后续指定自己Mock函数的期望
	2. 创建Mock对象
	3. 指定你对Mock函数的期望(一个函数应该被调用多少次,返回什么)
	4. 打桩完成了,开始业务代码的测试函数, 并对其进行断言(ASSERT)
#include "path/to/mock-turtle.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::AtLeast;                      // 1
using ::testing::Return;
 
TEST(test_machine, ResumeTheMachine)
{
  	MockMachine OBJ_mach;                      // 2
  	
  	EXPECT_CALL(OBJ_mach, Resume())            // 3
      	.Times(AtLeast(1));

  	ASSERT_TRUE(Fun_Resume(&OBJ_mach));        // 4
}

其中业务代码Fun_Resume的实现大概是这样:

bool Fun_Resume(Machine *Ship)
{
	/*
		...
	*/
	Ship->Resume();
	return TRUE;
}

5. 指定自己的期望

  如果定义的这个Mock供大家使用,那么每个人的需求都是不同的。即使一个人,在不同的单元测试用例中,对于该函数的期望也不可能不一样。

5.1 通用语法

使用EXPECT_CALL宏函数来定义.
  它有两个参数:创建的Mock对象、方法及其参数
  宏之后则是一些可选的配置

EXPECT_CALL(mock_object, method(matcher1, matcher2, ...))
    .With(multi_argument_matcher) //多个参数的匹配方式指定
    .Times(cardinality) //执行多少次
    .InSequence(sequences) //函数执行顺序
    .After(expectations) //指定某个方法只能在另一个方法后执行
    .WillOnce(action) //一般使用return,指定一次调用的输出
    .WillRepeatedly(action) //一直输出
    .RetiresOnSaturation(); //期待调用不会被相同的函数期待所覆盖

/*--------------------------*/
using ::testing::Return;

EXPECT_CALL(pencil, GetX(5))
    .Times(5)
    .WillOnce(Return(100))
    .WillOnce(Return(150))
    .WillRepeatedly(Return(200));
    /*
	pencil对象的GetX()方法将被调用五次,第一次返回`100`,第二次返回`150`,然后每次返回`200`.
	并且函数`GetX()`指定入参是5.
	*/

或者

TEST(mockcpp simple sample)
{
    MOCKER(function) / MOCK_METHOD(mocker, method)
        .stubs() / defaults() / expects(once())
        [.before("some-mocker-id")]
        [.with(eq(3))]
        [.after("some-mocker-id")]
        .will(returnValue(1)) / .will(repeat(1, 20))
        [.then(returnValue(2))]
        [.id("some-mocker-id")]
}

1、mock C函数用MOCKER;
mock 类成员方法先用MockObject mocker;声明一个mock对象,再用MOCK_METHOD(mocker, method)。

2、紧跟着MOCKER/MOCK_METHOD之后的是stubs、或者defaults、或者expects,三个必须有一个。(这是与AMOCK不同的地方,在这个层次上确定这三个关键字必须有一个,可以让mock语法更清晰)
stubs 表示指定函数的行为,不校验次数。
expects 与stubs的区别就是校验次数。(.expects(once()) / .expects(never()) / .expects(exactly(123)))
defaults 表示定义一个默认的mock规范,但它优先级很低;如果后面有stubs或者expects重新指定函数行为,就会按照新指定的来运行。

3、用will指定函数的返回值;
如果要指定20次调用都返回1,则用.will(repeat(1, 20));
要指定第一次返回1,第二次返回2,第三次返回3,就用
.will(returnValue(1))
.then(returnValue(2))
.then(returnValue(3))
如果你指定了前三次的返回值依次为1、2、3,那么第四次、第五次调用,都是返回最后的返回值3。

4、用id给一个mock规范指定一个名字,然后可以用after、before来指定多个mock应该的调用顺序。
注意before在with前,after在with后,id在整个mock规范的最后。

4.2 指定:调用次数

  当一个函数不会被调用时,则应将其指定为0,这样貌似Mock也有一点断言的意思.

using ::testing::_; 
EXPECT_CALL(foo, Bar(5)) 
    .Times(0);

4.3 指定:函数执行顺序

在Spring Boot的JUnit单元测试,可以使用Mockito来创建mock数据,Mockito是一个流行的Java测试框架,可以模拟对象的行为和方法,以便更容易地测试代码。 下面是一个简单的示例,演示如何在Spring Boot的JUnit测试使用Mockito创建mock数据: 假设有一个UserService类,其有一个getUserById()方法,可以通过用户ID获取用户对象。 ```java @Service public class UserService { @Autowired private UserRepository userRepository; public User getUserById(Long id) { return userRepository.findById(id); } } ``` 现在我们想测试getUserById()方法,但是我们不想依赖于实际的数据库和UserRepository对象,而是想使用mock数据来测试它。我们可以使用Mockito来创建mock UserRepository对象,并在测试使用它。 ```java @RunWith(SpringRunner.class) @SpringBootTest public class UserServiceTest { @Autowired private UserService userService; @MockBean private UserRepository userRepository; @Test public void testGetUserById() { User user = new User(); user.setId(1L); user.setName("Alice"); Mockito.when(userRepository.findById(1L)).thenReturn(user); User result = userService.getUserById(1L); Assert.assertEquals("Alice", result.getName()); } } ``` 在这个示例,我们使用@MockBean注解创建了一个mock的UserRepository对象,并使用Mockito.when()方法来告诉Mockito当findById()方法被调用时应该返回什么对象。 然后,我们调用UserService的getUserById()方法,它将使用我们创建的mock对象而不是实际的数据库和UserRepository对象。 最后,我们使用JUnit的Assert.assertEquals()方法来验证getUserById()方法返回的用户对象是否符合预期。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值