integration-testing.md

May 23, 2026 · View on GitHub

FieldValue
TitleIntegration Testing Design
Typedesign
Statusdraft
Version0.2.0
Componentintegration-testing
Date2026-04-12

Note — Design-level document: This document describes how the integration tests are implemented. It expands on the architecture by specifying component responsibilities, data flows, and design decisions that would not be obvious to a reviewer unfamiliar with the system.

Diagrams: All visuals must be Mermaid fenced code blocks or ASCII art. External image references are not allowed.


Overview

This document covers the design of the integration_tests/ suite. It references the integration testing architecture in documentation/architecture/system.md (section: Integration Testing) for the high-level context and the requirements (documentation/requirements/) for the acceptance criteria each scenario verifies.

The test suite uses the amp-cucumber-cpp-runner v4.0.0 framework. Scenarios are authored in Gherkin (.feature files). Step definitions share state through a typed context (context.Get<FocIntegrationFixture>()).


Responsibilities

Is responsible for:

  • Providing a shared test context (FocIntegrationFixture) that owns and wires all system-under-test components for each Cucumber scenario
  • Mocking hardware peripherals (PWM, encoder, current sense) directly via PlatformFactoryMock, which implements PlatformFactory (and therefore foc::ThreePhaseInverter + foc::Encoder) with MOCK_METHOD for each operation
  • Supplying an in-memory EEPROM stub so the full NVM stack executes synchronously without embedded hardware
  • Enabling step-by-step async callback control for calibration sequence testing
  • Injecting CAN commands directly into the category server to verify state machine transitions independently of CAN transport encoding

Is NOT responsible for:

  • Testing FOC control algorithm correctness — covered by unit tests in core/foc/implementations/test/
  • Testing CAN framing or transport encoding — covered by can-lite unit and integration tests
  • Running on an embedded target — host-only suite

Platform Factory Mock

PlatformFactoryMock mocks every pure virtual of application::PlatformFactory, including the inverter hot-path methods it inherits from foc::ThreePhaseInverter (PhaseCurrentsReady, ThreePhasePwmOutput, Start, Stop, BaseFrequency, MaxCurrentSupported) and the encoder methods from foc::Encoder (Read, Set, SetZero), plus the configuration methods (ConfigureAdcAndPwm, SetEncoderResolution, ConfigureCanBus, CanBus). There are no creator proxies, no per-peripheral wrapper mocks, and no PlatformAdapter.

The fixture registers standing EXPECT_CALL defaults in its constructor:

MethodDefault expectation
PhaseCurrentsReady, ThreePhasePwmOutput, Start, Stop, Set, SetZeroTimes(AnyNumber())
BaseFrequencyWillRepeatedly(Return(hal::Hertz{ 10000 }))
ReadWillRepeatedly(Return(foc::Radians{ 0.0f }))

The EepromStub (512-byte in-memory array, all 0xFF at construction, synchronous R/W) is a separate non-mock class owned by the fixture. The NVM regions reference the stub directly.

FOC Integration Fixture

Central test fixture (FocIntegrationFixture) shared across all scenarios via the Cucumber context. Member construction order is declaration order; the key constraint is that the FocStateMachineImpl must be constructed after the direct-method expectations are registered on PlatformFactoryMock.

Lifecycle of each scenario:

sequenceDiagram
    participant Context
    participant Fixture as FocIntegrationFixture
    participant PFM as PlatformFactoryMock
    participant SM as FocStateMachineImpl
    participant NVM as NonVolatileMemoryImpl

    Context->>Fixture: Emplace (constructor)
    Fixture->>PFM: EXPECT_CALL PhaseCurrentsReady / ThreePhasePwmOutput / Start / Stop (AnyNumber)
    Fixture->>PFM: EXPECT_CALL BaseFrequency → 10 kHz (AnyNumber)
    Fixture->>PFM: EXPECT_CALL Read → 0 rad (AnyNumber)
    Fixture->>PFM: EXPECT_CALL Set / SetZero (AnyNumber)
    Note over Fixture,NVM: calibrationRegion / configRegion reference eepromStub directly

    Context->>Fixture: GIVEN step calls ConstructWithInvalidNvm()
    Fixture->>SM: emplace(TerminalAndTracer, MotorHardware{platformFactory, platformFactory, vdc}, nvm, ...)
    Fixture->>NVM: IsCalibrationValid(cb)
    NVM-->>Fixture: cb(false) via event dispatcher
    Note over Fixture: State machine in Idle

The FocStateMachineImpl is always constructed with AutoTransitionPolicy so that test steps can call CmdCalibrate(), CmdEnable() and CmdDisable() directly without going through the terminal CLI.

State Machine Bridge

FocMotorStateMachineBridge implements FocMotorCategoryServerObserver and delegates the relevant lifecycle commands to FocStateMachineBase:

CAN observer callbackState machine method
OnStart()CmdEnable()
OnStop()CmdDisable()
OnClearFault()CmdClearFault()
All othersno-op

Component Details

Calibration Flow

The calibration scenario requires step-by-step control of async callbacks. The fixture captures each service callback as a member:

sequenceDiagram
    participant Step as Gherkin step
    participant Fixture
    participant SM as State Machine
    participant EIM as ElectricalIdentMock
    participant AMK as AlignmentMock

    Step->>SM: CmdCalibrate()
    SM->>EIM: EstimateNumberOfPolePairs(_, cb)
    EIM-->>Fixture: capturedPolePairsCallback = cb

    Step->>Fixture: CompletePolePairsEstimation(7)
    Fixture->>SM: capturedPolePairsCallback(7)
    SM->>EIM: EstimateResistanceAndInductance(_, cb)
    EIM-->>Fixture: capturedRLCallback = cb

    Step->>Fixture: CompleteRLEstimation(R, L)
    Fixture->>SM: capturedRLCallback(R, L)
    SM->>AMK: ForceAlignment(polePairs, cfg, cb)
    AMK-->>Fixture: capturedAlignmentCallback = cb

    Step->>Fixture: CompleteAlignment(offset)
    Fixture->>SM: capturedAlignmentCallback(offset)
    SM->>NVM: SaveCalibration(data, cb)
    Note over NVM: real EEPROM write, completes synchronously
    SM-->>Step: state == Ready

CAN Integration

To keep scenarios focused on the state machine response rather than CAN wire encoding, CAN frames are injected via FocMotorCategoryServer::HandleMessage() directly. A CanFrameTransport backed by a StrictMock<hal::CanMock> is still required because the server sends acknowledgement frames via the transport.

sequenceDiagram
    participant Step as Gherkin step
    participant MCS as FocMotorCategoryServer
    participant BRG as FocMotorStateMachineBridge
    participant SM as State Machine

    Step->>MCS: HandleMessage(focStartId, data)
    MCS->>BRG: OnStart()
    BRG->>SM: CmdEnable()
    SM-->>Step: CurrentState() == Enabled

Interfaces

Provided to Step Definitions

The FocIntegrationFixture exposes the following test API consumed by Gherkin step definitions:

MethodPurpose
ConstructWithInvalidNvm()Constructs the state machine with an empty EEPROM — starts in Idle
ConstructWithValidNvm(data)Pre-populates EEPROM and constructs the state machine — starts in Ready
SetupCalibrationExpectations()Arms the pole-pairs estimation mock to capture its callback
CompletePolePairsEstimation(n)Fires the captured pole-pairs callback with a success result
CompleteRLEstimation(R, L)Fires the captured R/L callback and arms the alignment mock
CompleteAlignment(offset)Fires the captured alignment callback, triggering NVM save
SetupCanIntegration()Wires the CAN category server and bridge to the state machine
InjectCanStart()Injects a CAN Start message via FocMotorCategoryServer::HandleMessage
InjectCanStop()Injects a CAN Stop message via FocMotorCategoryServer::HandleMessage
InjectCanClearFault()Injects a CAN ClearFault message via FocMotorCategoryServer::HandleMessage

Required from System Under Test

ComponentInterfacePurpose
FOC State MachineFocStateMachineBaseLifecycle commands and state inspection
Non-Volatile MemoryNonVolatileMemoryCalibration data load and save for the NVM-boot path
CAN Category ServerFocMotorCategoryServerCAN command dispatch via HandleMessage
Electrical IdentificationElectricalParametersIdentificationControlled via mock in calibration scenarios
Motor AlignmentMotorAlignmentControlled via mock in calibration scenarios
Fault NotifierFaultNotifierTriggered via mock to test hardware-fault transitions

Feature-to-Requirements Mapping

Feature fileScenariosRequirements covered
state_machine_lifecycle.featureIdle on boot, calibration start, enable/disable, fault, fault clear, valid NVM bootREQ-SM-001..010
calibration_flow.featureFull calibration success, calibration failureREQ-SM-003..005, REQ-SM-011
can_foc_motor.featureCAN Start, CAN Stop, CAN ClearFaultREQ-INT-001..003 (REQ-INT-004 is structural — verified by bridge design, not a dedicated scenario)