您如何使用 FFF 和 Google Test 在 C/C++ 中模拟和测试相同的函数? [英] How do you mock and test the same function in C/C++ with FFF and Google Test?

查看:49
本文介绍了您如何使用 FFF 和 Google Test 在 C/C++ 中模拟和测试相同的函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在探索 TDD(测试驱动开发)来测试我用 C 和 C++ 编写的代码.我选择使用 Google Test 作为单元测试框架.我选择使用 FFF 作为模拟框架.

I am exploring TDD (Test Driven Development) to test the code that I am writing in C and C++. I have chosen to use Google Test as the unit test framework. I have chosen to use FFF as the mocking framework.

我编写了一些测试并运行它们,效果很好.但是我遇到了一个我无法在网上找到任何参考的问题,我希望社区可以帮助我(这也可以帮助其他人).

I have written a few tests and run them and it works great. But I have come across an issue that I have not been able to find any reference to online and I was hoping the community could help me (and this would help others as well).

我遇到的问题是我想为函数 A1 编写一个测试(参见下面的场景 1).由于它调用了另外三个函数(B1、B2 和 B3)并且它们有很多依赖关系,所以我决定模拟它们,以便测试可能影响函数 A1 行为的各种场景.为了使模拟工作并避免链接器错误(例如B1 的多重定义"),我需要编写attribute((weak))".在函数(B1、B2 和 B3)之前.到现在为止还挺好.一切都很好.

The issue I have is that I would like to write a test for function A1 (see scenario 1 below). Since it calls three more functions (B1, B2 and B3) and they have a lot of dependencies, I decided to mock them so I could test various scenarios that could affect the behavior of function A1. In order for the mocks to work and avoid linker errors (such as "multiple definition of B1") I needed to write "attribute((weak))" before the functions (B1, B2 and B3). So far so good. All works great.

现在,考虑下面的场景 2.在这种情况下,我想在单独的测试中测试功能 B1.同样,我也会模拟它调用的函数(C1、C2、C3).然而,问题是我不能称真实的".B1 函数,因为如果这样做,我将获得我之前在 A1 函数测试中定义的模拟函数(在场景 1 下).

Now, consider scenario 2 below. In this scenario I would like to test function B1 in a separate test. Similarly, I will mock the functions it calls too (C1, C2, C3). However, the problem is that I can't call the "real" B1 function because if I do I will get the mock function I defined previously in the test for A1 function (under scenario 1).

那么在这种情况下我该怎么办?谢谢.

So what should I do in such a case? Thanks.

推荐答案

我在 James Grenning 的书中做了更多的挖掘和发掘 "嵌入式 C 的测试驱动开发"这个问题至少有两种解决方案.

I have done some more digging and unearthed in the book by James Grenning "Test Driven Development for Embedded C" at least two solutions to this problem.

  1. 链接时间替换
  2. 函数指针替换

链接时替换:

此选项的一般摘要:
总体而言,这似乎不太直观,难以实现和遵循,并且需要一些学习曲线.但是,好处是您不需要更改生产代码中的任何内容,这非常重要.

Link-time Substitution:

General summary of this option:
Overall, this seems rather less intuitive to implement and follow and requires a bit of a learning curve. However, the plus side is that you don't need to change anything in your production code which is very important.

在这种情况下,您需要使用 makefile 来执行以下步骤:

In this case, you need to work with makefiles to do the following steps:

  1. 将您的生产代码构建到库中

  1. Build your production code into a library

确保将您的测试分成不同的文件,以便需要使用某个函数作为模拟的测试与需要使用相同函数的原始实现的测试分开.

Be sure to separate your tests into different files so that tests that need to use a certain function as a mock are in separated from the tests that need to use the original implementation of that same function.

使用 make 文件,您需要对部分代码进行微构建,最后将它们组合在一起.例如,对于一个特定的函数,您希望在不同的测试中模拟和使用原始实现,例如,两个文件(test1.cpp 包含 func A1 的 mock 实现和test2.cpp 包含函数 A1 的 原始 实现)你会做:

With the make files you will need to make micro builds of parts of your code and finally combine them all together. As an example, for a specific function that you want to both mock and use the original implementation in different tests that are separated in, let's say, two files (test1.cpp contains mock implementation of func A1 and test2.cpp contains original implementation of function A1) you will do:

  • 首先,将 test1.cpp 与生产代码库一起构建,而无需 test2.cpp.模拟函数将在链接期间优先.
  • 其次,将 test2.cpp 与生产代码库一起构建,而无需 test1.cpp.函数 A1 库中的原始实现将在链接时优先考虑(因为它是那里唯一的实现).
  • 将创建的两个二进制文件组合成一个可执行文件.
  • 现在这三个步骤只是一个高级解释.我知道这并不理想,但仍然值得.我承认我不是自己做的,但我确实读过 James Grenning 的书,如果你愿意,他会在他的书中的附录 1(标题为开发系统测试环境)中更详细地解释它,你可能会看到 makefile 结构他在他的书中使用了代码示例:https://pragprog.com/titles/jgade/test-driven-development-for-embedded-c/

    Now these three steps are just a high-level explanation. I know it is not ideal but it is still worth something. I admit I didn't do it myself but I did read about it James Grenning's book and if you would like, he explains it in more detail in Appendix 1 (titled Development System Test Environment) in his book and you may see the makefile structure he used in his book-code-example here: https://pragprog.com/titles/jgade/test-driven-development-for-embedded-c/

    此选项的一般摘要:
    这更加直观和易于实现.但是,缺点是它需要对声明和定义函数的生产代码进行细微的更改.

    General summary of this option:
    This is much more intuitive and simple to implement. However, the downside is that it requires a subtle change to your production code where your function is declared and defined.

    假设您要模拟一个名为 A1 的函数,该函数在文件 Production.c 文件中定义为:

    Let's say you want to mock a function called A1 that is defined in the file Production.c file as:

    //Production.c    
    int A1(void)
    {
      ... original implementation written here
    } 
    

    在 Production.h 中声明为:

    and in Production.h it is declared as:

    //Production.h    
    int A1(void);
    

    因此您可以通过这种方式更改函数声明和定义:

    so you may change the function declaration and definition this way:

    //Production.c    
    int A1_original(void)
    {
      ... original implementation written here
    }
    
    int (*A1)(void) = A1_original; 
    

    在 Production.h 中声明为:

    and in Production.h it is declared as:

    //Production.h    
    extern int (*A1)(void);
    
    #ifdef TDD_ENABLED // use ifdef with TDD_ENABLED which is defined only in unit test project. This is because you want to declare the original implementation function as a public function so that you can freely assign it to the function pointer A1 in the test files.
        int A1_original(void);
    #endif
    

    现在,对于要使用原始函数实现的每个测试,只需按照更改前的相同方式调用它:

    Now, for each test in which you want to use the original function implementation simply call it the same way you would before the change:

    A1();
    

    如您所见,这意味着,在整个生产代码中,您无需更改函数的调用方式.您的测试文件也是如此.

    As you can see, this means that also, throughout your production code you don't need to change the way the function is called. This is also true to your testing files.

    现在,如果您想为此函数使用模拟,您只需执行以下操作:

    Now, in case you want to use a mock for this function you simply do the following:

    //Test1.cpp
    int Fake_A1(void)
    {
       ... fake function implementation
    }
    
    TEST(test_group_name,test_name)
    {
        int (*temp_holder)(void) = A1;  // hold the original pointer in a temp pointer
        A1 = Fake_A1;      // assign A1 to call the mock function 
    
        ... run all the test here
    
       A1 = temp_holder; // assign A1 to call the original function back again so that the mock function is used only in the scope of this test
    }
    

    理想情况下,如果您打算进行多个此类测试,您可以在使用具有 Setup()Teardown() 并使用如下测试夹具 (TEST_F):

    Ideally, if you intend to have multiple such tests you would make the assignment to the mock and reassignment to the original function while using a class with Setup() and Teardown() and using a test fixture (TEST_F) like this:

    //Test1.cpp
    class A1_Func_Test : public ::testing::Test
    {
    protected:
        int (*temp_holder)(void) = A1;  // hold the original pointer in a temp pointer
        virtual void SetUp()
        {
            A1 = Fake_A1;  // assign A1 to call the mock function
        }
    
        virtual void TearDown()
        {
            A1 = temp_holder; // assign A1 to call the original function back again so that the mock function exists only in the scope of this test
        }
    };
    
    TEST_F(A1_Func_Test , Test1_A1)
    {
        write tests here...
    }
    
    TEST_F(A1_Func_Test , Test2_A1)
    {
        write tests here...
    }
    

    如何使用 FFF Mock Framework 实现函数指针替换:

    按照上述说明进行操作后,应对您的生产代码文件(Production.c 和 Production.h)进行相同的更改.但是,对于您的单元测试文件,您只需执行以下操作,以防您想模拟该函数(如果您想测试该函数而不模拟它,则只需定期调用它):

    How to implement function pointer substitution with FFF Mock Framework:

    After following the instructions written above, the same changes should be made to your production code files (Production.c and Production.h). However, for your unit test files you simply do the following in case you want to mock the function (if you want to test the function without mocking it then just call it regularly):

    //Test1.cpp
    //Using FFF Mocking framework:
    
    DEFINE_FFF_GLOBALS;
    FAKE_VALUE_FUNC(int, A1_mock);
    
    int A1_custom(void)
    {
       write code here for mock function implementation...
    }
    
    TEST(test_group_name,test_name)
    {
        int (*temp_holder)(void) = A1;  // hold the original pointer in a temp pointer
        // setting customized mock function for this test
        A1_mock_fake.custom_fake = A1_custom;
        A1 = A1_mock;
    
        // simple example of a test using the the FFF framework:
        int x;
        x = A1();
        ASSERT_EQ(A1_mock_fake.call_count, 1);
    
        // assign A1 to call the original function back again so that the mock function exists only in the scope of this test
        A1 = temp_holder;
        RESET_FAKE(A1_mock); // reset all parameters of the mock function used so when used in a subsequent test we will start "clean"
    }
    

    总结

    我相信这回答了关于如何去做和做我所问的问题.

    Summary

    I believe this answers the questions on how to go about and do what I had asked.

    这篇关于您如何使用 FFF 和 Google Test 在 C/C++ 中模拟和测试相同的函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆