| name | code-driven-cluster-tdd-implementation |
| description | Guidelines for implementing or migrating Matter server clusters to the code-driven pattern using Test-Driven Development (TDD).
|
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-development for core implementation patterns.
- Read
matter-specification-access for 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-migration for
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.cpp using
ClusterTester.
- 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() using AttributeListBuilder and generated
metadata.
- Verify test passes.
- 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.
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
ReadAttribute switch, fetching data from
Delegate or member variables.
- Ensure the
default case returns
Protocols::InteractionModel::Status::UnsupportedAttribute directly.
- Verify test passes.
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).
- Implement:
- Add the case in
WriteAttribute or InvokeCommand switch.
- Decode the payload.
- Execute logic (update state or call delegate).
- Return
UnsupportedAttribute or UnsupportedCommand in the default
case.
- Verify Success: Run tests and ensure they pass.
Step 5: Create CodegenIntegration Layer
- Create or update
CodegenIntegration.h and .cpp in the cluster folder.
- Provide implementations for generated callbacks (e.g.,
Matter<ClusterName>ClusterInitCallback).
- Use
CodegenClusterIntegration::RegisterServer to 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.yaml and zcl.json as per
code-driven-cluster-migration skill.
- Run
zap_regen_all.py and commit all generated files.
- Integration Testing: Build an example app and test against
chip-tool.
- Verify Spec Conformance: Refer to the relevant cluster specification
.adoc file.
- Verify Against Test Plan: Refer to the relevant test plan
.adoc file.
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. Return
Status::UnsupportedCommand for unknown commands in InvokeCommand.
4.3 Timer-Driven Logic
- Failing Test: Verify that time passes and state changes (e.g.,
IdentifyTime decrements).
- Mocking: Do NOT use
sleep(). Use TimerDelegate and
TimerDelegateMock to 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
ListEncodeHelper and 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
OnPlatformEventHandler and hook it up in
Startup/Shutdown to 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"