Build Quality Patterns and Test Best Practices
Date Created: 2025-10-17
Session: Build Warning & Test Failure Resolution
Status: production-ready best-practices
Overview
This note documents patterns and best practices discovered during the Oct 17, 2025 session where we resolved build warnings and test failures. These patterns prevent common issues and maintain code quality.
Issues Resolved
1. EquipmentStatsModelsTest Failure
Problem: Test expected both consistency insights to be generated, but implementation only generated one.
Root Cause: Using when statement for independent conditions
// ❌ WRONG: Only one branch executes
consistency?.let { c ->
when {
c.consistencyPercentage > 90f -> insights.add("Excellent consistency")
c.consistencyPercentage < 70f -> insights.add("Work on consistency")
c.trend > 0.5f -> insights.add("Improving over time")
c.trend < -0.5f -> insights.add("Declining")
}
}Solution: Use separate if statements for independent conditions
// ✅ CORRECT: Both conditions can be evaluated
consistency?.let { c ->
// Consistency level
if (c.consistencyPercentage > 90f) {
insights.add("Excellent consistency across ends")
} else if (c.consistencyPercentage < 70f) {
insights.add("Work on shot routine consistency")
}
// Trend direction (independent of consistency level)
if (c.trend > 0.5f) {
insights.add("Performance improving over time")
} else if (c.trend < -0.5f) {
insights.add("Performance declining")
}
}Key Learning: When generating lists or multiple outputs, use if statements if conditions are independent. Use when for mutually exclusive conditions only.
2. kotlin-reflect Warnings
Problem: 3 compiler warnings from using kotlin-reflect API without dependency
- LandingPageTest.kt:33 -
.kotlin.isData - TournamentRoutesTest.kt:28 -
.isSealed - TournamentRoutesTest.kt:48 -
.sealedSubclasses
Root Cause: Tests used Kotlin reflection API without kotlin-reflect dependency (adds ~2.5MB to APK)
Solution: Replace metadata checks with behavior tests
Example 1: Data Class Verification
// ❌ WRONG: Requires kotlin-reflect dependency
assertThat(actionCardDataClass.kotlin.isData).isTrue()
// ✅ CORRECT: Test behavior instead of metadata
// Note: Verifying isData requires kotlin-reflect dependency
// Data class behavior is verified by equals/hashCode/toString/copy testsThe data class behavior tests already verify:
equals()andhashCode()consistencytoString()includes property namescopy()creates new instance with updated values
Example 2: Sealed Class Verification
// ❌ WRONG: Requires kotlin-reflect dependency
assertThat(tournamentRoutesClass.isSealed).isTrue()
val sealedSubclasses = tournamentRoutesClass.sealedSubclasses
// ✅ CORRECT: Test behavior with instance checks
val discovery = TournamentRoutes.Discovery
val creation = TournamentRoutes.Creation
val details = TournamentRoutes.Details("test")
val lobby = TournamentRoutes.Lobby("test")
assertThat(discovery).isInstanceOf(TournamentRoutes::class.java)
assertThat(creation).isInstanceOf(TournamentRoutes::class.java)
assertThat(details).isInstanceOf(TournamentRoutes::class.java)
assertThat(lobby).isInstanceOf(TournamentRoutes::class.java)Sealed class behavior is also verified by exhaustive when expression tests.
Key Learning: Avoid kotlin-reflect in tests. Test behavior (equals, hashCode, toString, copy, instanceof) instead of metadata (isData, isSealed, sealedSubclasses).
3. Redundant Type Check Warning
Problem: Compiler warning “Check for instance is always ‘true’”
Root Cause: Type check on compile-time typed variable
// ❌ WRONG: summaries has compile-time type List<ParticipantScoreSummary>
val summaries = viewModel.createParticipantScoreSummaries(round, null)
assertTrue(summaries is List) // Always true - redundant checkSolution: Verify call succeeded without redundant type check
// ✅ CORRECT: Verify behavior, not type
val summaries = viewModel.createParticipantScoreSummaries(round, null)
assertTrue(summaries.isNotEmpty() || summaries.isEmpty()) // Verifies call succeededKey Learning: Don’t check types that are known at compile time. Verify behavior or properties instead.
Best Practices
When to use when vs if
Use when for:
- Mutually exclusive conditions (only ONE should execute)
- Enum exhaustive matching
- Type checking with sealed classes
- Single-value branching
Use if for:
- Independent conditions (multiple can be true)
- Building lists or collections
- Conditions that should be evaluated regardless of previous results
- Complex boolean logic
Test Patterns to Avoid
❌ Don’t:
- Use kotlin-reflect API in tests (adds dependency overhead)
- Check metadata (isData, isSealed) instead of behavior
- Use redundant type checks on typed variables
- Test implementation details that are already verified
✅ Do:
- Test behavior (equals, hashCode, toString, copy)
- Use instance checks for type verification
- Verify actual functionality and contracts
- Keep tests focused on observable behavior
Build Quality Maintenance
Regular Checks:
- Run builds with
-Xlintto catch warnings early - Review compiler warnings during code review
- Run tests with
--warning-mode all - Use static analysis tools (detekt, ktlint)
Prevention:
- Add linter rules to prevent common issues
- Document patterns in code review guidelines
- Share learnings in team documentation
- Automate quality checks in CI/CD
Implementation Files Modified
-
EquipmentStatsModels.kt (lines 380-394)
- Changed consistency insights from
whentoifstatements - Allows independent evaluation of consistency level AND trend
- Changed consistency insights from
-
LandingPageTest.kt (line 33)
- Removed
.kotlin.isDatareflection check - Data class behavior verified by other tests
- Removed
-
TournamentRoutesTest.kt (lines 28, 48-60)
- Removed
.isSealedand.sealedSubclasseschecks - Replaced with direct instance verification
- Removed
-
RoundViewModelHelperMethodsTest.kt (line 550)
- Fixed redundant type check
- Changed to verify call succeeded
Results
- Build Warnings: 3 → 0 (100% reduction)
- Test Failures: 1 → 0 (100% resolution)
- Build Time: <40 seconds
- Test Success Rate: 100%
- Code Quality: Improved (clean, warning-free build)
Related Notes
Tags
kotlin testing build-quality best-practices code-review lessons-learned production-ready
Week 7-8 Update: P0 + P1 Coverage Improvements
Date: 2025-10-26 Agent: Agent 3 (AAA)
Coverage Progression
- Week 6 baseline: 81%
- After Week 7 (P0): 82-83%
- After Week 8 (P1): 84-85%
- Net gain: +3-4% absolute coverage
Tests Added
- EquipmentListViewModel: 24 tests (full lifecycle + state management)
- NameResolver: 19 tests (100% utility method coverage)
- SettingsViewModel: 4 tests (theme + repair operations)
- TournamentScoreCacheDaoTest: 18 tests (95% DAO coverage)
- Total: 65 new tests, all passing
Impact on KMP Migration
- Higher baseline coverage before entity migration (Week 9)
- Better validation infrastructure for database changes
- Reduced regression risk during KMP refactoring
Next Steps (Week 9)
- Support Agent 2’s entity migration validation
- Fix ~40 tests with data model changes
- Maintain 82%+ coverage during migration
- Add tests for new KMP-compatible entities
See: projects/kmp-migration/Week 7-8 Test Coverage.md for details.