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

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

问题描述

我正在探索TDD(测试驱动开发)来测试我用C和C ++编写的代码.我选择使用Google测试作为单元测试框架.我选择使用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的多个定义"),我需要在代码中写"属性((弱))".在功能(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的书"

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与生产代码库和WITHtest2.cpp一起构建.在链接期间,mock函数将具有优先级.
  • 第二步,将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.

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

    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天全站免登陆