如何模拟采用已定义的 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 测试样式来获得此覆盖率?

谢谢!

终于找到解决办法,贴出来供大家参考;

我的解决方案是找到一种方法将我感兴趣的回调方法存储在容器内,为此创建辅助方法并从该向量中获取一些输出。

  1. 在包含 MOCK_METHODX() 声明的 class 内部,我首先使用 typedef 来更容易地识别我的函数。
  2. 然后,在私有块中声明将用于传递函数的向量和空指针。
  3. 获取向量位置 x 处内容的辅助方法(我遇到过在测试中多次 运行 我的模拟函数的情况,因此想看看每个调用是如何执行的),添加到向量,清除内容(为其他测试重置)等等。
  4. 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();
       }

虽然不是最直接的解决方案,但它被证明是有效的,我尝试包含一些在创建测试时可能遇到的用例。

编码愉快!