Google 嵌入式系统测试

Google Test for embedded systems

我想使用 Google 测试为我的嵌入式应用程序软件编写单元测试。

这些测试将在用 C++ 编写的应用程序软件上执行。应用软件正在使用的驱动程序(例如I2CSPI),故障断言是用C写的。我的问题是:

  1. 从哪里开始比较好?我的意思是我可以阅读资源以了解有关在嵌入式环境中使用 Google 测试的更多信息。
  2. 如何模拟我的驱动程序文件?例如,如果我的 I2C 库中有一个 void read(uint8_t address) 函数,我该如何模拟这个函数,以便在我的 C++ class 中调用这个特定的函数?
  3. 这些用C 编写的驱动程序文件也包含在我的C++ 文件中。我尝试编译一个裸测试文件,只包括我的 C++ class 头文件,但遇到了编译问题,因为编译器找不到驱动程序头文件。我怎样才能避免这个问题?
  4. 使用代码管理失败断言 - 我的驱动程序库中的失败断言需要系统重置。我如何在测试中模拟它?
  1. 我不知道使用 Gtest 的任何特定资源 裸机目标测试,但一般来说是一个不错的起点 阅读 Gtest 底漆 并根据您的软件架构甚至 Gmock 文档。后者在测试应用程序相关 classes 而不仅仅是低级驱动程序时可能会很有用。
  2. 对此有多种选择。到目前为止我见过的最常见的是针对目标和平台 运行 测试有两种不同的实现。所以例如你可能有两个文件

    • ic2.c
    • i2c_x86.cpp

      并且根据您当前是针对目标平台还是针对测试平台进行编译,您可以使用其中之一。

    另一种选择是将 C 实现提升为 C++ 并围绕您的驱动程序编写一个 class 包装器。这将使您受益于 C++ 特性并使用依赖注入、继承、CRTP 等...

  3. 不确定我是否理解您的要求。

  4. Gtest 有一个 ASSERT_DEATH 测试,例如我当前的代码库包含以下测试

      // 2 byte message does not fit 14bit, assertion triggered
      ASSERT_DEATH(encode_datagram(make_datagram(0, 64, 0)), ".*");

我最近使用 gTest 为 Arm Cortex-M3 内核测试了 FAT 文件系统和引导加载程序实现,所以我将留下我的两分钱。

嵌入式软件测试存在无法通过模拟复制硬件环境的问题。我想出了三组测试:

A) 运行 在我的 PC 上的单元测试(我在 TDD 中使用)。我使用这些测试来开发我的应用程序逻辑。这是我需要mocking/stubbing的地方。我的公司使用硬件抽象层 (HAL),这就是我所嘲笑的。如果您想编写可测试的代码,最后一点是基础。

/* this is not testable */
my_register->bit0 = 1;

/* this is also not testable */
*my_register |= BIT0;

不要直接访问寄存器,使用可以模拟的简单 HAL 包装函数:

/* this is testable */
void set_bit(uint32_t* reg, uint8_t bit)
{
    *reg |= bit;
}

set_bit(my_register , BIT0);

后者是可测试的,因为您将模拟 set_bit 函数,从而打破对硬件的依赖。

B) 对目标进行单元测试。这是一组比 (A) 小得多的测试,但它仍然非常有用,尤其是对于测试驱动程序和 HAL 功能。这些测试背后的想法是我可以正确地测试我将模拟的功能。因为这个 运行s 在目标上,我需要它尽可能简单和轻便,所以我使用 MinUnit,它是一个 C header 文件。我已经 运行 on-target 在 Cortex-M3 内核和专有 DSP 代码(没有任何修改)上使用 MinUnit 进行了测试。我在这里也使用了 TDD。

C) 集成测试。我在这里使用 Python 和 Behave,以在目标上构建、下载和 运行 整个应用程序。

回答您的问题:

  1. 别人已经说过了,从gTest Primer, and don't worry about mocking, just getting the hang of using gTest. A good alternative that offers some memory checking (for leaks) is Cpputest开始。我稍微偏爱 gTest 语法来派生设置 类。 Cpputest 可以 运行 测试用 gTest 编写的。两者都是很棒的框架。

  2. 我用了Fake Function Frakework for mocking and stubbing. It's ridiculously simple to use and it offers everything expected from a good mocking framework: setting different return values, passing callbacks, checking the argument call history, etc. I want to give Ceedling了。到目前为止,FFF 一直很棒。

  3. 我不那样做。我用 C++ 编译器(在我的例子中是 g++)编译测试框架和我的测试,用 C 编译器 (gcc) 编译我的嵌入式代码,然后 link 它们在一起。从下面的示例中,您会看到我没有在 C 文件中包含 C++ headers。当 link 进行测试时,您将 link 除了要模拟的函数的 C 源文件之外的所有内容。

Managing failed assertions with the code - Failed assertions within my driver library, calls for a system reset. How can I emulate this within the tests?

我会模拟重置功能,添加一个回调来“重置”任何你需要的东西。

假设您要测试使用 read 函数的 read_temperature 函数。下面是一个使用 FFF 进行模拟的 gTest 示例。

hal_i2c.h

/* Low-level driver function */
uint8_t read(uint8_t address);

read_temperature.h

/* Reads the temperature from the I2C sensor */
float read_temperature(void);

read_temp.c

#include <hal_i2c.h>

float read_temperature(void)
{
    unit8_t raw_value;
    float temp;

    /* Read the raw value from the I2C sensor */
    raw_value = read(0xAB);

    /* Convert the raw value */
    temp = ((float)raw_value)/0.17+273;
    return temp;
}

test_i2c.cpp

#include <gtest/gtest.h>
#include <fff.h>

extern "C"
{
#include <hal_i2c.h>
#include <read_temperature.h>
}

DEFINE_FFF_GLOBALS;
// Create a mock for the uint8_t read(uint8_t address) function
FAKE_VALUE_FUNC(uint8_t , read, uint8_t);

TEST(I2CTest, test_read) {

    // This clears the FFF counters
    RESET_FAKE(read);

    // Set the raw temperature value
    read_fake.return_val = 0xAB;

    // Make sure that we read 123.4 degrees
    ASSERT_EQ((float)123.4, read_temperature());
}

希望对您有所帮助!干杯!