AUTOSAR Classic and Adaptive Testing Patterns for Automotive Software
AUTOSAR is the software architecture that underpins most modern ECUs. If you're testing automotive software without understanding AUTOSAR's layering, you're either testing the wrong things or missing entire failure modes. This post covers practical testing strategies for both AUTOSAR Classic (CP) — the real-time OS stack running on microcontrollers — and AUTOSAR Adaptive (AP) — the service-oriented platform running on high-performance processors.
AUTOSAR Classic: Architecture You're Testing Against
Before writing a single test, understand what you're dealing with:
┌─────────────────────────────────────┐
│ Application Layer (SWCs) │ ← Your business logic lives here
├─────────────────────────────────────┤
│ Runtime Environment (RTE) │ ← Communication broker (generated)
├────────────────┬────────────────────┤
│ Services │ ECU Abstraction │ ← BSW upper layers
├────────────────┴────────────────────┤
│ Microcontroller Abstraction │ ← MCAL (hardware-specific)
├─────────────────────────────────────┤
│ Hardware (MCU) │
└─────────────────────────────────────┘Testing happens at each layer, with different tools and concerns at each level.
Testing BSW (Basic Software) Components
BSW components — Com, NvM, Dcm, Dem, CanSM, EcuM, WdgM — are mostly configured rather than coded. Your testing focus is validating that the configuration produces the correct behavior.
Com Stack Testing with Vector CANoe
The Communication Manager stack (CanSM → ComM → Com → CanIf → Can) is best tested at the CAN bus level using CANoe with CAPL scripting:
/*
* Test: Com_SendSignal correctly packages a 10-bit sensor value
* into a CAN frame per the DBC signal definition.
* Requirement: COM_CFG_047
*/
testcase TC_COM_SignalPacking_SensorValue(void) {
message 0x1A4 rxMsg;
float rawValue = 512.0; // Mid-range for 10-bit signal
float tolerance = 0.5;
// Trigger SWC to write the sensor value via RTE
$SWC_SensorInput_Write_SensorValue = rawValue;
trigger ComSend();
// Wait for CAN frame and verify encoding
if (TestWaitForMessage(0x1A4, 50)) { // 50ms timeout
float decoded = (rxMsg.word(0) & 0x03FF) * 0.1; // Scale factor 0.1
TestCheckWithinRange(decoded, rawValue - tolerance,
rawValue + tolerance,
"Signal packing: SensorValue encoding");
} else {
TestStepFail("TC_COM_SignalPacking", "No CAN frame received within 50ms");
}
}
/*
* Test: CanSM transitions to CANSM_BSWM_NO_COMMUNICATION on bus-off
*/
testcase TC_CanSM_BusOff_Recovery(void) {
int stateBeforeBusOff;
int stateAfterRecovery;
// Read initial state
stateBeforeBusOff = $CanSM_Network0_State;
TestCheckEqual(stateBeforeBusOff, CANSM_BSWM_FULL_COMMUNICATION,
"Initial state: FULL_COMMUNICATION");
// Inject bus-off condition
gCan.BusOff();
TestWaitForTimeout(10); // 10ms
// Verify transition to NO_COMMUNICATION
TestCheckEqual($CanSM_Network0_State, CANSM_BSWM_NO_COMMUNICATION,
"Bus-off: State transition to NO_COMMUNICATION");
// Wait for recovery (T_BUSOFF_RECOVERY_TIME = 200ms)
TestWaitForTimeout(250);
TestCheckEqual($CanSM_Network0_State, CANSM_BSWM_FULL_COMMUNICATION,
"Recovery: State returns to FULL_COMMUNICATION");
}NvM Block Testing
NvM (Non-volatile Memory Management) is a critical BSW module — misconfigured NvM blocks cause data corruption that's hard to reproduce. Test every block's read/write/default-value behavior:
/* Using EB Tresos generated test stubs + Unity test framework */
#include "unity.h"
#include "NvM.h"
#include "NvM_Cfg.h"
#include "MemIf_stub.h"
/* Test NvM block write followed by simulated power loss and read-back */
void test_NvM_BlockWrite_PowerLoss_ReadDefault(void) {
uint8_t writeData[NVM_BLOCK_ODOMETER_SIZE] = {0x00, 0x12, 0x34, 0x56};
uint8_t readData[NVM_BLOCK_ODOMETER_SIZE] = {0};
NvM_RequestResultType result;
/* Write the block */
TEST_ASSERT_EQUAL(E_OK, NvM_WriteBlock(NvMConf_NvMBlockDescriptor_OdometerBlock,
writeData));
NvM_MainFunction(); /* Process request */
MemIf_Stub_CompleteJob(MEMIF_JOB_OK);
NvM_MainFunction(); /* Process callback */
NvM_GetErrorStatus(NvMConf_NvMBlockDescriptor_OdometerBlock, &result);
TEST_ASSERT_EQUAL(NVM_REQ_OK, result);
/* Simulate power loss: corrupt the RAM mirror */
MemIf_Stub_CorruptBlock(NvMConf_NvMBlockDescriptor_OdometerBlock);
/* Read should use ROM default value (CRC mismatch → use default) */
TEST_ASSERT_EQUAL(E_OK, NvM_ReadBlock(NvMConf_NvMBlockDescriptor_OdometerBlock,
readData));
NvM_MainFunction();
MemIf_Stub_CompleteJob(MEMIF_JOB_FAILED); /* Simulates corrupted EEPROM */
NvM_MainFunction();
NvM_GetErrorStatus(NvMConf_NvMBlockDescriptor_OdometerBlock, &result);
TEST_ASSERT_EQUAL(NVM_REQ_INTEGRITY_FAILED, result);
/* Verify default value was loaded */
const uint8_t expectedDefault[NVM_BLOCK_ODOMETER_SIZE] = {0x00, 0x00, 0x00, 0x00};
TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedDefault, readData, NVM_BLOCK_ODOMETER_SIZE);
}SWC Unit Testing with RTE Mocks
Software Components (SWCs) communicate exclusively through the RTE. The RTE is a generated layer — you don't test it, you mock it. This is the key to fast, hardware-independent SWC unit tests.
Generating RTE Stubs with EB Tresos
In EB Tresos (Elektrobit Tresos Studio), navigate to the SWC's port interface definitions and export the RTE header. The test harness then provides mock implementations:
/* Rte_Mock.h — manually maintained or generated by tooling */
#ifndef RTE_MOCK_H
#define RTE_MOCK_H
#include "Rte_Type.h"
/* Mock state for RTE ports */
typedef struct {
float VehicleSpeed_kph;
boolean BrakeRequest;
uint8_t GearPosition;
Std_ReturnType VehicleSpeed_ReadReturn; /* Simulate Rte_Read errors */
} RteMockState_t;
extern RteMockState_t g_RteMock;
/* RTE Read port stubs */
static inline Std_ReturnType Rte_Read_PPort_VehicleSpeed(float *value) {
*value = g_RteMock.VehicleSpeed_kph;
return g_RteMock.VehicleSpeed_ReadReturn;
}
/* RTE Write port stubs (capture writes for assertion) */
static inline Std_ReturnType Rte_Write_RPort_BrakeRequest(boolean value) {
g_RteMock.BrakeRequest = value;
return RTE_E_OK;
}
/* RTE Server Call stubs */
static inline Std_ReturnType Rte_Call_SR_DtcLog_SetDtc(uint32_t dtcId, uint8_t severity) {
/* Record the DTC for test assertions */
DtcLog_Stub_RecordDtc(dtcId, severity);
return RTE_E_OK;
}
#endif /* RTE_MOCK_H */SWC Test Harness Example
Testing a cruise control SWC against its RTE interfaces:
#include "unity.h"
#include "Rte_Mock.h"
#include "CruiseControl.h" /* SWC under test */
void setUp(void) {
/* Reset mock state before each test */
memset(&g_RteMock, 0, sizeof(g_RteMock));
g_RteMock.VehicleSpeed_ReadReturn = RTE_E_OK;
CruiseControl_Init(); /* SWC init */
}
/* Test: CC maintains set speed when vehicle speed drops */
void test_CC_SpeedMaintain_BelowSetPoint(void) {
/* Set up: CC active at 100 kph */
g_RteMock.VehicleSpeed_kph = 100.0f;
CruiseControl_SetSpeed(100.0f);
CruiseControl_Activate();
CruiseControl_RunCycle();
/* Simulate speed drop (e.g., uphill) */
g_RteMock.VehicleSpeed_kph = 95.0f;
CruiseControl_RunCycle();
/* Expect throttle increase, no brake request */
TEST_ASSERT_TRUE(g_RteMock.ThrottleRequest > 0.0f);
TEST_ASSERT_FALSE(g_RteMock.BrakeRequest);
}
/* Test: CC deactivates and sets DTC on RTE read error */
void test_CC_Deactivate_OnRteReadFailure(void) {
CruiseControl_Activate();
/* Simulate RTE communication error */
g_RteMock.VehicleSpeed_ReadReturn = RTE_E_COM_STOPPED;
CruiseControl_RunCycle();
/* CC must deactivate */
TEST_ASSERT_EQUAL(CC_STATE_INACTIVE, CruiseControl_GetState());
/* DTC 0x9A001 (CC_SENSOR_FAULT) must be logged */
TEST_ASSERT_TRUE(DtcLog_Stub_WasDtcLogged(0x9A001));
}
/* Test: CC does not activate below minimum speed */
void test_CC_NoActivation_BelowMinSpeed(void) {
g_RteMock.VehicleSpeed_kph = 25.0f; /* Below 30 kph minimum */
Std_ReturnType result = CruiseControl_Activate();
TEST_ASSERT_EQUAL(E_NOT_OK, result);
TEST_ASSERT_EQUAL(CC_STATE_INACTIVE, CruiseControl_GetState());
}Run the SWC test suite:
# Build SWC tests (no AUTOSAR stack, no hardware)
gcc -I include/ -I test/mocks/ \
src/CruiseControl.c \
test/test_CruiseControl.c \
test/mocks/Rte_Mock.c \
test/mocks/DtcLog_Stub.c \
unity/unity.c \
-o build/test_CruiseControl
./build/test_CruiseControl
# Output:
# test_CC_SpeedMaintain_BelowSetPoint: PASS
# test_CC_Deactivate_OnRteReadFailure: PASS
# test_CC_NoActivation_BelowMinSpeed: PASS
# 3 Tests, 0 Failures, 0 IgnoredAUTOSAR Adaptive: A Different Testing Model
AUTOSAR Adaptive (AP) runs on Linux-based high-performance compute platforms — central compute units, domain controllers, sensor fusion processors. The architecture is service-oriented, not signal-oriented.
┌────────────────────────────────────────────────┐
│ Adaptive Applications (AA) │
│ (C++14/17, POSIX processes) │
├────────────────────────────────────────────────┤
│ ARA (AUTOSAR Runtime for Adaptive) │
│ ara::com ara::diag ara::exec ara::log │
├────────────────────────────────────────────────┤
│ Adaptive Platform Foundation │
│ (Linux OS + POSIX) │
└────────────────────────────────────────────────┘Testing ARA Services with ara::com Mocks
ARA services use a proxy/skeleton pattern. The skeleton is the server; the proxy is the client. Testing an Adaptive Application in isolation means mocking the ara::com layer:
// test_ObjectDetectionService.cpp
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "ara/com/sample_ptr.h"
#include "ObjectDetection/ObjectDetectionSkeleton.h"
#include "mock/ara_com_mock.hpp"
class MockObjectDetectionProxy : public object_detection::proxy::ObjectDetectionProxy {
public:
MOCK_METHOD(ara::core::Future<object_detection::proxy::methods::DetectObjects::Output>,
DetectObjects,
(const object_detection::proxy::methods::DetectObjects::Input& input),
(override));
MOCK_METHOD(ara::com::SamplePtr<object_detection::proxy::events::DetectedObjects::SampleType>,
GetNewSamples,
(std::size_t max_num_samples),
(override));
};
class ObjectDetectionConsumerTest : public ::testing::Test {
protected:
MockObjectDetectionProxy mock_proxy_;
ObjectDetectionConsumer consumer_{mock_proxy_};
};
TEST_F(ObjectDetectionConsumerTest, ProcessesDetectedObjectsEvent) {
// Arrange: prepare a sample with 3 detected objects
auto sample = std::make_shared<object_detection::DetectedObjectsMsg>();
sample->objects.resize(3);
sample->objects[0].class_id = 1; // Pedestrian
sample->objects[0].confidence = 0.92f;
sample->objects[1].class_id = 2; // Vehicle
sample->objects[1].confidence = 0.88f;
sample->objects[2].class_id = 0; // Unknown (below threshold)
sample->objects[2].confidence = 0.35f;
EXPECT_CALL(mock_proxy_, GetNewSamples(testing::_))
.WillOnce(testing::Return(ara::com::SamplePtr<...>(sample)));
// Act
auto result = consumer_.ProcessLatestDetections();
// Assert: only objects above 0.5 confidence threshold
EXPECT_EQ(2, result.valid_objects.size());
EXPECT_EQ(1, result.valid_objects[0].class_id); // Pedestrian
EXPECT_EQ(2, result.valid_objects[1].class_id); // Vehicle
}
TEST_F(ObjectDetectionConsumerTest, HandlesServiceNotAvailable) {
EXPECT_CALL(mock_proxy_, GetNewSamples(testing::_))
.WillOnce(testing::Throw(ara::com::ServiceNotAvailableException{}));
auto result = consumer_.ProcessLatestDetections();
EXPECT_TRUE(result.valid_objects.empty());
EXPECT_EQ(ConsumerState::DEGRADED, consumer_.GetState());
}SOME/IP Communication Testing
SOME/IP (Scalable service-Oriented MiddlewarE over IP) is the communication protocol for AUTOSAR Adaptive service discovery and method calls. Testing SOME/IP requires validating both the protocol behavior and the service semantics.
SOME/IP Service Discovery Testing with Vector CANoe.Ethernet
/* Test: SD_FindService sends correct multicast to 239.192.255.255:30490 */
testcase TC_SOMEIP_SD_FindService_Multicast(void) {
EthernetPacket rxPkt;
// Trigger service consumer startup
writeDbgLevel(0, "Triggering service consumer...");
$ServiceConsumer_Start = 1;
// Expect multicast FindService packet within 500ms
if (TestWaitForEthernetPacket(rxPkt, 500)) {
// Verify destination
TestCheckEqual(rxPkt.IP.Dest, "239.192.255.255",
"SD FindService: multicast destination");
TestCheckEqual(rxPkt.UDP.DestPort, 30490,
"SD FindService: SOME/IP-SD port");
// Parse SOME/IP-SD header
long sdMessageId = rxPkt.Payload.DWord(0);
TestCheckEqual(sdMessageId, 0xFFFF8100, /* SD Message ID */
"SD header: Message ID");
long entryType = rxPkt.Payload.Byte(16); /* Entry array offset */
TestCheckEqual(entryType, 0x00, /* FindService entry type */
"SD Entry: FindService type byte");
} else {
TestStepFail("TC_SOMEIP_SD_FindService", "No FindService packet within 500ms");
}
}
/* Test: Method call returns correct response within 100ms */
testcase TC_SOMEIP_MethodCall_RTT(void) {
long startTime, rtt;
startTime = TimeNow();
$ServiceProxy_CallGetSpeed = 1; /* Trigger method call */
if (TestWaitForSignal($ServiceProxy_GetSpeed_Response, 100)) {
rtt = TimeNow() - startTime;
TestCheckRange(rtt, 0, 100, "SOME/IP method RTT (ms)");
TestCheckRange($ServiceProxy_GetSpeed_Result, 0.0, 300.0,
"GetSpeed result range (kph)");
} else {
TestStepFail("SOMEIP_RTT", "Method response not received within 100ms");
}
}Python-based SOME/IP Testing with pySOMEIP
For CI-integrated SOME/IP testing without CANoe licenses:
import asyncio
import someip # pip install pysomeip
SERVICE_ID = 0x1234
INSTANCE_ID = 0x0001
METHOD_ID_GET_SPEED = 0x0010
ECU_IP = "192.168.1.100"
SOMEIP_PORT = 30501
async def test_someip_get_speed_method():
"""Verify SOME/IP GetSpeed method returns valid response."""
client = someip.SomeIPClient(ECU_IP, SOMEIP_PORT)
await client.connect()
request = someip.SomeIPMessage(
service_id=SERVICE_ID,
method_id=METHOD_ID_GET_SPEED,
client_id=0xDEAD,
session_id=0x0001,
message_type=someip.MessageType.REQUEST,
payload=b''
)
response = await asyncio.wait_for(client.call(request), timeout=0.1)
assert response.return_code == someip.ReturnCode.OK, (
f"Unexpected return code: {response.return_code}"
)
# Payload: float32 speed in kph
import struct
speed = struct.unpack('>f', response.payload)[0]
assert 0.0 <= speed <= 300.0, f"Speed out of range: {speed}"
asyncio.run(test_someip_get_speed_method())Integration Strategy: Mixing Classic and Adaptive
Modern vehicles use gateways that bridge AUTOSAR Classic CAN-based signals to AUTOSAR Adaptive SOME/IP services. Testing this boundary requires end-to-end scenarios:
[Classic ECU] [Gateway ECU] [Adaptive ECU]
ABS SWC → RTE → Com → CAN → CAN → Signal→Service → SOME/IP → ARA → App
MappingTest the gateway mapping by injecting a CAN signal and asserting the corresponding SOME/IP service update:
import can
import asyncio
import someip
async def test_gateway_abs_signal_to_service():
"""
Verify the gateway maps CAN ABS_Active signal (0x2B0, bit 3)
to SOME/IP VehicleDynamics.ABSActive event update.
"""
bus = can.interface.Bus(channel='can0', bustype='socketcan')
someip_client = someip.SomeIPClient("192.168.1.50", 30501)
await someip_client.connect()
await someip_client.subscribe_event(0x2200, 0x0001, 0x8001) # ABSActive event
# Inject CAN frame: ABS_Active = 1
frame = can.Message(
arbitration_id=0x2B0,
data=[0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # bit 3 set
is_extended_id=False
)
bus.send(frame)
# Expect SOME/IP event notification within 20ms (gateway latency budget)
event = await asyncio.wait_for(someip_client.receive_event(), timeout=0.020)
assert event.service_id == 0x2200
assert event.event_id == 0x8001
assert event.payload[0] == 0x01 # ABSActive = True
bus.shutdown()CI Pipeline for AUTOSAR Testing
# .github/workflows/autosar-tests.yml
jobs:
swc-unit-tests:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Build SWC tests (no hardware)
run: |
cmake -B build -DTEST_TARGET=HOST -DAUTOSAR_STACK=STUB
cmake --build build --target swc_tests
- name: Run SWC unit tests
run: ./build/swc_tests --gtest_output=xml:results.xml
- name: Check coverage thresholds (ASIL B: 100% branch)
run: |
gcovr --branches --fail-under-branch 100 \
--exclude '.*_Mock.*' --exclude '.*_Stub.*'
someip-integration:
runs-on: ubuntu-22.04
services:
vsomeip-daemon:
image: someip-daemon:latest
steps:
- name: Run SOME/IP integration tests
run: pytest tests/someip/ -v --timeout=10Testing AUTOSAR software at all layers — BSW configuration, SWC logic with RTE mocks, Adaptive service contracts, and SOME/IP protocol correctness — is the only way to catch problems before they reach the vehicle. Mock at the right boundary (RTE for Classic, ara::com for Adaptive), and you can run the vast majority of tests without any hardware.