07 · Automated Test Sequences in CANoe¶
Module 2 introduced CANoe's Test Module structure (testcase,
testfunction) for individual checks. Real suites chain dozens of
these into ordered test sequences with shared setup, teardown, and
pass/fail reporting that survives a single case failing without
aborting the whole run. This module covers that orchestration layer.
About this module
The CAPL test-sequence constructs, XML test report structure, and execution-control APIs below are genuine, documented CANoe behavior. Nothing here was captured from a live CANoe session — treat every snippet as a reviewed reference and verify exact report formatting against your installed CANoe version.
Test module structure, end to end¶
variables
{
int gSetupOk;
}
testpreparation
{
gSetupOk = 0;
// Runs once, before any testcase in this module
if (InitBusSimulation() == 1)
{
gSetupOk = 1;
}
}
testcase tc_CoolantOverheatBoundary()
{
testCaseTitle("Coolant overheat activates above 120C, not at/below");
if (gSetupOk == 0)
{
testStepFail("Preparation failed, skipping");
return;
}
testStep("Set CoolantTemp=120.0");
SetSignalValue("CoolantTemp", 120.0);
testWaitForTimeout(150);
testStepCheck("OverheatWarning stays 0", GetSignalValue("OverheatWarning") == 0);
testStep("Set CoolantTemp=120.1");
SetSignalValue("CoolantTemp", 120.1);
testWaitForTimeout(150);
testStepCheck("OverheatWarning goes to 1", GetSignalValue("OverheatWarning") == 1);
}
testcase tc_LowFuelHysteresis()
{
testCaseTitle("Low fuel warning hysteresis 10%/15%");
// ... independent test body, order-independent of tc_CoolantOverheatBoundary
}
testcasefinalization
{
// Runs once after all testcases, regardless of individual pass/fail
ResetBusSimulation();
}
The structural rule that matters most: testpreparation and
testcasefinalization always run exactly once per module execution,
while every testcase function runs independently and a failure in
one must not prevent the others from executing — CANoe's test module
runner already guarantees this as long as you don't hand-roll your own
early-exit logic that skips sibling testcases.
Ordering and independence¶
A frequent design mistake is writing testcases that depend on each
other's side effects (e.g. tc_02 assumes tc_01 left a DTC set).
This breaks the moment someone reorders the module, runs a single
testcase in isolation for debugging, or a test-selection tool runs a
subset in CI (Level 4's continuous-testing module). Each testcase
should:
- Establish its own preconditions explicitly (even if that duplicates
a
SetSignalValuecall another testcase also makes). - Clean up any state it changed that isn't already handled by
testcasefinalization, if that state could leak into a differently ordered run.
Reporting and pass/fail semantics¶
CANoe's test report distinguishes several outcome levels per step, not just pass/fail:
| Step API | Meaning | Effect on testcase result |
|---|---|---|
testStepPass(msg) |
Explicit pass | Testcase passes if all steps pass |
testStepFail(msg) |
Explicit fail | Testcase fails, execution of that testcase can continue or return |
testStepCheck(msg, cond) |
Pass/fail based on cond |
Same as above, condition-driven |
testCaseFatalError(msg) |
Unrecoverable for this testcase | Aborts the testcase immediately, module continues |
testCaseInconclusive(msg) |
Neither pass nor fail | Common for skipped/preconditions-not-met tests, kept out of pass-rate stats |
Using testCaseInconclusive correctly matters for CI dashboards
(Level 4): a testcase that couldn't run because bench hardware wasn't
present should never silently count as either a pass (false confidence)
or a fail (false alarm) — inconclusive is the honest third option.
Building a sequence across multiple test modules¶
CANoe's Test Configuration (.testcfg / Test Environment)
orders multiple test modules and controls whether a module's failure
halts the whole configuration or just gets recorded and moves on. The
two common patterns:
| Pattern | Configuration | Use case |
|---|---|---|
| Fail-fast | Configuration halts on first module failure | Smoke tests, gating a build before spending time on the full suite |
| Fail-forward | All modules run regardless of earlier failures | Full regression run (Level 3 Module 7) where you want a complete picture in one pass |
A well-organized suite usually runs a small fail-fast smoke sequence first, then the full fail-forward regression sequence only if the smoke sequence passes — avoiding a 45-minute full run against a build that's obviously broken in the first two minutes.
XML/report output¶
CANoe test modules can export results as XML (and HTML) reports suitable for CI ingestion:
<TestModule name="CoolantAndFuelTests">
<TestCase name="tc_CoolantOverheatBoundary" verdict="passed">
<TestStep title="Set CoolantTemp=120.0" verdict="passed"/>
<TestStep title="Set CoolantTemp=120.1" verdict="passed"/>
</TestCase>
<TestCase name="tc_LowFuelHysteresis" verdict="failed">
<TestStep title="Verify warning off at 16%" verdict="failed"
detail="Expected 0, got 1"/>
</TestCase>
</TestModule>
This structure is exactly what a CI pipeline (Level 4 Module 2) parses to compute pass rates and flag regressions — designing testcase titles and step messages to be self-explanatory in this report is what makes a failed nightly run debuggable without re-running it locally first.
Cheat sheet¶
| Construct | Runs | Purpose |
|---|---|---|
testpreparation |
Once, before all testcases | Shared setup |
testcase |
Once each, order-independent | The actual check |
testcasefinalization |
Once, after all testcases | Shared teardown |
testStepCheck |
Per assertion | Condition-driven pass/fail |
testCaseInconclusive |
On unmet preconditions | Keeps skipped tests out of pass/fail stats |
| Fail-fast vs. fail-forward | Test Configuration level | Smoke vs. full regression strategy |
How It Actually Works: why one slow testcase doesn't stall the whole module¶
Module 2 established that a testcase runs as its own suspendable test
task, yielding back to the measurement's event loop during
testWaitForTimeout. Test sequences depend on one more layer built on
top of that: the test module runner itself schedules testcases
sequentially, but each one still owns the full event loop while it
runs — which is exactly why the fail-fast/fail-forward choice in this
module's exercise is a real architectural decision, not just a report
filter.
Concretely, tc_CoolantOverheatBoundary's two testWaitForTimeout(150)
calls block that testcase's task for up to 300 ms of wall-clock time
combined, during which the module runner does not start
tc_LowFuelHysteresis — testcases in one module execute strictly one at
a time, in sequence, each getting exclusive use of the shared bus
simulation between its own setup and testcasefinalization. This is why
independence (each testcase establishing its own preconditions rather
than relying on a predecessor's leftover state) isn't just good hygiene
for readability — it's the only thing that makes "run a fail-forward
config" and "run a single testcase in isolation for debugging" produce
identical results for testcase N: both execute against exactly the state
testpreparation and testcase N's own setup created, never against
whatever tc_{N-1} happened to leave behind, because the runner gives
you no guaranteed ordering-independent isolation beyond what
testpreparation/testcasefinalization explicitly provide.
The fail-fast/fail-forward distinction operates one level up, at the
Test Configuration, which sequences whole modules rather than
individual testcases — a module that hard-fails under a fail-fast
configuration causes the configuration to skip starting the next
module's testpreparation entirely, which is why DTCLifecycleSuite's
hardware-unavailability problem in the exercise below has to be solved
inside its own testcases via testCaseInconclusive, not by reordering
modules: even a perfectly-ordered fail-forward sequence still needs each
individual testcase to correctly distinguish "the bench wasn't there"
from "the DUT actually failed," because the module-level scheduler has
no visibility into why a testcase didn't pass, only that it didn't.
Exercise¶
You're assembling a nightly sequence with three existing test modules:
SmokeTests (5 basic sanity testcases, ~1 minute), CANSignalSuite
(40 testcases, ~10 minutes), and DTCLifecycleSuite (15 testcases,
~15 minutes, requires a bench with real hardware that's occasionally
unavailable).
- Propose an ordering and a fail-fast/fail-forward policy for these three modules, explaining your reasoning for each choice.
DTCLifecycleSuitesometimes can't run because the bench is occupied by another team. Which CAPL construct from this module should its testcases use in that situation, and why is it wrong to have them report a hard pass or fail instead?- Sketch (as pseudocode, not full CAPL) one
testcasefromCANSignalSuitethat demonstrates proper independence — i.e., it sets up its own preconditions rather than assuming a prior testcase already did.