AUTOSAR Classic and Adaptive Testing Patterns for Automotive Software

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 Ignored

AUTOSAR 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
                                      Mapping

Test 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=10

Testing 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.

Read more

Start now free