TDD Writing Guidelines for NG Component Pattern Methods
When writing TDD tests for NG component Pattern methods (e.g., OnModifyDone(), OnDirtyLayoutWrapperSwap()), follow these guidelines to avoid common pitfalls.
Core Principles
1. Direct Method Invocation
Call the target method directly, not through indirect triggers.
- ✅
menuPattern->OnModifyDone()
- ❌
menuPattern->FireBuilder() (doesn't trigger OnModifyDone())
2. State Reset
Always reset potentially interfering state before calling target method.
// Reset to clean state before testing
auto renderContext = menuNode->GetRenderContext();
renderContext->ResetOuterBorder(); // Reset entire property group
// Then call target method
menuPattern->OnModifyDone();
3. API Verification
Use Grep/Read tools to verify method names, never guess.
# Search for method definition
grep -rn "ResetOuterBorder\|Reset.*Outer" frameworks/core/components_ng/render/render_context.h
# Understand macro-generated methods
grep -A 5 "ACE_DEFINE_PROPERTY_GROUP" frameworks/core/components_ng/property/property.h
ACE_DEFINE_PROPERTY_GROUP(OuterBorder, OuterBorderProperty) generates:
GetOrCreateOuterBorder(), GetOuterBorder(), CloneOuterBorder(), ResetOuterBorder()
- NOT
ResetOuterBorderRadius() (common mistake)
4. Branch Coverage
Write paired tests for if/else branches.
// Branch 1: Condition is true
HWTEST_F(Xxx, Test_BranchTrue, TestSize.Level1) {
borderRadiusVP.SetRadius(Dimension(10.0_vp)); // No PERCENT unit
renderContext->ResetOuterBorder();
pattern->OnModifyDone();
EXPECT_TRUE(outerRadius.has_value());
}
// Branch 2: Condition is false
HWTEST_F(Xxx, Test_BranchFalse, TestSize.Level1) {
borderRadiusPercent.SetRadius(Dimension(50.0f, DimensionUnit::PERCENT));
renderContext->ResetOuterBorder();
pattern->OnModifyDone();
EXPECT_FALSE(outerRadius.has_value());
}
5. No Line Numbers in Comments
Avoid hardcoding line numbers in test comments.
- ❌
OnModifyDone should skip UpdateBorderRadius at line 299
- ✅
OnModifyDone should skip UpdateBorderRadius when borderRadius has percent unit
- Reason: Line numbers change as code evolves, making comments outdated
- Alternative: Describe the logical condition being tested rather than the physical line location
6. No Documentation-Only Tests
Every test case must have actual test logic.
- ❌ Documentation-only test with only comments and
EXPECT_TRUE(true)
- ✅ Test with real assertions and verification logic
- Reason: Tests without executable logic don't verify behavior and waste maintenance resources
- Alternative: Put branch documentation in separate design documents or code comments, not in test cases
- Example of what NOT to do:
// ❌ BAD: Documentation-only test
HWTEST_F(ComponentPatternTestNg, ComponentPattern_BranchDocumentation, TestSize.Level1) {
/**
* @tc.steps: step1. Document branches
* Branch 1: condition is true
* Branch 2: condition is false
*/
EXPECT_TRUE(true); // No actual testing!
}
- Correct approach: Write separate test cases for each branch with real logic
7. No Magic Numbers
Use named constants instead of hardcoded numbers.
- ❌ Hardcoded numbers like
720, 1280, 0, 3.0, -1, 100000
- ✅ Named constants with clear semantic meaning
- Reason: Magic numbers make code hard to understand and maintain. Constants provide context and make changes easier
- Where to define: Place constants in anonymous namespace at the top of the test file
- Example:
// ❌ BAD: Magic numbers
HWTEST_F(ComponentPatternTestNg, ComponentPattern_TestBranch, TestSize.Level1) {
SystemProperties::InitDeviceInfo(720, 1280, 0, 3.0, false);
auto size = GetSubWindowSize(100000, 0);
EXPECT_GT(size.Width(), 0);
}
// ✅ GOOD: Named constants
namespace {
const int32_t TEST_DEVICE_WIDTH = 720;
const int32_t TEST_DEVICE_HEIGHT = 1280;
const int32_t TEST_DEVICE_ORIENTATION = 0;
const double TEST_DEVICE_RESOLUTION = 3.0;
const bool TEST_DEVICE_IS_ROUND = false;
const int32_t TEST_PARENT_CONTAINER_ID = 100000;
const uint32_t DEFAULT_DISPLAY_ID = 0;
}
HWTEST_F(ComponentPatternTestNg, ComponentPattern_TestBranch, TestSize.Level1) {
SystemProperties::InitDeviceInfo(
TEST_DEVICE_WIDTH, TEST_DEVICE_HEIGHT, TEST_DEVICE_ORIENTATION,
TEST_DEVICE_RESOLUTION, TEST_DEVICE_IS_ROUND);
auto size = GetSubWindowSize(TEST_PARENT_CONTAINER_ID, DEFAULT_DISPLAY_ID);
EXPECT_GT(size.Width(), 0);
}
Test Template
/**
* @tc.name: PatternMethod_TestBranchName
* @tc.desc: Test Description covering specific behavior (e.g., "when X happens, Y should occur")
* @tc.type: FUNC
*/
HWTEST_F(ComponentPatternTestNg, ComponentPattern_TestBranchName, TestSize.Level1)
{
// 1. Environment setup
MockPipelineContext::GetCurrent()->SetMinPlatformVersion(static_cast<int32_t>(PlatformVersion::VERSION_ELEVEN));
ScreenSystemManager::GetInstance().dipScale_ = DIP_SCALE;
// 2. Create nodes (using existing helper methods like GetPreviewMenuWrapper)
auto menuWrapperNode = GetPreviewMenuWrapper();
ASSERT_NE(menuWrapperNode, nullptr);
auto menuNode = AceType::DynamicCast<FrameNode>(menuWrapperNode->GetChildAtIndex(0));
ASSERT_NE(menuNode, nullptr);
auto menuPattern = menuNode->GetPattern<MenuPattern>();
ASSERT_NE(menuPattern, nullptr);
auto menuLayoutProperty = menuNode->GetLayoutProperty<MenuLayoutProperty>();
ASSERT_NE(menuLayoutProperty, nullptr);
auto renderContext = menuNode->GetRenderContext();
ASSERT_NE(renderContext, nullptr);
// 3. Set test data to trigger target branch
TestDataType testData;
testData.SetValue(...); // Configure to trigger branch
menuLayoutProperty->UpdateProperty(testData);
// 4. Reset interfering state (CRITICAL!)
renderContext->ResetTargetProperty();
// 5. Call target method directly
menuPattern->TargetMethod();
// 6. Verify results match branch expectation
auto result = renderContext->GetTargetProperty();
EXPECT_TRUE(result.has_value()); // Or false based on branch
}
Common Pitfalls
| Pitfall |
Symptom |
Solution |
| Indirect method call |
Test doesn't trigger target code path |
Call target method directly |
| No state reset |
Test fails with unexpected values |
Reset all potentially interfering properties before calling method |
| Guessed API name |
Compilation error or method not found |
Use Grep to verify exact method name from source |
| Unclean test data |
Tests interfere with each other |
Each test should be independent, reset all state |
| Missing branch coverage |
Code coverage shows gaps |
Write tests for both true and false branches |
| Line numbers in comments |
Comments become outdated when code changes |
Describe logical conditions instead of physical locations |
| Documentation-only tests |
Test contains only comments with EXPECT_TRUE(true) |
Every test must have real assertions and verification logic |
| Magic numbers |
Hardcoded numbers like 720, 1280, 100000 without meaning |
Use named constants with clear semantic meaning in anonymous namespace |
Mandatory Self-Checklist
After generating test code, verify:
Reference Implementation
See working example at: test/unittest/core/pattern/menu/menu_pattern_test_ng.cpp:1482-1574
MenuPatternTest_OnModifyDone_UpdateBorderRadius - Tests when border has no PERCENT unit
MenuPatternTest_OnModifyDone_PercentBorderRadius - Tests when border has PERCENT unit
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: tdd-273description: This skill should be used when writing unit tests, TDD test cases, test coverage analysis, or test-driven development for NG component Pattern methods (e.g., OnModifyDone, OnDirtyLayoutWrapperSwap). Covers test principles, templates, common pitfalls, branch coverage, API verification, state reset, and mandatory self-checklist for ace_engine testing. Use when this capability is needed.4---56# TDD Writing Guidelines for NG Component Pattern Methods78When writing TDD tests for NG component Pattern methods (e.g., `OnModifyDone()`, `OnDirtyLayoutWrapperSwap()`), follow these guidelines to avoid common pitfalls.910## Core Principles1112### 1. Direct Method Invocation1314Call the target method directly, not through indirect triggers.1516- ✅ `menuPattern->OnModifyDone()`17- ❌ `menuPattern->FireBuilder()` (doesn't trigger `OnModifyDone()`)1819### 2. State Reset2021Always reset potentially interfering state before calling target method.2223```cpp24// Reset to clean state before testing25auto renderContext = menuNode->GetRenderContext();26renderContext->ResetOuterBorder(); // Reset entire property group2728// Then call target method29menuPattern->OnModifyDone();30```3132### 3. API Verification3334Use Grep/Read tools to verify method names, never guess.3536```bash37# Search for method definition38grep -rn "ResetOuterBorder\|Reset.*Outer" frameworks/core/components_ng/render/render_context.h3940# Understand macro-generated methods41grep -A 5 "ACE_DEFINE_PROPERTY_GROUP" frameworks/core/components_ng/property/property.h42```4344- `ACE_DEFINE_PROPERTY_GROUP(OuterBorder, OuterBorderProperty)` generates:45 - `GetOrCreateOuterBorder()`, `GetOuterBorder()`, `CloneOuterBorder()`, **`ResetOuterBorder()`**46 - NOT `ResetOuterBorderRadius()` (common mistake)4748### 4. Branch Coverage4950Write paired tests for if/else branches.5152```cpp53// Branch 1: Condition is true54HWTEST_F(Xxx, Test_BranchTrue, TestSize.Level1) {55 borderRadiusVP.SetRadius(Dimension(10.0_vp)); // No PERCENT unit56 renderContext->ResetOuterBorder();57 pattern->OnModifyDone();58 EXPECT_TRUE(outerRadius.has_value());59}6061// Branch 2: Condition is false62HWTEST_F(Xxx, Test_BranchFalse, TestSize.Level1) {63 borderRadiusPercent.SetRadius(Dimension(50.0f, DimensionUnit::PERCENT));64 renderContext->ResetOuterBorder();65 pattern->OnModifyDone();66 EXPECT_FALSE(outerRadius.has_value());67}68```6970### 5. No Line Numbers in Comments7172Avoid hardcoding line numbers in test comments.7374- ❌ `OnModifyDone should skip UpdateBorderRadius at line 299`75- ✅ `OnModifyDone should skip UpdateBorderRadius when borderRadius has percent unit`76- **Reason**: Line numbers change as code evolves, making comments outdated77- **Alternative**: Describe the logical condition being tested rather than the physical line location7879### 6. No Documentation-Only Tests8081Every test case must have actual test logic.8283- ❌ Documentation-only test with only comments and `EXPECT_TRUE(true)`84- ✅ Test with real assertions and verification logic85- **Reason**: Tests without executable logic don't verify behavior and waste maintenance resources86- **Alternative**: Put branch documentation in separate design documents or code comments, not in test cases87- **Example of what NOT to do**:88 ```cpp89 // ❌ BAD: Documentation-only test90 HWTEST_F(ComponentPatternTestNg, ComponentPattern_BranchDocumentation, TestSize.Level1) {91 /**92 * @tc.steps: step1. Document branches93 * Branch 1: condition is true94 * Branch 2: condition is false95 */96 EXPECT_TRUE(true); // No actual testing!97 }98 ```99- **Correct approach**: Write separate test cases for each branch with real logic100101### 7. No Magic Numbers102103Use named constants instead of hardcoded numbers.104105- ❌ Hardcoded numbers like `720`, `1280`, `0`, `3.0`, `-1`, `100000`106- ✅ Named constants with clear semantic meaning107- **Reason**: Magic numbers make code hard to understand and maintain. Constants provide context and make changes easier108- **Where to define**: Place constants in anonymous namespace at the top of the test file109- **Example**:110 ```cpp111 // ❌ BAD: Magic numbers112 HWTEST_F(ComponentPatternTestNg, ComponentPattern_TestBranch, TestSize.Level1) {113 SystemProperties::InitDeviceInfo(720, 1280, 0, 3.0, false);114 auto size = GetSubWindowSize(100000, 0);115 EXPECT_GT(size.Width(), 0);116 }117118 // ✅ GOOD: Named constants119 namespace {120 const int32_t TEST_DEVICE_WIDTH = 720;121 const int32_t TEST_DEVICE_HEIGHT = 1280;122 const int32_t TEST_DEVICE_ORIENTATION = 0;123 const double TEST_DEVICE_RESOLUTION = 3.0;124 const bool TEST_DEVICE_IS_ROUND = false;125 const int32_t TEST_PARENT_CONTAINER_ID = 100000;126 const uint32_t DEFAULT_DISPLAY_ID = 0;127 }128 HWTEST_F(ComponentPatternTestNg, ComponentPattern_TestBranch, TestSize.Level1) {129 SystemProperties::InitDeviceInfo(130 TEST_DEVICE_WIDTH, TEST_DEVICE_HEIGHT, TEST_DEVICE_ORIENTATION,131 TEST_DEVICE_RESOLUTION, TEST_DEVICE_IS_ROUND);132 auto size = GetSubWindowSize(TEST_PARENT_CONTAINER_ID, DEFAULT_DISPLAY_ID);133 EXPECT_GT(size.Width(), 0);134 }135 ```136137## Test Template138139```cpp140/**141 * @tc.name: PatternMethod_TestBranchName142 * @tc.desc: Test Description covering specific behavior (e.g., "when X happens, Y should occur")143 * @tc.type: FUNC144 */145HWTEST_F(ComponentPatternTestNg, ComponentPattern_TestBranchName, TestSize.Level1)146{147 // 1. Environment setup148 MockPipelineContext::GetCurrent()->SetMinPlatformVersion(static_cast<int32_t>(PlatformVersion::VERSION_ELEVEN));149 ScreenSystemManager::GetInstance().dipScale_ = DIP_SCALE;150151 // 2. Create nodes (using existing helper methods like GetPreviewMenuWrapper)152 auto menuWrapperNode = GetPreviewMenuWrapper();153 ASSERT_NE(menuWrapperNode, nullptr);154 auto menuNode = AceType::DynamicCast<FrameNode>(menuWrapperNode->GetChildAtIndex(0));155 ASSERT_NE(menuNode, nullptr);156 auto menuPattern = menuNode->GetPattern<MenuPattern>();157 ASSERT_NE(menuPattern, nullptr);158 auto menuLayoutProperty = menuNode->GetLayoutProperty<MenuLayoutProperty>();159 ASSERT_NE(menuLayoutProperty, nullptr);160 auto renderContext = menuNode->GetRenderContext();161 ASSERT_NE(renderContext, nullptr);162163 // 3. Set test data to trigger target branch164 TestDataType testData;165 testData.SetValue(...); // Configure to trigger branch166 menuLayoutProperty->UpdateProperty(testData);167168 // 4. Reset interfering state (CRITICAL!)169 renderContext->ResetTargetProperty();170171 // 5. Call target method directly172 menuPattern->TargetMethod();173174 // 6. Verify results match branch expectation175 auto result = renderContext->GetTargetProperty();176 EXPECT_TRUE(result.has_value()); // Or false based on branch177}178```179180## Common Pitfalls181182| Pitfall | Symptom | Solution |183|:---|:---|:---|184| Indirect method call | Test doesn't trigger target code path | Call target method directly |185| No state reset | Test fails with unexpected values | Reset all potentially interfering properties before calling method |186| Guessed API name | Compilation error or method not found | Use Grep to verify exact method name from source |187| Unclean test data | Tests interfere with each other | Each test should be independent, reset all state |188| Missing branch coverage | Code coverage shows gaps | Write tests for both true and false branches |189| Line numbers in comments | Comments become outdated when code changes | Describe logical conditions instead of physical locations |190| Documentation-only tests | Test contains only comments with `EXPECT_TRUE(true)` | Every test must have real assertions and verification logic |191| Magic numbers | Hardcoded numbers like `720`, `1280`, `100000` without meaning | Use named constants with clear semantic meaning in anonymous namespace |192193## Mandatory Self-Checklist194195After generating test code, verify:196197- [ ] All called methods exist (verified via Grep/Read)198- [ ] State is reset before calling target method199- [ ] Test data triggers the correct branch (HasPercentUnit returns expected value)200- [ ] Assertions match branch behavior201- [ ] All API signatures match source definitions202- [ ] Test follows existing test file structure and naming conventions203- [ ] Comments describe logical behavior, not line numbers (e.g., use "when X happens" instead of "at line 299")204- [ ] **Every test case has real test logic (not just documentation with `EXPECT_TRUE(true)`)**205- [ ] **No magic numbers - all numeric literals replaced with named constants**206207## Reference Implementation208209See working example at: `test/unittest/core/pattern/menu/menu_pattern_test_ng.cpp:1482-1574`210211- `MenuPatternTest_OnModifyDone_UpdateBorderRadius` - Tests when border has no PERCENT unit212- `MenuPatternTest_OnModifyDone_PercentBorderRadius` - Tests when border has PERCENT unit213214---215> Converted and distributed by [TomeVault](https://tomevault.io/claim/openharmony) — claim your Tome and manage your conversions.216<!-- tomevault:4.0:skill_md:2026-04-11 -->