A firmware bug in a car's brake controller. A memory overflow in a cardiac pacemaker. A timing violation in an aircraft flight management system. These are not theoretical risks — they are the real consequences of inadequate embedded software testing. This guide covers everything embedded teams need: testing types, frameworks with real code, key safety standards, HIL testing, and how to build a CI/CD pipeline for embedded systems in 2026.
Originally published in 2023. Updated and expanded for 2026 with HIL testing, embedded CI/CD, safety standards, RTOS testing, and modern tooling.
By Robonito Engineering Team · Updated May 2026 · 20 min read
Quick stats
| Fact | Source |
|---|---|
| Software defects account for 40% of automotive recalls in modern vehicles | NHTSA Vehicle Recall Database 2025 |
| A software defect found post-production in medical devices costs 100× more to fix than pre-certification | FDA Software Guidance |
| ISO 26262 compliance requires test coverage of at least MC/DC (Modified Condition/Decision Coverage) for ASIL D | ISO 26262:2018 |
| 73% of embedded teams report that inadequate test coverage causes project delays | VDC Research Embedded Market 2025 |
| Hardware-in-the-loop testing reduces integration defects by up to 60% vs software-only testing | dSPACE Customer Study 2025 |
Table of Contents
- What makes embedded software testing different
- The embedded testing pyramid
- Unit testing for embedded C/C++
- Integration testing in embedded systems
- Hardware-in-the-loop (HIL) testing
- RTOS and real-time testing
- Static analysis and code quality
- Safety standards and compliance testing
- Embedded testing tools compared
- CI/CD for embedded systems
- Web interface testing for embedded devices
- Pre-release embedded testing checklist
- Frequently Asked Questions
Testing the web interface of your embedded device or IoT platform?
Robonito auto-generates tests for device management dashboards, configuration portals, and IoT web interfaces — running across browsers in CI with self-healing when your UI changes. Try Robonito free →
1. What makes embedded software testing different
One-sentence definition for featured snippets: Embedded software testing is the verification and validation of software running directly on hardware — microcontrollers, FPGAs, and embedded processors — under real-time constraints, with limited resources, and often without a general-purpose operating system.
Three characteristics of embedded systems make their testing fundamentally different from testing web or mobile applications:
Target hardware as the test environment — unlike web applications where the production environment (a server or browser) closely resembles the development environment, embedded software runs on hardware that may be expensive, fragile, unavailable in quantity, or not yet manufactured. Test strategies must account for when target hardware is and is not available.
Real-time constraints — many embedded systems have hard real-time requirements: a motor control interrupt must complete in 50 microseconds, a CAN bus frame must be transmitted within 1 millisecond, a safety system must respond within 10 milliseconds. Tests must verify not just that functions produce correct outputs, but that they do so within the required timing window.
Limited observability and controllability — a web application's state is fully observable through browser DevTools, logs, and databases. An embedded system running on a microcontroller may have no display, no filesystem, and only a UART output for debugging. Testing requires deliberate design of observability interfaces (test hooks, debug ports, logging infrastructure).
Embedded Software Testing Lifecycle
A typical embedded testing lifecycle includes:
- Requirements analysis
- Static analysis
- Unit testing
- Integration testing
- Target hardware testing
- HIL testing
- System testing
- Compliance validation
- Release certification
Testing should begin as early as possible and continue throughout the product lifecycle.
2. The embedded testing pyramid
The testing pyramid applies to embedded systems, but with important differences from web application testing:
┌─────────────────────────┐
│ System Tests / HIL │ ← Real hardware + simulation
│ (few, slow, expensive) │
┌──┴─────────────────────────┴──┐
│ Target Integration Tests │ ← On actual embedded board
│ (moderate, on hardware) │
┌───┴────────────────────────────────┴───┐
│ Host-Based Unit Tests │ ← On development machine
│ (many, fast, hardware-independent) │
────┴────────────────────────────────────────┴────
Static Analysis (always running)
Host-based unit testing — the majority of embedded tests should run on a developer's workstation or CI server, not on the target hardware. This requires careful architecture: hardware-dependent code is isolated behind abstraction layers (Hardware Abstraction Layer, HAL), and unit tests mock those abstractions. Running tests on the host is 10–100× faster than running on target hardware and requires no physical device.
Target integration testing — a smaller set of tests that must run on real hardware to verify hardware-software interactions, timing, and device driver behaviour.
HIL / system testing — the smallest, most expensive layer. Full system validation connecting the real controller to a hardware simulator or test rig.
The common embedded testing mistake is inverting this pyramid — testing primarily on hardware because "that is what it runs on" — which creates slow, expensive, hardware-dependent test cycles.
3. Unit testing for embedded C/C++
Unit testing embedded C and C++ requires frameworks designed for resource-constrained environments. Standard web testing frameworks are not suitable — they assume a full OS, heap allocators, and C++ standard library features that may not be available on bare-metal systems.
Unity — the most widely used embedded C unit testing framework
Unity is a lightweight C testing framework designed specifically for embedded systems. It has no dependencies, fits in under 3KB of flash, and runs on any platform from Arduino to Linux.
/* test_temperature_sensor.c — Unity unit test for temperature sensor driver */
#include "unity.h"
#include "temperature_sensor.h"
#include "mock_adc.h" /* CMock-generated mock for ADC hardware abstraction */
void setUp(void) {
/* Called before each test — reset mock expectations */
mock_adc_Init();
}
void tearDown(void) {
/* Called after each test — verify all mock expectations were met */
mock_adc_Verify();
mock_adc_Destroy();
}
void test_TemperatureSensor_ReturnsCorrectDegreesC_ForMidrangeADCValue(void) {
/* Arrange: mock ADC returns 512 (mid-range of 0-1023 for 3.3V reference) */
/* ADC 512 → 1.65V → 25°C for this sensor's transfer function */
ADC_Read_ExpectAndReturn(ADC_CHANNEL_TEMP, 512);
/* Act */
float temp_celsius = TemperatureSensor_ReadCelsius();
/* Assert */
TEST_ASSERT_FLOAT_WITHIN(0.5f, 25.0f, temp_celsius);
/* ±0.5°C tolerance accounts for floating-point rounding */
}
void test_TemperatureSensor_ReturnsErrorCode_WhenADCReadFails(void) {
/* Arrange: simulate ADC hardware failure */
ADC_Read_ExpectAndReturn(ADC_CHANNEL_TEMP, ADC_ERROR);
/* Act */
float temp_celsius = TemperatureSensor_ReadCelsius();
/* Assert: error sentinel value returned, not a garbage temperature */
TEST_ASSERT_EQUAL_FLOAT(TEMPERATURE_SENSOR_ERROR, temp_celsius);
}
void test_TemperatureSensor_HandlesMaximumADCValue(void) {
/* Boundary test: ADC saturated at 1023 (maximum 10-bit value) */
ADC_Read_ExpectAndReturn(ADC_CHANNEL_TEMP, 1023);
float temp_celsius = TemperatureSensor_ReadCelsius();
/* Verify result is within physical sensor range (not NaN, not infinity) */
TEST_ASSERT_TRUE(temp_celsius > -40.0f); /* Below absolute minimum */
TEST_ASSERT_TRUE(temp_celsius < 150.0f); /* Above absolute maximum */
}
/* Unity test runner — main() calls each test function */
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_TemperatureSensor_ReturnsCorrectDegreesC_ForMidrangeADCValue);
RUN_TEST(test_TemperatureSensor_ReturnsErrorCode_WhenADCReadFails);
RUN_TEST(test_TemperatureSensor_HandlesMaximumADCValue);
return UNITY_END();
}
GoogleTest — for C++ embedded systems
// test_pid_controller.cpp — GoogleTest for PID controller
#include <gtest/gtest.h>
#include "pid_controller.h"
#include "mock_actuator.h"
class PIDControllerTest : public ::testing::Test {
protected:
PIDController pid;
MockActuator mock_actuator;
void SetUp() override {
// Standard PID tuning parameters for this system
pid.Configure(
/* Kp */ 2.0f,
/* Ki */ 0.5f,
/* Kd */ 0.1f,
/* dt_ms */ 10, // 10ms sample period
/* output_min */ -100.0f,
/* output_max */ 100.0f
);
}
};
TEST_F(PIDControllerTest, ProportionalTerm_ProducesCorrectOutput_ForPositiveError) {
float setpoint = 50.0f;
float measured = 40.0f; // Error = 10.0
float expected_proportional = 2.0f * 10.0f; // Kp * error
// Single step with zero integral and derivative history
float output = pid.Compute(setpoint, measured);
EXPECT_NEAR(expected_proportional, output, 0.01f);
}
TEST_F(PIDControllerTest, OutputClamped_WhenIntegralWindupOccurs) {
// Drive integrator to maximum
for (int i = 0; i < 1000; i++) {
pid.Compute(100.0f, 0.0f); // Large sustained error
}
float output = pid.Compute(100.0f, 0.0f);
// Output must be clamped, not exceeding max
EXPECT_LE(output, 100.0f);
EXPECT_GE(output, -100.0f);
}
TEST_F(PIDControllerTest, IntegratorResets_WhenResetCalled) {
// Accumulate integrator state
pid.Compute(100.0f, 0.0f);
pid.Compute(100.0f, 0.0f);
pid.Reset(); // Reset should clear all state
// After reset, output should be purely proportional
float output = pid.Compute(50.0f, 40.0f); // Error = 10
EXPECT_NEAR(20.0f, output, 0.1f); // Only Kp * error
}
Hardware Abstraction Layer — the key to testable embedded code
/* hal_adc.h — Hardware Abstraction Layer interface */
/* Production: implemented by hal_adc_stm32f4.c */
/* Testing: implemented by mock_adc.c (generated by CMock) */
#ifndef HAL_ADC_H
#define HAL_ADC_H
#include <stdint.h>
#define ADC_ERROR (-1)
#define ADC_CHANNEL_TEMP 0
#define ADC_CHANNEL_VBAT 1
/**
* @brief Read ADC channel
* @param channel ADC channel number
* @return 12-bit ADC value (0-4095), or ADC_ERROR on failure
*/
int32_t ADC_Read(uint8_t channel);
/**
* @brief Initialize ADC hardware
* @return 0 on success, negative error code on failure
*/
int32_t ADC_Init(void);
#endif /* HAL_ADC_H */
/* The production implementation (hal_adc_stm32f4.c) uses STM32 HAL:
int32_t ADC_Read(uint8_t channel) {
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = adc_channel_map[channel];
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
return HAL_ADC_GetValue(&hadc1);
}
The mock implementation (generated by CMock from the header):
int32_t ADC_Read(uint8_t channel) {
// Returns whatever value the test specified with ADC_Read_ExpectAndReturn()
}
*/
4. Integration testing in embedded systems
Integration testing in embedded systems verifies that software modules communicate correctly across their interfaces — function call APIs, shared memory regions, message queues, interrupt handlers, and hardware registers.
Software integration testing on host
/* test_sensor_to_logger_integration.c */
/* Tests the integration between temperature sensor module and data logger module */
/* Runs on host — both modules compiled for x86, hardware mocked */
#include "unity.h"
#include "temperature_sensor.h"
#include "data_logger.h"
#include "mock_adc.h"
#include "mock_flash_storage.h"
void test_TemperatureReading_LoggedCorrectly_WhenAboveThreshold(void) {
/* Arrange: temperature above warning threshold (30°C) */
ADC_Read_ExpectAndReturn(ADC_CHANNEL_TEMP, 614); // → 30.5°C
/* Expect logger to write one entry with correct data */
LogEntry_t expected_entry = {
.sensor_id = SENSOR_TEMPERATURE,
.value_x100 = 3050, // 30.50°C stored as integer × 100
.severity = LOG_WARNING,
.timestamp = 0, // Will be set by logger — ignore in comparison
};
FlashStorage_Write_ExpectWithArrayAndReturn(
0, // Any address
&expected_entry,
sizeof(LogEntry_t),
1 // Return success
);
/* Act: run the sensor-to-logger pipeline */
float temp = TemperatureSensor_ReadCelsius();
DataLogger_LogSensorReading(SENSOR_TEMPERATURE, temp);
/* Assert: Unity verifies CMock expectations were all called */
}
5. Hardware-in-the-loop (HIL) testing
Hardware-in-the-loop testing connects the real embedded controller to a real-time simulator that models the physical plant (engine, battery, motor, vehicle dynamics). The controller receives realistic, time-accurate sensor inputs and sends actuator commands that the simulator responds to.
Why HIL is essential for safety-critical systems:
Software-only simulation on a host machine cannot replicate the timing accuracy that safety-critical control algorithms require. A motor control algorithm that runs correctly in simulation at 1× speed may produce instability at the 10kHz interrupt rate of real hardware. HIL testing catches these timing-dependent issues by running the actual controller software at actual execution speed.
HIL test architecture
┌─────────────────────────────────────────────────────────┐
│ HIL Test System │
│ │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ Real-time │ I/O │ Embedded Controller │ │
│ │ Simulator │◄──────►│ (Device Under Test) │ │
│ │ (dSPACE/ │ boards │ Running actual │ │
│ │ NI VeriStand) │ │ production firmware │ │
│ │ │ │ │ │
│ │ Models: │ │ Inputs received: │ │
│ │ - Engine model │ │ - CAN messages │ │
│ │ - Vehicle dyn. │ │ - Analog sensor signals│ │
│ │ - Battery model │ │ - Digital I/O │ │
│ │ - Fault inject. │ │ │ │
│ └─────────────────┘ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ Test Automation Layer │ │
│ │ (Python / MATLAB) │ │
│ │ - Define test sequences│ │
│ │ - Inject fault conditions│ │
│ │ - Capture responses │ │
│ │ - Verify timing │ │
│ └────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
## HIL test script — Python automation layer (dSPACE/ETAS/National Instruments)
## Tests emergency braking system response to sudden obstacle detection
import hil_interface as hil
import time
def test_emergency_braking_activates_within_50ms():
"""
Regulatory requirement: Emergency braking must activate
within 50ms of obstacle detection signal assertion.
ISO 26262 ASIL-C requirement.
"""
## Set up initial conditions — vehicle at 80 km/h on flat road
hil.set_signal("vehicle_speed_kmh", 80.0)
hil.set_signal("road_gradient_deg", 0.0)
hil.set_signal("brake_pedal_position_pct", 0.0)
## Wait for system to reach steady state
time.sleep(0.1)
## Record precise timestamp of obstacle detection signal assertion
t_obstacle_detected = hil.get_timestamp()
hil.set_signal("obstacle_detected", True)
## Poll for brake actuator response
brake_activated = False
t_brake_activated = None
for _ in range(100): ## Poll for up to 100ms
time.sleep(0.001) ## 1ms polling interval
brake_pressure = hil.get_signal("brake_pressure_bar")
if brake_pressure > 5.0: ## Threshold for "activated"
brake_activated = True
t_brake_activated = hil.get_timestamp()
break
## Assert: braking activated
assert brake_activated, "Emergency braking did not activate after obstacle detection"
## Assert: response time within 50ms requirement
response_time_ms = (t_brake_activated - t_obstacle_detected) * 1000
assert response_time_ms <= 50.0, \
f"Emergency braking response time {response_time_ms:.1f}ms exceeds 50ms requirement"
print(f"✅ Emergency braking response: {response_time_ms:.1f}ms (requirement: ≤50ms)")
6. RTOS and real-time testing
Real-Time Operating System (RTOS) testing adds another dimension beyond function correctness: timing correctness. An embedded system running FreeRTOS, Zephyr, VxWorks, or QNX must meet deterministic timing requirements — tasks must complete within their deadline, context switches must take predictable time, and interrupt latency must remain bounded.
Timing violation detection
/* Watchdog timer pattern for detecting timing violations in RTOS tasks */
/* Used when formal timing analysis tools are not available */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
/* Maximum allowed execution time for motor control task: 500 microseconds */
#define MOTOR_TASK_DEADLINE_US 500U
#define TIMER_PERIOD_US 600U /* Expires if task exceeds 500us */
static TimerHandle_t deadline_timer;
static volatile bool deadline_violated = false;
static void DeadlineTimerCallback(TimerHandle_t xTimer) {
/* This callback fires if the motor control task ran too long */
deadline_violated = true;
/* In production: trigger safety shutdown */
/* In testing: record violation for test assertion */
}
void MotorControlTask(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount();
for (;;) {
/* Start deadline monitoring */
deadline_violated = false;
xTimerStart(deadline_timer, 0);
/* Critical control loop — must complete within 500us */
uint32_t adc_raw = ADC_Read(ADC_CHANNEL_CURRENT_SENSE);
float current_amps = ConvertAdcToAmps(adc_raw);
float pwm_duty = PID_Compute(&motor_pid, current_setpoint, current_amps);
PWM_SetDuty(PWM_CHANNEL_MOTOR, pwm_duty);
/* Stop deadline monitoring */
xTimerStop(deadline_timer, 0);
/* TEST ASSERTION: No deadline violation occurred */
configASSERT(!deadline_violated);
/* Wait for next 1ms period */
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1));
}
}
7. Static analysis and code quality
Static analysis examines source code without executing it, finding defects, undefined behaviour, MISRA C/C++ violations, and security vulnerabilities. For safety-critical embedded systems, static analysis is not optional — it is mandated by ISO 26262, DO-178C, and IEC 61508.
MISRA C compliance — common violations and fixes
/* MISRA C:2012 — common violations in embedded code */
/* ❌ Rule 14.4: Controlling expression of if/while must be essentially Boolean */
int sensor_value = ADC_Read(ADC_CHANNEL_1);
if (sensor_value) { /* MISRA violation: integer used as Boolean */
ProcessSensorData(sensor_value);
}
/* ✅ MISRA compliant */
int32_t sensor_value = ADC_Read(ADC_CHANNEL_1);
if (sensor_value > 0) { /* Explicit comparison */
ProcessSensorData(sensor_value);
}
/* ❌ Rule 17.7: Return value of non-void function shall be used */
memcpy(dest_buffer, src_buffer, BUFFER_SIZE); /* Return value discarded */
/* ✅ MISRA compliant */
void *result = memcpy(dest_buffer, src_buffer, BUFFER_SIZE);
if (result == NULL) {
HandleCopyError();
}
/* ❌ Rule 12.1: Pointer arithmetic increases maintenance risk */
uint8_t *ptr = buffer;
*(ptr + 5) = 0xFF; /* Pointer arithmetic without bounds check */
/* ✅ MISRA compliant — use index notation with bounds check */
if (5U < BUFFER_SIZE) {
buffer[5] = 0xFFU;
}
Code coverage for safety-critical systems
ISO 26262 ASIL-D requires Modified Condition/Decision Coverage (MC/DC) — every condition in every decision must independently affect the outcome. Standard branch coverage (used in most web testing) is insufficient for safety-critical certification.
## Makefile — code coverage with gcov for embedded code (host compilation)
CFLAGS += --coverage -O0 -g
LDFLAGS += --coverage
coverage: test
lcov --capture --directory . --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/test/*' --output-file coverage_filtered.info
genhtml coverage_filtered.info --output-directory coverage_report
@echo "Coverage report: coverage_report/index.html"
## For ASIL compliance: verify MC/DC coverage requirements met
## Commercial tools (LDRA, VectorCAST) provide MC/DC measurement
8. Safety standards and compliance testing
ISO 26262 — Automotive functional safety
ISO 26262 defines Automotive Safety Integrity Levels (ASIL A through D) based on severity, exposure, and controllability of hazardous events. Higher ASIL ratings require more rigorous testing:
| ASIL Level | Application example | Required coverage | Unit testing | HIL required |
|---|---|---|---|---|
| ASIL A | Windshield wipers | Statement coverage | Recommended | No |
| ASIL B | Power windows, sunroof | Branch coverage | Required | Recommended |
| ASIL C | Electric power steering | MC/DC coverage | Required | Required |
| ASIL D | Brake-by-wire, airbags | MC/DC + fault injection | Required | Required |
DO-178C — Aviation software
DO-178C classifies airborne software by Design Assurance Level (DAL):
| DAL | Failure condition | Impact | Testing requirements |
|---|---|---|---|
| A | Catastrophic | Loss of aircraft | MC/DC, structural coverage, independent review |
| B | Hazardous | Serious injury | Decision coverage, reviews |
| C | Major | Minor injury or aircraft issue | Statement coverage |
| D | Minor | Inconvenience | Testing per plan |
| E | No safety effect | None | No requirements |
IEC 61508 — Industrial functional safety
IEC 61508 defines Safety Integrity Levels (SIL 1–4) for industrial systems including process control, manufacturing automation, and energy systems. SIL 4 (highest) requires fault tolerance, diverse redundancy, and formal verification methods in addition to comprehensive testing.
Common Challenges in Embedded Software Testing
Embedded teams frequently encounter:
- Limited hardware availability
- Real-time timing verification
- Hardware-software dependency issues
- Incomplete observability
- Certification requirements
- Long hardware test cycles
Modern CI pipelines using host testing, simulation, and HIL help reduce these challenges.
Embedded Software Testing vs Traditional Software Testing
| Aspect | Embedded Software Testing | Traditional Software Testing |
|---|---|---|
| Environment | Hardware dependent | Browser, desktop, cloud |
| Timing Constraints | Critical | Usually less critical |
| Resource Limits | Memory and CPU constrained | Generally abundant resources |
| Hardware Interaction | Required | Rare |
| Safety Standards | ISO 26262, DO-178C | Usually not required |
| Testing Cost | Higher | Lower |
9. Embedded testing tools compared
Unit testing frameworks
| Framework | Language | Target deployment | Standards support | Best for |
|---|---|---|---|---|
| Unity | C | Bare metal + RTOS | MISRA friendly | Most embedded C projects |
| CppUTest | C/C++ | Host + target | — | C++ embedded projects |
| GoogleTest | C++ | Host (primarily) | — | Modern C++ embedded |
| Cpputest | C/C++ | Host + target | — | Legacy C++ projects |
| CTest (CMake) | Any | Host + CI | — | CMake-based projects |
Commercial embedded test automation
| Tool | Type | Standards | Best for |
|---|---|---|---|
| VectorCAST | Unit test + coverage | ISO 26262, DO-178C, IEC 61508 | Automotive, aerospace |
| LDRA | Static analysis + testing | All major safety standards | Aviation, medical |
| Parasoft C/C++test | Unit test + static | ISO 26262, MISRA | Automotive, industrial |
| Polyspace (MathWorks) | Static analysis | ISO 26262, MISRA | Automotive |
HIL simulation platforms
| Platform | Provider | Best for |
|---|---|---|
| dSPACE | dSPACE | Automotive, complex plant models |
| NI VeriStand | National Instruments | Multi-domain, scalable |
| Wind River Simics | Wind River | Processor simulation, virtual prototypes |
| QEMU | Open source | Processor emulation, CI integration |
| Renode | Antmicro | Open-source embedded system simulation |
Open-source simulation for CI
## QEMU — run embedded tests in CI without physical hardware
## Example: test ARM Cortex-M firmware in GitHub Actions
- name: Build firmware for STM32
run: |
arm-none-eabi-gcc \
-mcpu=cortex-m4 \
-mthumb \
-DTESTING \
-o firmware_test.elf \
src/*.c test/*.c
- name: Run unit tests in QEMU (ARM emulation)
run: |
qemu-system-arm \
-machine lm3s6965evb \
-kernel firmware_test.elf \
-semihosting-config enable=on,target=native \
-nographic \
-serial stdio \
-monitor null | tee test_output.txt
grep -q "OK" test_output.txt || exit 1
10. CI/CD for embedded systems
Integrating embedded software testing into CI/CD pipelines reduces the time between a defect introduction and its detection. The architecture uses host-based tests for speed and QEMU simulation for target verification, reserving physical hardware for pre-release integration.
## .github/workflows/embedded-ci.yml
name: Embedded Software CI
on:
push:
branches: [main, develop]
pull_request:
jobs:
## Stage 1: Host-based unit tests (fast, no hardware needed)
unit-tests-host:
name: Unit Tests (Host x86_64)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Install build tools
run: |
sudo apt-get update
sudo apt-get install -y gcc g++ cmake gcovr
pip install gcovr --break-system-packages
- name: Build unit tests for host (x86_64)
run: |
cmake -B build_host \
-DTARGET=host \
-DBUILD_TESTS=ON \
-DCMAKE_C_FLAGS="--coverage -O0"
cmake --build build_host
- name: Run unit tests
run: |
cd build_host
ctest --output-on-failure --timeout 60
- name: Generate coverage report
run: |
gcovr --root . \
--html-details coverage/index.html \
--xml coverage/coverage.xml \
--exclude '*/test/*' \
--exclude '*/third_party/*'
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
## Stage 2: Target simulation tests (QEMU)
target-simulation:
name: Target Tests (QEMU ARM)
runs-on: ubuntu-latest
needs: unit-tests-host
steps:
- uses: actions/checkout@v4
- name: Install cross-compilation toolchain + QEMU
run: |
sudo apt-get install -y \
gcc-arm-none-eabi \
binutils-arm-none-eabi \
qemu-system-arm
- name: Build firmware for ARM Cortex-M target
run: |
cmake -B build_arm \
-DCMAKE_TOOLCHAIN_FILE=cmake/arm-none-eabi.cmake \
-DTARGET=stm32f4 \
-DBUILD_TESTS=ON
cmake --build build_arm
- name: Run tests in QEMU ARM emulation
run: |
timeout 120 qemu-system-arm \
-machine lm3s6965evb \
-kernel build_arm/firmware_tests.elf \
-semihosting-config enable=on,target=native \
-nographic \
-serial stdio 2>&1 | tee qemu_output.txt
## Verify tests passed
grep -q "PASS\|OK" qemu_output.txt && \
! grep -q "FAIL\|ASSERT" qemu_output.txt
## Stage 3: Static analysis (MISRA, undefined behaviour)
static-analysis:
name: Static Analysis (cppcheck + MISRA)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install -y cppcheck
- name: Run cppcheck with MISRA rules
run: |
cppcheck \
--enable=all \
--std=c11 \
--platform=arm32-wchar_t4 \
--suppress=missingInclude \
--error-exitcode=1 \
--xml \
src/ 2> cppcheck_results.xml
- uses: actions/upload-artifact@v4
if: always()
with:
name: static-analysis-results
path: cppcheck_results.xml
11. Web interface testing for embedded devices
Modern embedded systems increasingly expose web-based management interfaces — device configuration portals, IoT dashboards, fleet management consoles, and remote monitoring tools. These web interfaces require the same rigorous testing as any web application.
This is where Robonito provides direct value to embedded teams: automated AI-powered testing of the web interfaces that accompany embedded devices, without requiring any scripting.
Common embedded device web interfaces that need testing:
- Device configuration portals — network settings, firmware updates, parameter configuration
- IoT dashboard UIs — sensor data visualisation, alert management, device fleet monitoring
- Industrial HMI web panels — process control interfaces, alarm management, historian views
- Medical device management systems — device configuration, patient data review, calibration interfaces
// Playwright test for IoT device management dashboard
// (Embedded team testing their web interface, not the firmware)
import { test, expect } from '@playwright/test';
test.describe('IoT Device Management Dashboard', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://iot-dashboard.yourcompany.com');
await page.getByLabel('Username').fill('admin');
await page.getByLabel('Password').fill(process.env.DASHBOARD_ADMIN_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL(/\/dashboard/);
});
test('device list shows all registered devices with correct status', async ({ page }) => {
await page.getByRole('link', { name: 'Devices' }).click();
// All registered test devices should appear
await expect(page.getByTestId('device-count')).not.toHaveText('0');
// Status indicators should be accurate
const onlineDevices = page.getByTestId('status-online');
const offlineDevices = page.getByTestId('status-offline');
await expect(onlineDevices.first()).toBeVisible();
// Verify status badge accessibility
await expect(onlineDevices.first()).toHaveAttribute('aria-label', /online/i);
});
test('firmware update workflow completes without error', async ({ page }) => {
await page.getByRole('link', { name: 'Firmware' }).click();
await page.getByRole('button', { name: 'Upload firmware' }).click();
// Select test firmware file
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles('./test-fixtures/firmware-v2.2.0-test.bin');
await expect(page.getByTestId('firmware-file-name')).toHaveText('firmware-v2.2.0-test.bin');
await expect(page.getByTestId('firmware-file-size')).not.toHaveText('0 bytes');
// Confirm upload (do not trigger actual deployment in test)
await expect(page.getByRole('button', { name: 'Deploy to devices' })).toBeVisible();
});
});
Robonito automates this web interface testing without scripting — recording the management portal interactions, generating test cases, and running them across browsers in CI automatically.
12. Pre-release embedded testing checklist
Software testing (host-based)
- Unit test suite achieving required coverage level for target ASIL/DAL
- All MISRA C/C++ violations resolved or formally justified
- Static analysis completed with no critical findings
- Memory analysis completed (no leaks, no buffer overflows)
- All boundary values tested for sensor inputs, register values, and communication parameters
- Error handling tested for all hardware failure modes
Target and integration testing
- All software modules tested on target hardware (not just host simulation)
- Interrupt latency measured and verified within requirements
- Stack usage measured — minimum 20% headroom below stack size
- Timing-critical tasks verified to meet deadlines under worst-case load
- Communication protocol conformance tested (CAN, SPI, I2C, UART, Ethernet)
- Flash and RAM usage within allocation budget
HIL testing (safety-critical systems)
- All normal operating scenarios validated at representative timing
- Fault injection tests completed for all single-point failures
- Response time requirements verified under simulation
- System behaviour verified during hardware reset and power cycle sequences
- Communication loss scenarios tested and safe states verified
Standards compliance
- Required code coverage level achieved and documented
- Traceability matrix linking requirements → test cases → results complete
- All safety requirements traced to at least one passing test
- Test reports generated in format required by certification authority
- Tool qualification evidence available for all testing tools (DO-330 / ISO 26262 Part 8)
Web interface (for devices with management UI)
- Device management web UI tested cross-browser (Chrome + Safari minimum)
- Configuration changes reflected correctly in device behaviour
- Firmware upload workflow tested end-to-end in staging
- Authentication and authorisation verified for all user roles
- Mobile responsiveness verified for field service tablet use cases
Common Embedded Software Testing Interview Questions
What is the difference between HIL and SIL testing?
Software-in-the-Loop (SIL) testing executes the software against simulated models, while Hardware-in-the-Loop (HIL) testing runs the actual controller hardware against a real-time simulator.
Why is MC/DC coverage important?
MC/DC coverage verifies that each condition in a decision independently affects the outcome. It is required by standards such as ISO 26262 ASIL D and DO-178C Level A.
Which embedded testing framework is most widely used?
Unity remains one of the most widely adopted open-source frameworks for embedded C testing because of its small footprint and portability.
Frequently Asked Questions
What is embedded software testing?
Embedded software testing is the verification and validation of software running directly on hardware — microcontrollers, FPGAs, and embedded processors — under real-time constraints, with limited memory and processing resources. It differs from application software testing by requiring tests that run on or simulate the target hardware, account for timing constraints, and verify hardware-software interactions.
What are the main types of embedded software testing?
Unit testing (functions in isolation, typically on host), integration testing (module interactions), hardware-in-the-loop (HIL) testing (software against simulated hardware), target-based testing (on actual hardware), performance and timing testing (real-time constraint verification), and static analysis (code quality without execution).
What standards govern embedded software testing?
ISO 26262 for automotive, DO-178C for aviation, IEC 61508 for industrial systems, IEC 62443 for industrial cybersecurity, and MISRA C/C++ for safety-critical coding standards. The applicable standard depends on the industry and safety integrity level of the system.
What tools are used for embedded unit testing?
Unity and CppUTest for C, GoogleTest for C++, VectorCAST for commercial C/C++ test automation with certification evidence, LDRA for code coverage and MISRA compliance, gcov/lcov for open-source coverage measurement. QEMU provides free processor emulation for CI without physical hardware.
How do you integrate embedded testing into CI/CD?
Host-based unit tests run on every commit (no hardware required, fast). QEMU or Renode simulation runs on every PR (ARM emulation without physical devices). Physical hardware tests run pre-release only (slow, expensive, limited availability). Static analysis runs alongside unit tests. This tiered approach balances thoroughness against pipeline speed.
How is Robonito relevant to embedded software testing?
Robonito specifically tests the web-based management interfaces that accompany modern embedded devices — IoT dashboards, device configuration portals, industrial HMI web panels. It does not test firmware or microcontroller code (that is the domain of Unity, VectorCAST, and HIL tools). For embedded teams building web interfaces alongside their hardware, Robonito provides no-code automated testing of that web layer.
External references
- Unity Test Framework — Embedded C unit testing
- GoogleTest Documentation — C++ testing framework
- FreeRTOS Documentation — RTOS reference
- ISO 26262 Overview — Automotive functional safety standard
- MISRA C:2012 Guidelines — Safety-critical C coding standard
- QEMU Documentation — Open-source processor emulation
- VectorCAST — Commercial embedded test automation
Testing the web dashboard or configuration portal of your embedded device?
Robonito automates the web interface layer of your embedded system — IoT dashboards, device management portals, and industrial HMI web panels — with no-code test generation, cross-browser execution, and self-healing when your UI changes. Start free at Robonito.com →
Automate your QA — no code required
Stop writing test scripts.
Start shipping with confidence.
Join thousands of QA teams using Robonito to automate testing in minutes — not months.
