Как смоделировать функцию, которая принимает уже определенную лямбда-функцию и имеет покрытие над лямбда-функцией - PullRequest
1 голос
/ 28 марта 2019

Я изучаю юнит-тестирование с помощью googletest и не знаю, как выполнить функцию. Короче говоря, мне нужно получить покрытие над лямбда-функцией (скажем, L), определенной внутри материнской функции M. M вызывает функцию C (определенную в другом файле), которая принимает в качестве аргумента лямбда-функцию L (обратный вызов). Я пишу модульный тест для M, и мне нужно вызвать M, высмеивая внешнюю функцию M, а также проверяя, что C смоделирована и L покрыта должным образом.

Короче говоря, объект-> M имеет L, а M вызывает C (L).

Есть ли способ в google test сделать это?

Общая форма того, что я уже пробовал:

    /* 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, если оно покрыто. То, что я нашел до сих пор по этой теме, предложило смоделировать мою лямбда-функцию L, которую я не хочу здесь делать, поскольку мне нужно охватить ее код как часть функции M.

Хотя от меня не требуется, в этом случае строго использовать стиль GTest (поскольку мой преподаватель не знал, как поступить с этим покрытием функций L), и мог бы использовать заглушку для C, которая вынудила бы использовать L ( У меня есть эта версия, реализованная в настоящее время, чтобы иметь возможность компилировать с остальным кодом), возможно ли, тем не менее, получить это покрытие, используя строго стиль googletest?

Спасибо!

1 Ответ

0 голосов
/ 08 мая 2019

В конце концов нашел решение и разместил его для справки;

Мое решение состояло в том, чтобы найти способ сохранить интересующий меня метод обратного вызова внутри контейнера, создать для этого вспомогательные методы и получить некоторый вывод изэтот вектор.

  1. Внутри класса, содержащего объявления MOCK_METHODX (), я сначала использовал typedef, чтобы легче идентифицировать мою функцию.
  2. Затем, объявленный в приватном блоке вектор и указатель void, который будет использоваться для передачи функции.
  3. Вспомогательные методы для получения содержимого в позиции x вектора (у меня были случаигде я должен был запускать мою макетированную функцию несколько раз внутри теста и, таким образом, хотел видеть, как выполняется каждый вызов), добавление к вектору, очистка содержимого (сброс для других тестов) и т. д.
  4. Статический указательтипа MockedFunctions, используемые для предоставления объекта для «возобновления» нормального поведения управления макетом после того, как наши нужные данные были извлечены.

    //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();
       }

Хотя это не самое прямое решение, оно оказалось эффективным, и я попытался включитьнесколько случаев использования, с которыми можно столкнуться при создании своих тестов.

Счастливого кодирования!

...