【问题标题】:Google Test for embedded systems嵌入式系统的谷歌测试
【发布时间】:2020-03-05 18:58:36
【问题描述】:

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

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

  1. 从哪里开始比较好?我的意思是我可以阅读资源以了解有关在嵌入式环境中使用 Google Test 的更多信息。
  2. 如何模拟我的驱动程序文件?例如,如果我有一个 void read(uint8_t address) 函数,在我的 I2C 库中,我该如何模拟这个函数,以便在我的 C++ 类中调用这个特定的函数?
  3. 这些用 C 编写的驱动程序文件也包含在我的 C++ 文件中。我尝试编译一个仅包含我的 C++ 类头文件的裸测试文件,但遇到了编译问题,因为编译器找不到驱动程序头文件。我怎样才能避免这个问题?
  4. 使用代码管理失败的断言 - 我的驱动程序库中的失败断言需要系统重置。如何在测试中模拟这一点?

【问题讨论】:

  • 如何将嵌入式系统中 GoogleTest 的输出发送到 PC?
  • 一个想法是将读取 I2C 的调用替换为从文件读取的函数。这将是一个 stub 或模拟 I2C 函数。
  • 我强烈建议在 PC 上执行测试和模拟。
  • 我将沿着这条路线前进。测试将在 PC 上完成,而不是在微控制器上完成。我正在仔细研究这个资源 - Using GoogleTest and GoogleMock frameworks for embedded C。而这里的应用程序是用 C 编写的,而我的恰好是用 C++ 编写的,所以在我的案例中找出正确的方法有点令人困惑。
  • 这真的是嵌入式系统吗?这听起来像是一些伪装的 PC,“嵌入式 Linux”或类似的东西。还是……?

标签: c++ c unit-testing embedded googletest


【解决方案1】:

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

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

A) 在我的 PC 上运行的单元测试(我在 TDD 中使用)。我使用这些测试来开发我的应用程序逻辑。这是我需要模拟/存根的地方。我的公司使用硬件抽象层 (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 功能。这些测试背后的想法是我可以正确地测试我将模拟的函数。因为它在目标上运行,所以我需要它尽可能简单和轻量,所以我使用MinUnit,它是一个单独的 C 头文件。我已经使用 MinUnit 在 Cortex-M3 内核和专有 DSP 代码上运行了目标测试(没有任何修改)。我这里也用过 TDD。

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

回答您的问题:

  1. 正如其他人已经说过的,从gTest Primer 开始,不要担心嘲笑,只要掌握使用gTest 的窍门。 Cpputest 是提供一些内存检查(针对泄漏)的好选择。我对派生设置类的 gTest 语法有一点偏好。 Cpputest 可以运行用 gTest 编写的测试。两者都是很棒的框架。

  2. 我使用Fake Function Frakework 进行模拟和存根。它使用起来非常简单,它提供了一个好的模拟框架所期望的一切:设置不同的返回值、传递回调、检查参数调用历史等。我想试试Ceedling。到目前为止,FFF 一直很棒。

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

使用代码管理失败的断言 - 我的驱动程序库中的失败断言需要系统重置。如何在测试中模拟这一点?

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

假设您要测试使用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());
}

希望这会有所帮助!干杯!

【讨论】:

  • 感谢您的回答。那时当我问这个问题时,我对存根/嘲笑的想法很有限,GTest 最初很痛苦。所以,我决定使用CppUTest。现在,在阅读了您的答案后,我想我可以再试一次GTest。关于Fake Function Frakework 的一个问题。这与GTest 兼容吗?另外,当您有很多寄存器时,您如何模拟(模拟)说?比如说,写入0xAB0xC1 地址的寄存器。我最终使用了一个大数组并在这些索引处写入,然后验证值是否匹配。
  • 是的,这就是我一直在使用的:gTest+FFF。我的例子就是这样。
【解决方案2】:
  1. 我不知道使用 Gtest 的任何特定资源 裸机目标测试,但一般来说,一个好的起点是 是阅读Gtest Primer 并且取决于您的软件架构,甚至可能是Gmock documentation。后者在测试应用程序相关类时可能会派上用场,而不仅仅是低级驱动程序。
  2. 对此有多种选择。到目前为止,我看到的最常见的是目标和运行测试的平台有两种不同的实现。所以例如你可能有两个文件

    • ic2.c
    • i2c_x86.cpp

      并且取决于您当前是针对目标进行编译,还是针对您使用其中之一的测试平台进行编译。

    另一种选择是将 C 实现提升到 C++ 并围绕驱动程序编写一个类包装器。这将使您受益于 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)), ".*");

【讨论】:

  • 驱动程序根据定义是特定于硬件的,因此您只是无法在该死的 x86 上测试 I2C。 必须在特定硬件上测试驱动程序,期间。您可以测试硬件抽象层,但根据定义,这不是驱动程序,而是驱动程序的包装器。
  • 我正在尝试测试应用程序软件而不是驱动程序。因此,我问的是嘲笑司机。我想检查应用程序逻辑是否按预期工作,而不是驱动程序。
  • @Lundin 整页没有一句表明他要测试 I2C 驱动程序。除了你,没有人会这样假设。他甚至特意询问嘲讽……
  • 这个问题并不完全清楚,但不管您是否需要在实时硬件上测试这些东西。时间将完全不同,机器代码将完全不同等等。在 PC 上进行测试将显示 PC 是否工作,这就是它的全部优点。
  • 这个问题在这方面非常清楚。它声明单元测试将在用 C++ 编写的应用软件上执行。接下来的句子说应用软件使用的驱动程序是用 C 语言编写的,然后其中一个要点是专门关于如何mock这些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多