Ceedling中使用ASSERT语句进行单元测试时断言失败问题排查
被测函数代码
fsp_err_t ad7606c_init(const bsp_io_port_pin_t cs, const spi_instance_t* spi) { fsp_err_t result = FSP_SUCCESS; uint8_t rx[20] = {0}; /* write config reg use 1 d out line */ volatile ad7606c_config_reg_t config = {.DOUT_FORMAT = 0}; write_reg(cs, spi, AD7606C_REG_CONFIG, config.REG); /* read config reg */ read_reg(cs, spi, AD7606C_REG_CONFIG, 1, rx); /* read id reg */ read_reg(cs, spi, AD7606C_REG_ID, 1, rx); /* id must be 0x32 */ assert(rx[1] == ID); /* write range reg */ volatile ad7606c_range_reg_t range = {.CH1_RANGE = AD7606C_RANGE_12500M_TO_12500P_DIFF, .CH2_RANGE = AD7606C_RANGE_12500M_TO_12500P_DIFF}; write_reg(cs, spi, AD7606C_REG_RANGE_CH1_CH2, range.REG); write_reg(cs, spi, AD7606C_REG_RANGE_CH3_CH4, range.REG); write_reg(cs, spi, AD7606C_REG_RANGE_CH5_CH6, range.REG); write_reg(cs, spi, AD7606C_REG_RANGE_CH7_CH8, range.REG); #ifdef INTERFACE_CHECK write_reg(cs, spi, AD7606C_REG_DIGITAL_DIAG_ENA, 0x81); #endif return result; }
测试报错信息
ERROR: Test executable "test_ad7606c.out" failed.
Produced no final test result counts in $stdout:
Assertion failed: rx[1] == ID, file src/ad7606c/ad7606c.c, line 5962:test_ad7606c_init_normal_initialization:PASS
And exited with status: [0] (count of failed tests).
This is often a symptom of a bad memory access in source or test code.
辅助断言支持头文件
#ifndef ASSERT_UNIT_TEST_SUPPORT_H_ #define ASSERT_UNIT_TEST_SUPPORT_H_ #include "CException.h" #define TEST_ASSERT_FAIL_ASSERT(_code_under_test) \ { \ CEXCEPTION_T e; \ Try { \ _code_under_test; \ TEST_FAIL_MESSAGE("Code under test did not assert"); \ } Catch(e) {} \ } #define TEST_ASSERT_PASS_ASSERT(_code_under_test) \ { \ CEXCEPTION_T e; \ Try { \ _code_under_test; \ } Catch(e) { \ TEST_FAIL_MESSAGE("Code under test failed an assertion"); \ } \ } #endif
测试用例代码
#include "unity.h" #include "r_spi_api.h" #include "bsp_io.h" #include "ad7606c.h" #include "Helper.h" #include "mock_regread.h" // Definitions for testing #define CS_PIN 1 // Chip select pin mock value #define SPI_INSTANCE NULL // SPI instance mock (assuming a null pointer for simplicity) void test_ad7606c_init_id_mismatch(void) { uint8_t rx[20] = {0}; rx[1] = 0x00; // Incorrect ID value // Expected sequence write_reg_Expect(CS_PIN, SPI_INSTANCE, AD7606C_REG_CONFIG, 0); read_reg_Expect(CS_PIN, SPI_INSTANCE, AD7606C_REG_CONFIG, 1, rx); read_reg_Expect(CS_PIN, SPI_INSTANCE, AD7606C_REG_ID, 1, rx); // Call the function and expect assertion failure TEST_ASSERT_FAIL_ASSERT(ad7606c_init(CS_PIN, SPI_INSTANCE)); }
排查与解决步骤
1. 替换原生assert为异常抛出宏
标准库的assert会直接调用abort()终止程序,导致Ceedling无法捕获测试结果,必须替换成能抛出CException的版本:
在测试环境的头文件(比如ad7606c.h或专门的测试配置头文件)中添加:
#ifdef TEST #include "CException.h" #define assert(expr) do { if (!(expr)) Throw(1); } while(0) #else #include <assert.h> #endif
确保编译测试代码时定义了TEST宏(可在Ceedling的project.yml中配置:defines: - TEST)。
2. 修正Mock函数的参数处理
被测函数中的rx是局部变量,测试用例中定义的rx无法直接传递给被测函数。需要用CMock的输出参数设置功能,让read_reg的mock实现将测试用例中预设的错误ID写入被测函数的rx数组:
修改测试用例中的mock调用:
void test_ad7606c_init_id_mismatch(void) { uint8_t rx_id_mismatch[20] = {0}; rx_id_mismatch[1] = 0x00; // Incorrect ID value // Expected sequence for config write/read write_reg_Expect(CS_PIN, SPI_INSTANCE, AD7606C_REG_CONFIG, 0); read_reg_Expect(CS_PIN, SPI_INSTANCE, AD7606C_REG_CONFIG, 1, rx_id_mismatch); read_reg_IgnoreArg_rx(); read_reg_SetBuffer_rx(rx_id_mismatch, 20); // Expected sequence for ID read read_reg_Expect(CS_PIN, SPI_INSTANCE, AD7606C_REG_ID, 1, rx_id_mismatch); read_reg_IgnoreArg_rx(); read_reg_SetBuffer_rx(rx_id_mismatch, 20); // Call the function and expect assertion failure TEST_ASSERT_FAIL_ASSERT(ad7606c_init(CS_PIN, SPI_INSTANCE)); }
read_reg_IgnoreArg_rx()告诉CMock忽略传入的实际参数,read_reg_SetBuffer_rx()指定要复制到被测函数参数中的数组值。
3. 验证测试环境预定义宏
检查Ceedling的project.yml,确保TEST宏已被正确定义:
:defines: :common: - TEST # 若需要测试INTERFACE_CHECK分支,可添加: # - INTERFACE_CHECK
4. 检查ID宏定义
确认被测代码中的ID宏是否为0x32,避免因宏定义不一致导致的断言失败。
内容的提问来源于stack exchange,提问作者Pranoti Joshi - Chepurwar

