如何模拟采用已定义的 lambda 函数并覆盖 lambda 函数的函数
How to mock a function that takes an already defined lambda function and have coverage over lambda function
我正在使用 googletest 学习单元测试,但不确定如何覆盖函数。
简而言之,我需要了解在母函数 M 中定义的 lambda 函数(假设为 L)。M 调用函数 C(在另一个文件中定义),该函数将 lambda 函数 L(回调)作为参数。我正在为 M 编写单元测试,需要调用 M,模拟外部函数 M,同时确保模拟 C 并正确覆盖 L。
简而言之,object->M有L,M调用C(L)。
在 google 测试中有没有办法做到这一点?
我已经尝试过的大致形状:
/* source code */
/* header */
struct Object
{/*struct methods*/
//M declaration
int M();
};
/* cpp file */
int M()
{
/* some operations */
auto L = [](int number){/* operations, returns 0; */};
int store; //storing the result of C
store = C(L);
}
/* other file */
int C(int* L(int))
{
/* some operations */
L(1);
return some_int;
}
单元测试文件代码:
#include <gmock/gmock.h>
#include <gtest.h>
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::DoAll;
using ::testing::SetArgReferee;
using ::testing::SetArgPointee;
using ::testing::SetArrayArgument;
using ::testing::_;
/* mock class*/
class MockedFunctions
{
public:
/* going to put 5 ints for the sake of the example */
MOCK_METHOD1(C, int(int));
};
class TestObject : public ::testing::Test
{
public:
TestObject(){}
~TestObject(){}
protected:
Object *objectInstance;
virtual void SetUp()
{ objectInstance = new Object;}
virtual void TearDown()
{ delete objectInstance;}
};
/* test for function */
TEST_F(TestObject, test_M)
{
MockedFunctions test_C;
EXPECT_CALL(test_C, C(_))
.Times(1)
/* don't care about passed number to L */
.WillOnce(DoALL (SetArgPointee<0>(L(3)), Return(0));
/* coud put EXPECT_EQ as well */
objectInstance->M();
}
这让我在 .WillOnce 处出错,指出 L 未在此范围内声明。
注意,我不关心L的内容,只要覆盖就可以了。到目前为止我发现关于这个主题的建议是模拟我的 lambda 函数 L,我不想在这里做,因为我需要覆盖它的代码作为函数 M 的一部分。
虽然我不需要,但在这种情况下,严格使用 GTest 风格(因为我的导师不知道如何继续这个 L 函数覆盖)并且可以使用 C 的存根来强制使用L(我现在已经实现了这个版本,以便能够与其余代码一起编译),是否仍然可以严格使用 google 测试样式来获得此覆盖率?
谢谢!
终于找到解决办法,贴出来供大家参考;
我的解决方案是找到一种方法将我感兴趣的回调方法存储在容器内,为此创建辅助方法并从该向量中获取一些输出。
- 在包含 MOCK_METHODX() 声明的 class 内部,我首先使用 typedef 来更容易地识别我的函数。
- 然后,在私有块中声明将用于传递函数的向量和空指针。
- 获取向量位置 x 处内容的辅助方法(我遇到过在测试中多次 运行 我的模拟函数的情况,因此想看看每个调用是如何执行的),添加到向量,清除内容(为其他测试重置)等等。
MockedFunctions 类型的静态指针,用于在提取所需数据后为 "resume" 正常模拟控制行为提供对象。
//inside my header file.
#include <gmock/gmock.h>
#include <vector>
class MockedFunctions {
public:
//lets say function L has params (int, void*)
typedef int (myCb) (int arg_from_cb_L, void* objptr_from_cb_L));
MockedFunctions(){}
virtual ~MockedFunctions(){}
//giving C more arguments than just the callback L
MOCK_METHOD2(C, int(int arg1, int(*L)(int, void*));
private:
//note due to namespace limitations, I'm including the std prefix
std::vector<myCb> callback_storage;
void *cb_obj_copy;
public:
/* Multiple public blocks just for readability purposes.
* Note the internal usage of vector methods.
*/
void cb_clear(){
if(callback_storage.empty())
{
callback_storage.clear();
}
}
void cb_add(myCb callback){
callback_storage.push_back(callback);
}
myCb* cb_result_at(int i){
return callback_storage.at(i);
}
//to store one of the params from the passed callback
void cb_copy_obj(void* obj){
cb_obj_cb = obj;
}
void cb_get_obj_copy(){
return cb_obj_copy;
}
};
class TestMethods : public ::testing::Test
{
public:
static std::unique_ptr<MockedMethods> testMockedMethods;
TestMethods(){
/* as we call other methods from MockedMethods apart from C,
* it would trigger warnings. Using NiceMock hides them for
* cleaner output.
*/
testMockedMethods.reset(new ::testing::NiceMock<MockedMethods>());
}
~TestMethods() override{
testMockedMethods.reset();
}
virtual void SetUp() override {}
virtual void TearDown() override {}
};
现在,在我的 cpp 文件中,我定义了 C 并实例化了指针。
std::unique_ptr<MockedMethods> TestObject::testMockedMethods(new MockedMethods() );
int C ( int arg1, int(*L)(int arg1_cb, void* arg2_cb)){
TestMethods::testMockedMethods->cb_add(L);
TestMethods::testMockedMethods->cb_copy_obj(arg2_cb);
/* to avoid complications, took L's return type of int and put a 0
* directly, we don't care at this point since we will overload the
* method afterwards.
*/
return TestMethods::testMockedMethods->C(arg1, 0);
}
至于实际测试,放在你认为合适的地方:
class TestCaseClass : public TestMethods
{
public:
TestCaseClass(){}
~TestCaseClass(){}
protected:
//Here, object is the struct name mentioned previously in the question.
Object *instance;
// ...
virtual void SetUp()
{
instance = new Object();
// ...
}
virtual void TearDown()
{
delete instance;
}
};
TEST_F(TestCaseClass, test_M)
{
// testing method M from struct Object
/* Run tests normally on C, results will be stored. By our definition, a different object
* will be the one calling C, so pay attention to it.
*/
EXPECT_CALL(*testMockedMethods, C(_, _))
.Times(1)
.WillOnce(Return(1))
// I noticed at this point M above did not return anything, suppose it returns store
EXPECT_EQ(instance->M(), 1);
// Now to get coverage or data from our callback. First, get the method in cb_obj
MockedMethods::myObj* cb_method = testMockedMethods->cb_result_at(0);
// Now get one of its arguments, like that void*. Note we're using our get-ers
void* mock_object = testMockedMethods->cb_get_obj_copy();
/* And finally, call our method, finally getting coverage. You can pass
* various parameters for branching.
*/
cb_method(0, mock_object);
// Don't forget we clear the vector of contents in case we re-use it in another test.
testMockedMethods->cb_clear();
}
虽然不是最直接的解决方案,但它被证明是有效的,我尝试包含一些在创建测试时可能遇到的用例。
编码愉快!
我正在使用 googletest 学习单元测试,但不确定如何覆盖函数。 简而言之,我需要了解在母函数 M 中定义的 lambda 函数(假设为 L)。M 调用函数 C(在另一个文件中定义),该函数将 lambda 函数 L(回调)作为参数。我正在为 M 编写单元测试,需要调用 M,模拟外部函数 M,同时确保模拟 C 并正确覆盖 L。
简而言之,object->M有L,M调用C(L)。
在 google 测试中有没有办法做到这一点?
我已经尝试过的大致形状:
/* source code */
/* header */
struct Object
{/*struct methods*/
//M declaration
int M();
};
/* cpp file */
int M()
{
/* some operations */
auto L = [](int number){/* operations, returns 0; */};
int store; //storing the result of C
store = C(L);
}
/* other file */
int C(int* L(int))
{
/* some operations */
L(1);
return some_int;
}
单元测试文件代码:
#include <gmock/gmock.h>
#include <gtest.h>
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::DoAll;
using ::testing::SetArgReferee;
using ::testing::SetArgPointee;
using ::testing::SetArrayArgument;
using ::testing::_;
/* mock class*/
class MockedFunctions
{
public:
/* going to put 5 ints for the sake of the example */
MOCK_METHOD1(C, int(int));
};
class TestObject : public ::testing::Test
{
public:
TestObject(){}
~TestObject(){}
protected:
Object *objectInstance;
virtual void SetUp()
{ objectInstance = new Object;}
virtual void TearDown()
{ delete objectInstance;}
};
/* test for function */
TEST_F(TestObject, test_M)
{
MockedFunctions test_C;
EXPECT_CALL(test_C, C(_))
.Times(1)
/* don't care about passed number to L */
.WillOnce(DoALL (SetArgPointee<0>(L(3)), Return(0));
/* coud put EXPECT_EQ as well */
objectInstance->M();
}
这让我在 .WillOnce 处出错,指出 L 未在此范围内声明。
注意,我不关心L的内容,只要覆盖就可以了。到目前为止我发现关于这个主题的建议是模拟我的 lambda 函数 L,我不想在这里做,因为我需要覆盖它的代码作为函数 M 的一部分。
虽然我不需要,但在这种情况下,严格使用 GTest 风格(因为我的导师不知道如何继续这个 L 函数覆盖)并且可以使用 C 的存根来强制使用L(我现在已经实现了这个版本,以便能够与其余代码一起编译),是否仍然可以严格使用 google 测试样式来获得此覆盖率?
谢谢!
终于找到解决办法,贴出来供大家参考;
我的解决方案是找到一种方法将我感兴趣的回调方法存储在容器内,为此创建辅助方法并从该向量中获取一些输出。
- 在包含 MOCK_METHODX() 声明的 class 内部,我首先使用 typedef 来更容易地识别我的函数。
- 然后,在私有块中声明将用于传递函数的向量和空指针。
- 获取向量位置 x 处内容的辅助方法(我遇到过在测试中多次 运行 我的模拟函数的情况,因此想看看每个调用是如何执行的),添加到向量,清除内容(为其他测试重置)等等。
MockedFunctions 类型的静态指针,用于在提取所需数据后为 "resume" 正常模拟控制行为提供对象。
//inside my header file. #include <gmock/gmock.h> #include <vector> class MockedFunctions { public: //lets say function L has params (int, void*) typedef int (myCb) (int arg_from_cb_L, void* objptr_from_cb_L)); MockedFunctions(){} virtual ~MockedFunctions(){} //giving C more arguments than just the callback L MOCK_METHOD2(C, int(int arg1, int(*L)(int, void*)); private: //note due to namespace limitations, I'm including the std prefix std::vector<myCb> callback_storage; void *cb_obj_copy; public: /* Multiple public blocks just for readability purposes. * Note the internal usage of vector methods. */ void cb_clear(){ if(callback_storage.empty()) { callback_storage.clear(); } } void cb_add(myCb callback){ callback_storage.push_back(callback); } myCb* cb_result_at(int i){ return callback_storage.at(i); } //to store one of the params from the passed callback void cb_copy_obj(void* obj){ cb_obj_cb = obj; } void cb_get_obj_copy(){ return cb_obj_copy; } }; class TestMethods : public ::testing::Test { public: static std::unique_ptr<MockedMethods> testMockedMethods; TestMethods(){ /* as we call other methods from MockedMethods apart from C, * it would trigger warnings. Using NiceMock hides them for * cleaner output. */ testMockedMethods.reset(new ::testing::NiceMock<MockedMethods>()); } ~TestMethods() override{ testMockedMethods.reset(); } virtual void SetUp() override {} virtual void TearDown() override {} };
现在,在我的 cpp 文件中,我定义了 C 并实例化了指针。
std::unique_ptr<MockedMethods> TestObject::testMockedMethods(new MockedMethods() );
int C ( int arg1, int(*L)(int arg1_cb, void* arg2_cb)){
TestMethods::testMockedMethods->cb_add(L);
TestMethods::testMockedMethods->cb_copy_obj(arg2_cb);
/* to avoid complications, took L's return type of int and put a 0
* directly, we don't care at this point since we will overload the
* method afterwards.
*/
return TestMethods::testMockedMethods->C(arg1, 0);
}
至于实际测试,放在你认为合适的地方:
class TestCaseClass : public TestMethods
{
public:
TestCaseClass(){}
~TestCaseClass(){}
protected:
//Here, object is the struct name mentioned previously in the question.
Object *instance;
// ...
virtual void SetUp()
{
instance = new Object();
// ...
}
virtual void TearDown()
{
delete instance;
}
};
TEST_F(TestCaseClass, test_M)
{
// testing method M from struct Object
/* Run tests normally on C, results will be stored. By our definition, a different object
* will be the one calling C, so pay attention to it.
*/
EXPECT_CALL(*testMockedMethods, C(_, _))
.Times(1)
.WillOnce(Return(1))
// I noticed at this point M above did not return anything, suppose it returns store
EXPECT_EQ(instance->M(), 1);
// Now to get coverage or data from our callback. First, get the method in cb_obj
MockedMethods::myObj* cb_method = testMockedMethods->cb_result_at(0);
// Now get one of its arguments, like that void*. Note we're using our get-ers
void* mock_object = testMockedMethods->cb_get_obj_copy();
/* And finally, call our method, finally getting coverage. You can pass
* various parameters for branching.
*/
cb_method(0, mock_object);
// Don't forget we clear the vector of contents in case we re-use it in another test.
testMockedMethods->cb_clear();
}
虽然不是最直接的解决方案,但它被证明是有效的,我尝试包含一些在创建测试时可能遇到的用例。
编码愉快!