简体   繁体   English

您如何使用 FFF 和 Google Test 在 C/C++ 中模拟和测试相同的 function?

[英]How do you mock and test the same function in C/C++ with FFF and Google Test?

I am exploring TDD (Test Driven Development) to test the code that I am writing in C and C++.我正在探索 TDD(测试驱动开发)来测试我在 C 和 C++ 中编写的代码。 I have chosen to use Google Test as the unit test framework.我选择使用 Google Test 作为单元测试框架。 I have chosen to use FFF as the mocking framework.我选择使用 FFF 作为 mocking 框架。

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).但是我遇到了一个问题,我无法在网上找到任何参考资料,我希望社区可以帮助我(这也可以帮助其他人)。

The issue I have is that I would like to write a test for function A1 (see scenario 1 below).我遇到的问题是我想为 function A1 编写一个测试(参见下面的场景 1)。 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.由于它调用了另外三个函数(B1、B2 和 B3)并且它们有很多依赖关系,因此我决定模拟它们,以便测试可能影响 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).为了使模拟工作并避免 linker 错误(例如“B1 的多个定义”),我需要在函数(B1、B2 和 B3)之前编写“属性((弱))”。 So far so good.到目前为止,一切都很好。 All works great.一切都很好。

Now, consider scenario 2 below.现在,考虑下面的场景 2。 In this scenario I would like to test function B1 in a separate test.在这种情况下,我想在单独的测试中测试 function B1。 Similarly, I will mock the functions it calls too (C1, C2, C3).同样,我也会模拟它调用的函数(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).但是,问题是我不能调用“真实的”B1 function,因为如果我这样做,我将得到我之前在 A1 测试中定义的模拟 function(场景 94F41A 下定义) ZC1C425268E68385D1AB5074C1

So what should I do in such a case?那么在这种情况下我该怎么办呢? Thanks.谢谢。

模拟和测试相同的功能

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.在 James Grenning 所著的《嵌入式 C 的测试驱动开发》一书中,我做了一些更多的挖掘和发掘,至少有两个解决这个问题的方法。

  1. Link-time Substitution链接时替换
  2. Function Pointer Substitution Function 指针替换

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.但是,好处是您不需要更改生产代码中的任何内容,这非常重要。

In this case, you need to work with makefiles to do the following steps:在这种情况下,您需要使用 makefile 来执行以下步骤:

  1. Build your production code into a library将您的生产代码构建到库中

  2. 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.请务必将您的测试分成不同的文件,以便需要使用某个 function 作为模拟的测试与需要使用相同 function 的原始实现的测试分开。

  3. With the make files you will need to make micro builds of parts of your code and finally combine them all together.使用 make 文件,您将需要对部分代码进行微构建,最后将它们组合在一起。 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:例如,对于特定的 function,您希望在不同的测试中模拟和使用原始实现,这些测试被分隔在两个文件中(test1.cpp 包含 func A1 的模拟实现,test2.cpp 包含function A1) 你会做:

  • First, build test1.cpp together with the library of the production code and WITHOUT test2.cpp.首先,将 test1.cpp 与生产代码库一起构建,并且不包含 test2.cpp。 The mock function will take preference during link time.模拟 function 将在链接期间优先。
  • Second, build test2.cpp together with the library of the production code and WITHOUT test1.cpp.其次,将 test2.cpp 与生产代码的库一起构建,而无需 test1.cpp。 The original implemenation in the library of function A1 will take preference during link time (as it is the only one there). function A1 库中的原始实现将在链接期间优先(因为它是那里唯一的实现)。
  • combine the two binaries created together into one executable.将一起创建的两个二进制文件组合成一个可执行文件。

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/我承认我不是自己做的,但我确实读过 James Grenning 的书,如果您愿意,他会在他的书中的附录 1(标题为开发系统测试环境)中更详细地解释它,您可能会看到 makefile 结构他在这里使用了他的书代码示例: https://pragprog.com/titles/jgade/test-driven-development-for-embedded-c/

Function Pointer Substitution : Function 指针替换

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.但是,缺点是它需要对您的生产代码进行细微的更改,在该代码中声明和定义了 function。

Let's say you want to mock a function called A1 that is defined in the file Production.c file as:假设您要模拟一个名为 A1 的 function 文件 Production.c 文件中定义为:

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

and in Production.h it is declared as:在 Production.h 中声明为:

//Production.h    
int A1(void);

so you may change the function declaration and definition this way:因此您可以通过这种方式更改 function 声明和定义:

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

int (*A1)(void) = A1_original; 

and in Production.h it is declared as:在 Production.h 中声明为:

//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:现在,对于要使用原始 function 实现的每个测试,只需按照更改前的相同方式调用它:

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.如您所见,这意味着,在整个生产代码中,您无需更改调用 function 的方式。 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:现在,如果您想为此 function 使用模拟,您只需执行以下操作:

//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
}

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:理想情况下,如果您打算进行多个此类测试,您可以在使用带有Setup()和 Teardown( Teardown()的 class 并使用如下测试夹具 ( TEST_F ) 时对模拟进行分配并重新分配给原始 function:

//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...
}

How to implement function pointer substitution with FFF Mock Framework:如何使用 FFF Mock 框架实现 function 指针替换:

After following the instructions written above, the same changes should be made to your production code files (Production.c and Production.h).按照上面编写的说明进行操作后,应对您的生产代码文件(Production.c 和 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):但是,对于您的单元测试文件,您只需执行以下操作,以防您想模拟 function (如果您想在没有 mocking 的情况下测试 function )然后定期调用它:

//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.我相信这回答了关于如何 go 的问题并按照我的要求进行操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM