Test Module Development
Learning Objectives
After completing this article, you will be able to:
- Understand the basic concepts and architecture of CAPL Test Modules
- Master the core components of test modules:
testcase,MainTest, and Test Steps - Learn to use the Test Service Library (TSL) for Checks and Stimulus functions
- Independently create a complete test module and generate test reports
- Have a clear understanding of the final project in this tutorial series
From Simulation to Testing
In the previous articles, we've learned how to use CAPL to create simulation nodes. Simulation nodes can simulate real ECUs, send messages, and respond to events, just like actual hardware.
However, as an automotive electronics engineer, you might ask:
"My ECU code is finished, and the simulation runs smoothly. But how do I know it really works according to requirements? Are there any bugs?"
At this point, you need to transition from simulation to testing. CAPL provides specialized test module functionality that allows you to write automated test cases to verify whether ECU behavior meets expectations.
In simple terms:
- Simulation nodes = "Pretend" other ECUs exist
- Test modules = "Verify" my ECU works correctly
What is a Test Module?
A Test Module is a special program type in CAPL used for automated testing. Unlike simulation nodes, test modules don't participate in bus communication. Instead, they:
- Send Stimulus: Send test commands to the ECU under test
- Check Responses: Verify whether ECU behavior meets expectations
- Generate Reports: Automatically record test results
Core Components of Test Modules
A complete test module contains the following parts:
1. testcase (Test Case)
testcase is the basic unit of testing, defining a complete test scenario:
testcase TC_DoorLockTest()
{
// Test implementation
}
2. MainTest (Main Test Function)
MainTest is the entry point of the test module, responsible for scheduling and organizing all test cases:
void MainTest()
{
// Call test cases
TC_DoorLockTest();
TC_WindowControlTest();
}
3. Test Steps (Test Step)
Test steps are the smallest units within test cases, used to record and judge the testing process:
TestStep(0, "Step 1", "Send lock command");
TestStepPass("Lock command sent successfully");
4. Test Service Library (TSL)
TSL provides rich testing tool functions, including:
- Checks: Verify signals, messages, timing, etc.
- Stimulus: Generate test data, simulate signal changes
Creating Your First Test Module
Let's start with a simple example to test the door lock functionality.
Step 1: Create a New Test Module File
- In CAPL Browser, click
File→New→CAPL Test Module File - The system will create a test module file with basic structure
Step 2: Write the MainTest Function
First, we create an empty MainTest function framework:
void MainTest()
{
// Step 1: Call test cases
// Step 2: Add more test cases here
}
Step 3: Create the First Test Case
Now, we add a test case:
testcase TC_DoorLockTest()
{
// This test case verifies door lock functionality
}
Step 4: Add Test Steps
Next, we complete this test case:
variables
{
byte doorStatus = 0; // Variable to store received door status
}
// Handler to update doorStatus when DoorStatus message is received
on message 0x300 // DoorStatus message
{
doorStatus = this.byte(0); // Update door status from message
}
testcase TC_DoorLockTest()
{
// Step 1: Set test case title and description
TestCaseTitle("TC 1.0", "Door Lock Function Test");
TestCaseDescription("This test verifies that the door lock responds correctly to lock commands");
// Step 2: Initialize system
TestStep(0, "Initialization", "Reset door status variables");
doorStatus = 0; // Reset door status
// Step 3: Send lock command
TestStep(1, "Send Command", "Send lock command to ECU");
message 0x200 lockCmd;
lockCmd.byte(0) = 0x01; // Lock command
output(lockCmd);
// Step 4: Wait for response
TestStep(1, "Wait Response", "Wait for door status response");
long result;
result = TestWaitForMessage(0x300, 500); // Use message ID, timeout 500ms
// Step 5: Verify result
if (result == 1)
{
TestStepPass("Door status message received");
}
else
{
TestStepFail("Door status message not received");
}
}
Let's understand this code line by line:
TestCaseTitle(): Set the test case title and numberTestCaseDescription(): Add detailed description of the test caseTestStep(): Record a test step (without judgment of success/failure)TestStepPass(): Record a successful test stepTestStepFail(): Record a failed test stepTestWaitForMessage(): Wait for a specified message to arrive
Step 5: Complete the MainTest Function
Now, we complete the MainTest function:
void MainTest()
{
// Set module title
TestModuleTitle("Door Control Test Module");
// Execute test cases
TC_DoorLockTest();
// Add more test cases as needed
}
Note: Test module files have a
.canextension, but their structure is slightly different from simulation nodes.
Deep Dive into Test Case Development
In real projects, a test module usually contains multiple test cases. Let's look at a more complete example.
Complete Test Case Example
variables
{
byte doorStatus = 0; // Store received door status
}
// Update doorStatus when message received
on message 0x300 // DoorStatus message
{
doorStatus = this.byte(0);
}
testcase TC_DoorLockTest()
{
// ====== Test Case Setup ======
TestCaseTitle("TC 1.0", "Door Lock Function Test");
TestCaseDescription("This test verifies that the door lock responds correctly to lock commands");
// ====== Test Execution ======
// Step 1: Initialize
TestStep(0, "Init", "Initialize door status to unlocked");
doorStatus = 0x00; // Reset door status variable
TestStepPass("Initialization complete");
// Step 2: Send lock command
TestStep(1, "Send Lock", "Send lock command (0x200) to door controller");
message 0x200 lockCmd;
lockCmd.byte(0) = 0x01;
output(lockCmd);
TestStepPass("Lock command sent");
// Step 3: Wait for response
TestStep(1, "Wait", "Wait for door status update (timeout: 500ms)");
long result;
result = TestWaitForMessage(0x300, 500); // Wait for message ID 0x300
// Step 4: Verify response
TestStep(2, "Verify", "Check if door is now locked");
if (result == 1) // Message received
{
if (doorStatus == 0x01) // Door is locked
{
TestStepPass("Door successfully locked");
}
else
{
TestStepFail("Door status incorrect: expected 0x01, got 0x%02X", doorStatus);
}
}
else // Timeout or error
{
TestStepFail("No response from door controller");
}
// Step 5: Cleanup (optional)
TestStep(0, "Cleanup", "Reset door status");
}
Key Points:
- Clear Structure: Divide the test case into Setup, Execution, and Cleanup phases
- Detailed Steps: Record each key operation with TestStep
- Clear Judgments: Use TestStepPass and TestStepFail to clearly mark test results
- Parameterized Messages: Using
TestStepFail("...", value)can output specific values when failing
Testing Message Reception
TestWaitForMessage() is one of the most commonly used functions in test modules:
// Wait for a specific message by name
result = TestWaitForMessage(DoorStatus, 1000);
// Wait for a message by ID
result = TestWaitForMessage(0x201, 1000);
// Wait for any message
result = TestWaitForMessage(1000);
Return Value Explanation:
1: Message successfully received0: Timeout-2: Aborted due to constraint violation-1: General error
Using the Test Service Library (TSL)
The Test Service Library (TSL) is a powerful testing toolkit provided by CAPL. It contains two core types of functions:
1. Checks
Checks are used to continuously monitor signals, messages, or time conditions. When conditions are violated, they are automatically recorded in the test report.
Example 1: Check Signal Value Range
dword checkId;
// Create a check: EngineSpeed should be between 800 and 6000 RPM
checkId = ChkCreate_MsgSignalValueRangeViolation(
EngineData, // Message name
EngineData::EngineSpeed, // Signal name (use :: for signal access)
800, // Minimum value
6000 // Maximum value
);
// Activate the check as a constraint
TestAddConstraint(checkId);
// ... run test ...
// Deactivate and cleanup
TestRemoveConstraint(checkId);
ChkControl_Destroy(checkId);
Example 2: Check Message Cycle Time
dword checkId;
// Create a check: VehicleSpeed message should occur every 100ms (±10ms)
checkId = ChkCreate_MsgAbsCycleTimeViolation(
VehicleSpeed, // Message name
90, // Minimum cycle time (ms)
110 // Maximum cycle time (ms)
);
TestAddConstraint(checkId);
// Run test...
TestRemoveConstraint(checkId);
ChkControl_Destroy(checkId);
How Checks Work:
- Create the check (
ChkCreate_*) - Add as constraint (
TestAddConstraint) - Check runs in the background, automatically monitoring
- Remove constraint (
TestRemoveConstraint) - Destroy check (
ChkControl_Destroy)
2. Stimulus Generators
Stimulus is used to actively generate test data, simulating sensor signals, environmental variable changes, etc.
Example 1: Generate Ramp Signal
dword stimId;
// Create a ramp stimulus for AcceleratorPedal signal
// Start value: 0, End value: 100, Duration: 2000ms
stimId = StmCreate_Ramp(
EngineData, // Message
EngineData::AcceleratorPedal, // Signal
0, // Start value
100, // End value
5, // Step size
100, // Cycle time (ms)
2000, // Total duration (ms)
0 // Offset
);
// Start the stimulus
StmControl_Start(stimId);
// Wait for completion
TestWaitForTimeout(2500);
// Stop and cleanup
StmControl_Stop(stimId);
ChkControl_Destroy(stimId);
Example 2: Toggle Signal Values
dword stimId;
// Create a toggle stimulus for WindowSwitch signal
stimId = StmCreate_Toggle(
DoorControl, // Message
DoorControl::WindowSwitch, // Signal
0, // Value 1 (window up)
1, // Value 2 (window down)
500, // Toggle interval (ms)
3 // Number of toggles
);
StmControl_Start(stimId);
TestWaitForTimeout(2000);
StmControl_Stop(stimId);
ChkControl_Destroy(stimId);
Real-World Case: Complete Test Case
Let's use a complete example to test the door window functionality, using both Checks and Stimulus:
testcase TC_WindowControlTest()
{
dword checkId, stimId;
// ====== Setup ======
TestCaseTitle("TC 2.0", "Window Control Test");
TestCaseDescription("Test window up/down movement with stimulus and checks");
// Create check: Verify window position signal is valid
checkId = ChkCreate_MsgSignalValueRangeViolation(
DoorFeedback,
DoorFeedback::WindowPosition,
0, // Min: fully up
100 // Max: fully down
);
TestAddConstraint(checkId);
// ====== Test Execution ======
// Step 1: Move window down
TestStep(1, "Stimulus", "Generate window down signal");
stimId = StmCreate_Toggle(
DoorControl,
DoorControl::WindowSwitch,
0, 1, 500, 1 // Toggle once: 0->1
);
StmControl_Start(stimId);
TestStepPass("Window down signal generated");
// Step 2: Wait for movement
TestStep(1, "Wait", "Wait for window to move down (2000ms)");
TestWaitForTimeout(2000);
StmControl_Stop(stimId);
ChkControl_Destroy(stimId);
// Step 3: Verify position
TestStep(2, "Verify", "Check if window moved to down position");
if (windowPosition >= 50) // Threshold for "down"
{
TestStepPass("Window moved down successfully");
}
else
{
TestStepFail("Window did not move: position is %d", windowPosition);
}
// ====== Cleanup ======
TestRemoveConstraint(checkId);
ChkControl_Destroy(checkId);
}
Running and Debugging Test Modules
Adding Test Modules in CANoe
- Open a CANoe project
- In the menu bar, select
Test→Test Setup - Right-click on
Test Environment, selectInsert Test Module - Browse and select your test module file (
.can) - Click
OK
[!SCREENSHOT]
Location: CANoe Test Setup window
Content: Shows the newly added test module
Annotation: Circle the test module node and Test Units panel
Running Tests
- In the
Test Setupwindow, right-click on the test module - Select
StartorStart with Report - Observe the
Test Reportwindow to see test progress and results
[!SCREENSHOT]
Location: CANoe Test Report window
Content: Shows test execution process and results
Annotation: Circle Passed/Failed status and detailed steps
Viewing Test Reports
Test reports display:
- Test Case List: All executed testcases
- Test Step Details: Time and results for each TestStep
- Judgment Results: Pass/Fail/Inconclusive
- Timestamps: Execution time for each step
Best Practices
1. Test Case Design Principles
Single Responsibility: Each testcase should test only one functionality.
// Good: One functionality per test case
testcase TC_DoorLockTest() { ... }
testcase TC_WindowUpTest() { ... }
// Avoid: Mixing multiple functionalities
testcase TC_DoorAndWindowTest() { ... } // Don't do this
Clear Naming: Use descriptive test case names.
// Good: Clear and descriptive
testcase TC_LockCommandResponseTime() { ... }
// Avoid: Vague names
testcase TC_Test1() { ... }
2. Improving Report Readability
Detailed Descriptions: Add clear descriptions for each testcase.
TestCaseTitle("TC 3.0", "Door Lock Response Time");
TestCaseDescription(
"This test verifies that the door controller responds to lock commands "
"within 100ms. The test sends a lock command and measures the time until "
"the door status feedback is received."
);
Hierarchical Steps: Use the LevelOfDetail parameter to distinguish important steps.
TestStep(0, "Critical", "System initialization"); // Very important
TestStep(1, "Normal", "Send test command"); // Standard step
TestStep(2, "Detail", "Parse response data"); // Detailed info
3. Modularity and Reuse
Create Test Libraries: Encapsulate common checks and stimulus into functions.
// Test helper function
void WaitForDoorLock()
{
long result;
result = TestWaitForMessage(DoorStatus, 1000);
if (result != 1)
{
TestStepFail("Door status not received");
}
}
// Reuse in multiple test cases
testcase TC_LockTest1()
{
// ... setup ...
WaitForDoorLock();
// ... verify ...
}
Summary
Through this article, we've mastered:
- Test Module Concepts: Unlike simulation nodes, test modules focus on verifying ECU behavior
- Core Components:
testcase: The basic unit of testingMainTest: Entry point and scheduler of test modulesTestStep: Recording and judging test steps
- Test Service Library (TSL):
Checks: Continuously monitor signals, messages, timing, and other conditionsStimulus: Actively generate test data, simulate signal changes
- Test Reports: Automatically generate detailed test execution records
Test modules are an important part of the CAPL development process. They enable you to automatically verify ECU functionality, improving testing efficiency and reliability.
Exercises
-
Basic Exercise: Modify the
TC_DoorLockTestfrom this article, adding a test case for the "unlock command". -
Advanced Exercise: Use
ChkCreate_MsgSignalValueRangeViolationto check whether theEngineSpeedsignal is within a reasonable range (0-8000 RPM). -
Challenge Exercise: Create a test case that uses
StmCreate_Rampto generate an accelerator pedal signal, gradually increasing from 0% to 100%, and check the engine speed response.