Code-Driven Cluster TDD Implementation
When to use this skill
Use this skill when implementing or migrating Matter server clusters to the code-driven pattern using Test-Driven Development (TDD).
[!IMPORTANT] > Preserve Legacy Behavior: If migrating an existing cluster, constantly refer to the legacy code to ensure no functional drop or unexpected behavior changes.
1. Prerequisites
- Read
code-driven-cluster-developmentfor core implementation patterns. - Read
matter-specification-accessfor instructions on how to access the Matter specification and test plans, so that tests can be based on the spec. - If performing a migration, read
code-driven-cluster-migrationfor general migration steps (renaming, directory layout). We assume you have already completed Phase 1 (Renames) and Phase 2 (Moves) before starting the TDD implementation.
2. Step-by-Step Implementation Workflow
Follow these steps for the substantive implementation using TDD:
Step 1: Initial Boilerplate & Tests Setup
- Create the new cluster class inheriting from
DefaultServerCluster. - Create a basic test fixture in
tests/Test<ClusterName>Cluster.cppusingClusterTester. - Verify that the cluster compiles and tests can run (even if empty).
Step 2: Implement Static Metadata Methods (TDD)
- Attributes():
- Write a failing test expecting
Attributes()to return the list of mandatory attributes. - Implement
Attributes()usingAttributeListBuilderand generated metadata. - Verify test passes.
- Write a failing test expecting
- AcceptedCommands():
- Write a failing test expecting
AcceptedCommands()to return the list of supported commands. - Implement
AcceptedCommands()returning the list (conditional on features). - Verify test passes.
- Write a failing test expecting
Step 3: Implement ReadAttribute (TDD)
- For each mandatory or supported attribute:
- Write a failing test reading the attribute via
tester.ReadAttribute()and expecting a default or mocked value. - Implement the case in
ReadAttributeswitch, fetching data from Delegate or member variables. - Ensure the
defaultcase returnsProtocols::InteractionModel::Status::UnsupportedAttributedirectly. - Verify test passes.
- Write a failing test reading the attribute via
Step 4: Implement Writable Attributes and Commands (TDD)
For each writable attribute or command, follow this cycle:
- Write a Failing Test:
- Attributes: Use
tester.WriteAttribute()and expect failure or success based on setup. - Commands: Invoke via
tester.Invoke()and assert failure (e.g.,UnsupportedCommand).
- Attributes: Use
- Implement:
- Add the case in
WriteAttributeorInvokeCommandswitch. - Decode the payload.
- Execute logic (update state or call delegate).
- Return
UnsupportedAttributeorUnsupportedCommandin thedefaultcase.
- Add the case in
- Verify Success: Run tests and ensure they pass.
Step 5: Create CodegenIntegration Layer
- Create or update
CodegenIntegration.hand.cppin the cluster folder. - Provide implementations for generated callbacks (e.g.,
Matter<ClusterName>ClusterInitCallback). - Use
CodegenClusterIntegration::RegisterServerto bridge ZAP defaults to the new cluster instance. - Maintain legacy classes (e.g.,
ChimeServer) as proxy wrappers if needed for backward compatibility.
Step 6: Verification & ZAP Regen
- Run all unit tests.
- Update
config-data.yamlandzcl.jsonas percode-driven-cluster-migrationskill. - Run
zap_regen_all.pyand commit all generated files. - Integration Testing: Build an example app and test against
chip-tool. - Verify Spec Conformance: Refer to the relevant cluster specification
.adocfile. - Verify Against Test Plan: Refer to the relevant test plan
.adocfile.
4. Common TDD Scenarios
4.1 Security Checks (CASE/FailSafe)
- Failing Test: Use
tester.SetSubjectDescriptor()to simulate non-CASE session, or don't arm FailSafe, and expect specific error (UnsupportedAccess,FailsafeRequired). - Implementation: Add checks at top of
InvokeCommand.
4.2 Async Completion & Breadcrumbs
- Failing Test: Mock the async callback and verify that side effects (like
setting breadcrumb via
BreadCrumbTracker) occur. - Implementation: Use the appropriate interface or callback (e.g.,
OnCommandNameComplete) to handle side effects. ReturnStatus::UnsupportedCommandfor unknown commands inInvokeCommand.
4.3 Timer-Driven Logic
- Failing Test: Verify that time passes and state changes (e.g.,
IdentifyTimedecrements). - Mocking: Do NOT use
sleep(). UseTimerDelegateandTimerDelegateMockto advance the mock clock and trigger timer callbacks synchronously.
4.4 List Attributes
- Failing Test: Read the list attribute and verify its contents.
- Implementation: Use
ListEncodeHelperand delegate methods to fetch items by index and encode them.
4.5 Handling Platform Events
- Failing Test: Write tests that trigger specific platform events (e.g.,
kFailSafeTimerExpired) directly on the platform event handler and verify expected side effects. - Implementation: Implement
OnPlatformEventHandlerand hook it up inStartup/Shutdownto listen for needed events.
5. Best Practices for Test Design
- Focused Tests: Keep tests small and focused on a single requirement. Avoid monolithic tests that verify multiple behaviors at once.
- Isolation: Use mocks (e.g.,
MockDelegate,MockBreadcrumbTracker) to isolate cluster logic from platform dependencies. This ensures tests are fast and deterministic. - Edge Cases: Always test boundary values (min/max) and invalid inputs to verify spec compliance and error handling.
6. Common Commands Reference
Environment Activation
source scripts/activate.sh
Or run commands directly in the environment:
scripts/run_in_build_env.sh "<command>"
Running Unit Tests
To compile and run a specific cluster test:
scripts/run_in_build_env.sh "ninja -C out/linux-x64-tests-clang src/app/clusters/<cluster-folder>/tests:Test<ClusterName>Cluster.run"
ZAP Regeneration
To regenerate files after updating templates or ZCL JSON:
scripts/run_in_build_env.sh "scripts/tools/zap_regen_all.py"