ormus-solutions/libreembed-claude-code/embedded-testing-patterns

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Firmware test patterns with Unity, CMock, Ceedling, and QEMU: project layout, testable HAL interfaces, CMock expect chaining, error-path tests, and an on-target test runner over ITM. Use when making embedded C testable or writing tests for it.

Install

npx vskill@latest install ormus-solutions/libreembed-claude-code/embedded-testing-patterns

SKILL.md

embedded-testing-patterns

Knowledge Base

Embedded C test patterns using Unity, CMock, Ceedling, and QEMU.


Pattern 1: Ceedling Project Structure

project/
├── project.yml # Ceedling configuration
├── src/
│ ├── sensor.c
│ └── sensor.h
├── test/
│ ├── test_sensor.c
│ └── support/ # Test helpers, stubs
└── vendor/ # Unity, CMock (git submodule or gem)

Minimal project.yml:

yaml
:project:
:build_root: build
:test_file_prefix: test_
:paths:
:source: ["src/**"]
:test: ["test/**"]
:include: ["src"]
:tools:
:test_compiler:
:arguments:
- -std=c11
- -DUNIT_TEST
:plugins:
:load_paths: ["vendor/ceedling/plugins"]
:enabled: [gcov, xml_tests_report]
:cmock:
:enforce_strict_ordering: TRUE
:plugins: [:ignore, :return_thru_ptr, :callback]

Run:

bash
ceedling test:all # All tests
ceedling test:sensor # Single module
ceedling gcov:all # Coverage
ceedling utils:gcov # HTML coverage report

Pattern 2: Testable HAL Interface Pattern

The golden rule: drivers never call HAL_I2C_Master_Transmit directly. They call through a pointer.

c
/* hal_i2c.h */
typedef int (*i2c_write_fn)(uint8_t dev_addr, uint8_t reg_addr,
const uint8_t *data, uint8_t len);
typedef int (*i2c_read_fn)(uint8_t dev_addr, uint8_t reg_addr,
uint8_t *data, uint8_t len);
typedef struct {
i2c_write_fn write;
i2c_read_fn read;
} i2c_ops_t;
/* sensor.c uses i2c_ops_t, not HAL directly */
static const i2c_ops_t *s_i2c;
int sensor_begin(const i2c_ops_t *i2c_ops) {
s_i2c = i2c_ops;
return s_i2c->write(SENSOR_ADDR, REG_RESET, (uint8_t[]){0xB6}, 1);
}

Production: pass a &stm32_i2c_ops that wraps HAL_I2C_Master_Transmit. Test: pass a &mock_i2c_ops backed by function pointers set by CMock.


Pattern 3: CMock Expect Chaining

Order matters. CMock verifies calls happen in declared order when enforce_strict_ordering: TRUE.

c
void test_bme280_init_sequence(void)
{
/* Expect exact I2C register writes in order */
i2c_write_ExpectAndReturn(BME280_ADDR, 0xF3, 0x00, 0); /* status check */
i2c_write_ExpectAndReturn(BME280_ADDR, 0xF4, 0x27, 0); /* ctrl_meas */
i2c_write_ExpectAndReturn(BME280_ADDR, 0xF5, 0xA0, 0); /* config */
int rc = bme280_init();
TEST_ASSERT_EQUAL_INT(0, rc);
/* CMock verifies all 3 Expects were called, in order, at test end */
}

If fewer calls happen than expected, CMock fails with "Called fewer times than expected."


Pattern 4: Testing Error Paths

c
void test_sensor_init_returns_error_on_i2c_failure(void)
{
/* Inject I2C failure on first write */
i2c_write_ExpectAndReturn(0x76, REG_RESET, 0xB6, I2C_ERR_NACK);
sensor_error_t result = sensor_init();
TEST_ASSERT_EQUAL(SENSOR_ERR_BUS, result);
}
void test_sensor_handles_crc_mismatch(void)
{
uint8_t bad_data[4] = { 0xAA, 0xBB, 0xCC, 0x00 }; /* Wrong CRC */
i2c_read_ExpectAndReturn(0x76, REG_DATA, bad_data, 4, 0);
i2c_read_ReturnThruPtr_data(bad_data);
int32_t temp;
sensor_error_t result = sensor_read_temperature(&temp);
TEST_ASSERT_EQUAL(SENSOR_ERR_CRC, result);
}

Error path coverage is often the most valuable. Always inject failures.


Pattern 5: On-Target Test Runner via ITM

For tests that must run on hardware (timing-sensitive, peripheral integration):

c
/* On-target Unity output via ITM */
void unity_output_char(char c)
{
ITM_SendChar((uint32_t)c);
}
/* main.c in test build */
int main(void)
{
SystemInit();
itm_enable(168000000UL, 2000000UL);
UNITY_BEGIN();
RUN_TEST(test_sensor_read_temperature_converts_raw);
RUN_TEST(test_sensor_init_configures_i2c);
return UNITY_END();
}

Output captured via J-Link SWO Viewer or OpenOCD SWO. Pass/fail visible on host.


Anti-Patterns

  • Testing HAL calls instead of business logic: mock the HAL, test what your code does with the result.
  • Global state between tests: reset all static variables in setUp(). Static state leaks cause false passes.
  • Single large test per function: one test should test one behavior. Refactor to separate test_X_when_Y.
  • Ignoring all mock calls with `_IgnoreAndReturn`: fine for stubs, but hides regressions. Be explicit.

References

  • Unity source + docs: https://github.com/ThrowTheSwitch/Unity
  • CMock: https://github.com/ThrowTheSwitch/CMock
  • Ceedling: https://github.com/ThrowTheSwitch/Ceedling
  • James Grenning, "Test-Driven Development for Embedded C" (Pragmatic Bookshelf)

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ormus-solutions/libreembed-claude-code/embedded-testing-patterns/badge)](https://verified-skill.com/skills/ormus-solutions/libreembed-claude-code/embedded-testing-patterns)

Something wrong with this skill or its page? Tell us.