Code Quality & Coverage Standards
This template enforces strict code quality standards including 85% minimum test coverage to ensure maintainable, reliable code.
π Coverage Requirements
Minimum Coverage Standards
- Overall Project Coverage: 85% instruction and branch coverage
- Individual Class Coverage: 85% instruction coverage
- Method Coverage: 76.5% instruction coverage (90% of overall requirement)
Coverage Verification
# Run tests with coverage verification
./gradlew test jacocoTestCoverageVerification
# Generate coverage reports
./gradlew jacocoTestReport
# Aggregate coverage across all modules
./gradlew jacocoRootReport
Coverage Reports
- HTML Report:
build/reports/jacoco/test/html/index.html
- XML Report:
build/reports/jacoco/test/jacocoTestReport.xml
(for CI tools) - CSV Report:
build/reports/jacoco/test/jacocoTestReport.csv
(for badges) - Aggregate Report:
build/reports/jacoco/aggregate/index.html
π― Whatβs Excluded from Coverage
Automatically Excluded Classes
The template intelligently excludes boilerplate code that doesnβt require testing:
// Configuration and framework classes
'**/*Config*', '**/*Configuration*', '*Application*'
// Data classes and DTOs
'*.dto.*', '*.entity.*', '*.model.*'
// Exception classes (definition only)
'*.exception.*', '**/*Exception*'
// Kotlin-generated code
'**/*\$Companion*', '**/*\$WhenMappings*'
// Spring auto-configuration
'**/*AutoConfiguration*', '**/autoconfigure/**'
Smart Exclusions
- Properties classes: Configuration binding classes
- Builder patterns: Often boilerplate code
- Generated code: Serializers, creators, etc.
- Framework classes: Spring context and configuration
π§ͺ Coverage-Driven Testing Strategy
Test Organization for Coverage
src/test/kotlin/
βββ unit/small/ # Fast tests (< 100ms) - High coverage impact
βββ unit/medium/ # Tests with mocks (< 1s) - Medium coverage
βββ integration/ # Full context tests - Lower coverage priority
βββ performance/ # JMH benchmarks - Excluded from coverage
Writing Tests for 85% Coverage
Example Service with Full Coverage:
@Service
@Transactional
class ExampleService {
fun getWelcomeMessage(name: String = "World"): String {
return "Hello, $name! This is your Kotlin Multimodule Template."
}
suspend fun getAsyncWelcomeMessage(name: String = "World"): String {
kotlinx.coroutines.delay(100)
return "Hello async, $name! This is your Kotlin Multimodule Template."
}
}
Comprehensive Test Coverage:
class ExampleServiceUnitTest {
@Test
fun getWelcomeMessage_WithCustomName_ReturnsPersonalizedMessage() {
// Covers: main path with parameter
}
@Test
fun getWelcomeMessage_WithDefaultName_ReturnsDefaultMessage() {
// Covers: default parameter branch
}
@Test
fun getWelcomeMessage_WithEmptyString_ReturnsMessageWithEmptyName() {
// Covers: edge case handling
}
@Test
fun getAsyncWelcomeMessage_WithCustomName_ReturnsAsyncMessage() {
// Covers: async method main path
}
@Test
fun getAsyncWelcomeMessage_ExecutionTime_CompletesWithinReasonableTime() {
// Covers: async delay behavior
}
}
π¦ Coverage Quality Gates
Build Integration
# GitHub Actions example
- name: Run Tests with Coverage
run: ./gradlew test jacocoTestCoverageVerification
- name: Upload Coverage Reports
uses: codecov/codecov-action@v3
with:
file: build/reports/jacoco/test/jacocoTestReport.xml
Local Development
# Quick coverage check during development
./gradlew test jacocoTestReport
# View coverage in browser
open build/reports/jacoco/test/html/index.html
# Verify coverage meets standards
./gradlew jacocoTestCoverageVerification
Coverage Failure Handling
When coverage falls below 85%:
- Identify uncovered code:
# Generate detailed report
./gradlew jacocoTestReport --info
- Add targeted tests:
- Focus on uncovered branches
- Test edge cases and error paths
- Ensure all public methods are tested
- Review exclusions:
- Verify excluded classes are actually boilerplate
- Consider if complex logic needs coverage
π Coverage Best Practices
1. Write Testable Code
// β
Good: Testable with clear dependencies
@Service
class UserService(private val repository: UserRepository) {
fun createUser(userData: UserData): User {
return repository.save(User(userData))
}
}
// β Avoid: Hard to test with static dependencies
@Service
class UserService {
fun createUser(userData: UserData): User {
return StaticRepository.save(User(userData))
}
}
2. Test Both Success and Failure Paths
@Test
fun createUser_WithValidData_ReturnsUser() {
// Test happy path
}
@Test
fun createUser_WithInvalidData_ThrowsValidationException() {
// Test error path - important for coverage!
}
3. Cover Edge Cases
@Test
fun processInput_WithEmptyString_HandlesGracefully() {
// Edge case coverage
}
@Test
fun processInput_WithNullValue_ThrowsAppropriateException() {
// Null handling coverage
}
4. Mock External Dependencies
@Test
fun serviceMethod_WithMockedDependency_ProcessesCorrectly() {
// Given
every { mockRepository.findById(any()) } returns testUser
// When & Then - focuses on service logic only
}
π§ Configuration Details
Gradle Configuration
The template uses these coverage settings in gradle.properties
:
code_coverage_minimum=0.85
JaCoCo Rules
violationRules {
rule {
limit {
counter = 'INSTRUCTION'
value = 'COVEREDRATIO'
minimum = 0.85
}
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.85
}
}
}
π― Coverage Metrics Explained
Instruction Coverage (85% required)
- What: Percentage of bytecode instructions executed
- Why: Most accurate measure of code execution
- Focus: Ensure all code paths are tested
Branch Coverage (85% required)
- What: Percentage of decision branches taken
- Why: Ensures conditional logic is tested
- Focus: Test both true/false paths of conditions
Method Coverage (76.5% required)
- What: Percentage of methods called during tests
- Why: Ensures public API is tested
- Focus: All public methods should have tests
π¨ Troubleshooting Coverage Issues
Common Problems
Issue: Coverage below 85% but all logic seems tested Solution: Check for uncovered exception handling or default branches
Issue: Kotlin companion objects affecting coverage Solution: Already excluded via '**/*\$Companion*'
pattern
Issue: Spring configuration classes lowering coverage Solution: Already excluded via '**/*Config*'
pattern
Debugging Low Coverage
# Generate detailed HTML report
./gradlew jacocoTestReport
# Look for red/yellow highlighted code in:
# build/reports/jacoco/test/html/index.html
This comprehensive coverage strategy ensures your template projects maintain high quality standards while providing clear guidelines for developers to achieve and maintain 85% test coverage.