λͺ©μ°¨
λ°°κ²½
μ ν¬λ νλ‘μ νΈμ λΉμ¦λμ€ λ‘μ§ κ°λ°μ λͺ°λνλ©΄μ ν
μ€νΈμ λ€μ μννμ§λ§ ν¬κ² λ¬Έμ λ‘ μΈμνμ§λ μμμ΅λλ€. κ·Έλ¬λ€κ° κ°λ° μλ²μ λ°°ν¬ν λΉμ¦λμ€ λ‘μ§ μ€ λ―Έμ² ν
μ€νΈ μ½λλ₯Ό μμ±νμ§ λͺ»ν λΆλΆμμ μμΈκ° λ°μνλ μν©μ λ§μ£Όνμ΅λλ€. κ·Έλμ ν λ΄λΆ νμλ₯Ό κ±°μ³, ν
μ€νΈμ κ΄μ¬μ κ°μ§κ³ ν
μ€νΈ μΌμ΄μ€λ₯Ό λλ¦¬κΈ°λ‘ κ²°μ νμ΅λλ€. νμ§λ§ ν
μ€νΈ μΌμ΄μ€λ₯Ό λλ €λ κΈ°μ‘΄μ μλ λ‘μ§λ€μ λͺ¨λ 컀λ²νμ§ λͺ»ν΄ μ μ¬ν μμΈκ° λ°μνκ±°λ, μλ‘μ΄ λ‘μ§μ λν΄ ν
μ€νΈ 컀λ²λ₯Ό νλμ§ νμ ν μ μλ μν©μ΄μμ΅λλ€.
μ΄μ ν
μ€νΈ μ½λ 컀λ²λ¦¬μ§λ₯Ό μ λμ μΌλ‘ μΈ‘μ νκ³ λ¬Έμνλ₯Ό ν μ μλ ν΄μ λμ
νκΈ°λ‘ κ²°μ νμμ΅λλ€.
JaCoCo μ±ν μ΄μ
JaCoCo μ μ© μ΄μ κΉμ§λ μΈν
리μ μ΄μΒ Run with CoverageΒ κΈ°λ₯μ μ¬μ©νμμ΅λλ€. ν΄λΉ κΈ°λ₯μ μ½κ² μ κ·Όμ΄ κ°λ₯νλ€λ μ₯μ μ΄ μμμ§λ§, λ¬Έμνκ° μ΄λ ΅κ³ μΈ‘μ κΈ°μ€μ μΈλΆννκΈ° νλ€μ΄ JaCoCo λμ
μ κ²°μ νμμ΅λλ€.
JaCoCo λ₯Ό μ±νν μ΄μ λ λ€μκ³Ό κ°μ΅λλ€.
1.
λ¬Έμνκ° μ½λ€.
β’
μ€μ μ ν΅ν΄ μΈ‘μ λ κ²°κ³Όλ₯Ό html, xml λ±μ νμΌ ννλ‘ λ§λ€μ΄λΌ μ μμ΅λλ€.
2.
λ μμΈνλ€.
β’
μΈλΆμ μΈ μ€μ μΌλ‘ μΈ‘μ λ¨μ, κΈ°μ€ λ±μ μΈλΆμ μΌλ‘ μ€μ ν μ μμ΅λλ€.
3.
ν
μ€νΈ 컀λ²λ₯Ό κ°μ ν μ μλ€.
β’
μΈ‘μ λ κ²°κ³Όκ° μ€μ ν λ¨μ λ° κΈ°μ€μ λν κ·μΉμ ν΅κ³Όνμ§ λͺ»νλ©΄ build μ μ€ν¨νκ² λ©λλ€.
κΈ°λ³Έ μ€μ
μμΌλ‘ κΈ°μ λ gradle μ Groovy DSL μ κΈ°λ°μΌλ‘ ν©λλ€. κΈ°λ³Έμ μΈ μ€μ μ λ€μκ³Ό κ°μ΅λλ€.
plugins {
id 'jacoco'
}
jacoco {
toolVersion = '0.8.7'
}
jacocoTestReport {
reports {
html.enabled true
xml.enabled true
csv.enabled false
}
}
jacocoTestCoverageVerification {
violationRules {
rule {
element = 'CLASS'
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.90
}
}
}
}
Groovy
볡μ¬
β’
plugin
β¦
jacoco νλ¬κ·ΈμΈμ μΆκ°ν©λλ€.
β¦
νλ¬κ·ΈμΈμ μ΄νμ κΈ°μ ν task μ λ¬Άμμ
λλ€. λΌμ΄λΈλ¬λ¦¬μ μ±κ²©μ΄ λΉμ·νκ³ , λΌμ΄λΈλ¬λ¦¬κ° μ½λμμ μ¬μ©ν κ°μ²΄μ λ©μλμ λ¬Άμμ΄λΌλ©΄, plugin μ gradle μμ μ¬μ©ν λ©μλμ λ¬Άμμ΄λΌκ³ μκ°νλ©΄ λ©λλ€.
β’
jacoco
β¦
jacoco plugin μ λν μ€μ μ ν μ μμ΅λλ€.
β¦
toolVersion μ΄μΈμλ reportsDirectory(μμ±λ νμΌ λλ ν 리)λ±μ μ€μ ν μ μμΌλ©° κΈ°λ³Έκ°μ $buildDir/reports/jacocoΒ μ
λλ€.
β’
jacocoTestReport, jacocoTestCoverageVerification
β¦
jacoco plugin μ ν¬ν¨λμ΄μλ task μ
λλ€. μ΄νμ μ΄ task λ₯Ό μ€λ²λΌμ΄λ©νμ¬ μ¬μ©ν μμ μ
λλ€.
κΈ°λ³Έ task
μμ μΈκΈνλ―μ΄, jacoco plugin μλΒ jacocoTestReportΒ μΒ jacocoTestCoverageVerificationΒ λ κ°μ§ task κ° μμ΅λλ€.
β’
jacocoTestReport
β¦
λ°μ΄λλ¦¬λ‘ λ 컀λ²λ¦¬μ§ κ²°κ³Όλ₯Ό μ¬λ¬ νμΌ ννλ‘ μ μ₯ν©λλ€. html μ κ²°κ³Ό λΆμμ©, xmlκ³Ό csvλ μΈλΆ λΆμ λꡬμμ μ°λμ μν΄ λ§λλλ€.
β’
jacocoTestCoverageVerification
β¦
μνλ 컀λ²λ¦¬μ§ κΈ°μ€(λ£°)μ μΈμΈ μ μμ΅λλ€. κΈ°μ€μ μΆ©μ‘±νμ§ λͺ»νλ€λ©΄ build κ° μ€ν¨νκ² λ©λλ€.
./gradlew test jacocoTestReport jacocoTestCoverageVerification
Bash
볡μ¬
μμ λͺ
λ Ήμ΄λ₯Ό μ€ννλ©΄ κΈ°λ³Έ directory μΈ /build/reports/jacoco/test/html/index.html μ νμΌμ΄ μμ±λ κ²μ νμΈν μ μμ΅λλ€.
κ·μΉμ ν΅κ³Όνμ§ λͺ»νλ©΄ build μ μ€ν¨ν©λλ€.
λ task λ₯Ό νλ²μ!
task testCoverage(type: Test) {
group 'verification'
description 'Runs the unit tests with coverage'
dependsOn(':test',
':jacocoTestReport',
':jacocoTestCoverageVerification')
tasks['jacocoTestReport'].mustRunAfter(tasks['test'])
tasks['jacocoTestCoverageVerification'].mustRunAfter(tasks['jacocoTestReport'])
}
Groovy
볡μ¬
testCoverage λΌλ task λ₯Ό λ§λ€κ³ ν΄λΉ task μμ μμ λ task λ₯Ό λ¬Άμ μ μμ΅λλ€.
β’
group
β¦
gradle μ task λ μ¬λ¬κ°μ§ κ·Έλ£Ή(Application, Build, Build Setup, Documentation, Verification λ±)μΌλ‘ λλ©λλ€.
β¦
μ΄ κ·Έλ£Ήλ€μ task λ€μ΄ μ κΈ°μ μΌλ‘ κ²°ν©νκ³ μ€νλμ΄ λΉλ λλ μ€νλ§λΆνΈ μ€νμ ν μ μμ΅λλ€. terminal μμ μλμ λͺ
λ Ήμ΄λ₯Ό μ
λ ₯νλ©΄ κ°κ°μ task μ λν μ€λͺ
μ νμΈν μ μμ΅λλ€.
./gradlew tasks
Bash
볡μ¬
β’
mustRunAfter
β¦
mustRunAfter μΌλ‘ νμ€ν¬ μμλ₯Ό μ§μ ν΄μ€ μ μμ΅λλ€.
β¦
mustRunAfter λ dependsOn λ³΄λ€ νμμμ
λλ€.Β μ΄μΈμ λ©μλλ€μ λΉλ μ€ν¬λ¦½νΈ κΈ°μ΄λ₯Ό μ°Έκ³ ν΄μ£ΌμΈμ!
μ΄ μ€μ μΌλ‘Β ./gradlew testCoverageΒ λͺ
λ Ήμ΄λ₯Ό μ€ννλ©΄Β ./gradlew test jacocoTestReport jacocoTestCoverageVerificationΒ λͺ
λ Ήμ΄μ κ°μ μ€νμ ν μ μμ΅λλ€.
μΈλΆ μ€μ
jacocoTestCoverageVerification νμ€ν¬μμ 컀λ²λ¦¬μ§ κΈ°μ€μ μμΈνκ² μ€μ ν μ μμ΅λλ€.
λ€μμ μ ν¬ λͺ¨λ½ νμ΄ νμλ₯Ό ν΅ν΄ μ μν λ£°μ
λλ€.
tasks.named('jacocoTestCoverageVerification') {
violationRules {
rule {
element = 'CLASS'
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.80
}
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.70
}
limit {
counter = 'METHOD'
value = 'COVEREDRATIO'
minimum = 0.60
}
excludes = [
'**.*Formatter*',
'**.*BaseEntity*',
'**.*GithubOAuthClient*',
'**.*MorakBackApplication*',
'**.*Interceptor*',
'**.*Extractor*',
'**.RestSlackClient',
'**.*Config'
]
}
rule {
element = 'METHOD'
limit {
counter = 'LINE'
value = 'TOTALCOUNT'
maximum = 200
}
}
}
}
Groovy
볡μ¬
violationRules μμ±μμ rule μ μ μν μ μμΌλ©°, μ¬λ¬ κ°μ rule μ μ μν μ μμ΅λλ€.
β’
element
β¦
컀λ²λ¦¬μ§λ₯Ό 체ν¬ν κΈ°μ€μ μ€μ ν©λλ€.
β¦
κ°λ₯ν κ°μΌλ‘λ BUNDLE, PACKAGE, CLASS, GROUP, SOURCEFILE, METHOD κ° μμΌλ©° κΈ°λ³Έ κ°μ BUNDLE μ
λλ€.
β’
counter
β¦
컀λ²λ¦¬μ§ μΈ‘μ μ μ΅μ λ¨μμ
λλ€.
β¦
κ°λ₯ν κ°μΌλ‘λ INSTRUCTION, LINE, BRANCH, COMPLEXITY, METHOD, CLASS κ° μμΌλ©° κΈ°λ³Έ κ°μ INSTRUCTION μ
λλ€.
β’
value
β¦
컀λ²λ¦¬μ§λ₯Ό μΈ‘μ ν λ¨μμ
λλ€.
β¦
κ°λ₯ν κ°μΌλ‘λ TOTALCOUNT, MISSEDCOUNT, COVEREDCOUNT, MISSEDRATIO, COVEREDRATIO κ° μμΌλ©° κΈ°λ³Έ κ°μ COVEREDRATIO μ
λλ€.
μμ 3κ°λ₯Ό μ‘°ν©ν΄μ μ¬μ©ν μ μμ΅λλ€.
ex)
1.
PACKAGE, METHOD, COVEREDRATIO β ν¨ν€μ§μ λ©μλ 컀λ²λΉμ¨
2.
METHOD, BRANCH, COVEREDRATIO β λ©μλμ λΆκΈ° 컀λ²λΉμ¨
3.
CLASS, LINE, TOTALCOUNT β ν΄λμ€μ λΌμΈ μ΄ κ°―μ
β’
includes
β¦
κ°κ°μ rule μμ μ§μ λ element λ₯Ό κΈ°μ€μΌλ‘, ν¬ν¨ν element λ₯Ό μ§μ ν μ μμ΅λλ€.
β¦
κΈ°λ³Έ κ°μ λͺ¨λ element μ
λλ€([*]).
β’
excludes
β¦
κ°κ°μ rule μμ μ§μ λ element λ₯Ό κΈ°μ€μΌλ‘, μ μΈν element λ₯Ό μ§μ ν μ μμ΅λλ€.
β¦
κΈ°λ³Έ κ°μ λΉ list μ
λλ€.
β¦
ant μ€νμΌλ‘ κ°μ μ§μ ν μ μμ΅λλ€.
λ©μλ μ μΈνκΈ°
μμ rule μ νμ©νμ¬ ν΄λμ€ λ΄λΆμ ν¬ν¨λ Β equalsΒ μ κ°μ λ©μλλ₯Ό μΈ‘μ μμ μ μΈνλ €κ³ νλλ° λ¬Έμ κ° λ°μνμ΅λλ€. rule μμ exclude ν μ μλ νλͺ©μΒ elementΒ μμ μ μλ CLASSΒ μ
λλ€. κ·Έλμ ν΄λΉ rule μμ exclude λ‘λ ν΄λμ€μ λ©μλλ₯Ό exclude λ‘ μ μΈν μ μμμ΅λλ€. μ¦ element κ° ν΄λμ€μ΄λ©΄ ν΄λμ€λ§ μ μΈν μ μκ³ , element κ° λ©μλμ¬μΌ λ©μλλ₯Ό μ μΈν μ μμ΅λλ€.
μ ν¬ νλ‘μ νΈμλ μλΉμ€ λ‘μ§ μ equals μ hashCode λ₯Ό μ¬μ μν λ‘μ§μ΄ μλλ°, κ΅³μ΄ ν
μ€νΈν νμκ° μμ΄μ ν
μ€νΈ μΌμ΄μ€μ μΆκ°νμ§ μμμ΅λλ€. νμ§λ§ ν΄λΉ λ©μλλ₯Ό ν
μ€νΈνμ§ μμΌλ©΄ μ§μ ν rule μΒ BRANCHΒ μΒ METHODΒ μμ κ±Έλ €, λΉλμ μ€ν¨νκ² λμμ΅λλ€. ν
μ€νΈ 컀λ²λ¦¬μ§λ₯Ό μν΄ ν΄λΉ λ©μλλ₯Ό ν
μ€νΈνμ¬ λΆνμν ν
μ€νΈ μΌμ΄μ€λ₯Ό μΆκ°ν기보λ€λ, ν΄λΉ λ©μλλ₯Ό μΈ‘μ μμ μ μΈνλ λ°©λ²μ μ°Ύμ보μμ΅λλ€. μ ν¬κ° κ²ͺμ λ¬Έμ λ₯Ό λ§μ κ°λ°μλ€μ΄ μꡬνλμ§, JaCoCo 0.8.2 λΆν°λ
1.
'Generated' λΌλ λ¨μ΄κ° μ΄λ¦μ ν¬ν¨Β
2.
RetentionPolicy κ° 'CLASS' λλ 'Runtime'Β
μ μΆ©μ‘±νλ μ΄λ
Έν
μ΄μ
μ΄ λΆμ΄μμΌλ©΄ ν΄λΉ Target μ JaCoCo μΈ‘μ μμ μ μΈν μ μλ€κ³ ν©λλ€.
μ΄μ λ°λΌ μμ κΈ°μ€μ μΆ©μ‘±νλ μ΄λ
Έν
μ΄μ
μ λ§λ€κ³ , ν
μ€νΈκ° λΆνμν λ©μλμ λ§λ μ΄λ
Έν
μ΄μ
μ λ¬μμ£Όμμ΅λλ€.
@Documented
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Generated {
}
Java
볡μ¬
@Override
@Generated
public boolean equals(Object o) {
// ...
}
Java
볡μ¬
λ¨μ μΌλ‘λ μλΉμ€ μ½λμ ν
μ€νΈ μ½λ λ‘μ§μ΄ λ€μ΄κ°λ€λ μ μ΄μμ΅λλ€. νμ§λ§ μ΄λ¬ν λ¨μ 보λ€λ ν
μ€νΈ μΌμ΄μ€μ μλλ₯Ό λμ΄λ κ²μ΄ λ μ ν©νλ€κ³ νλ¨νμ¬ μμ μ΄λ
Έν
μ΄μ
λ°©μμ μ¬μ©νκΈ°λ‘ κ²°μ νμμ΅λλ€.
lombok κ΄λ ¨ μ μΈνκΈ°
μ ν¬ νλ‘μ νΈμμλ μ¬μ μν equals, hashcode λ©μλ λΏλ§ μλλΌ NoArgsConstructor, Getter λ±μ lombok μ΄λ
Έν
μ΄μ
λ μ΄μ©νκ³ μμ΅λλ€. μ΄μ λ°λΌ ν΄λΉ lombok κ΄λ ¨ λ©μλλ€λ ν
μ€νΈ 컀λ²λ¦¬μ§μ μΈ‘μ λμ΄ λΉλμ μ€ν¨νλ κ²½μ°κ° μκ²Όμ΅λλ€. μ΄μ λ°λΌ lombok κ³Ό κ΄λ ¨λ λ¬Έμ λ μΈ‘μ μμ μ μΈνκΈ°λ‘ κ²°μ νμμ΅λλ€.
lombok.addLombokGeneratedAnnotation = true
Plain Text
볡μ¬
νλ‘μ νΈ λ£¨νΈ λλ ν 리 νμμ lombok.config νμΌμ project μ root path μ μμ±νκ³ μμ κ°μ μ€μ μ ν΄μ£Όμμ΅λλ€. μ΄ μ€μ μ λͺ¨λ lombok μΌλ‘ μμ±λ λ©μλμ λν΄ βGeneratedβ μ΄λ
Έν
μ΄μ
μ λΆμΈλ€λ μ€μ μ
λλ€.
μ€μ λΆμ
μμ κ°μ΄ ν
μ€νΈκ° λΆνμν λ©μλμ lombok κ΄λ ¨ λ©μλλ₯Ό μ μΈνλ κ²μ΄ κ°λ₯ν μ΄μ λ lombok μ λ©μλ μμ± μμ κ³Ό, JaCoCo μ μ½λ 컀λ²λ¦¬μ§ μΈ‘μ λ°©μμ μμ΅λλ€.
lombok λ©μλ μμ± μμ
lombok μ λ©μλλ complie μμ μμ±μ΄ λ©λλ€(annotation processing). μ΄μ λ°λΌ 컀μ€ν
ν μ΄λ
Έν
μ΄μ
μΈ @Generated κ° λΆμ λ©μλλ lombok λ©μλλ compile λ μ΄ν μμ±λ class νμΌμ λ©μλμ@Generated κ° λΆμ΄μμ΅λλ€.
JaCoCo μΈ‘μ λ°©μ
JaCoCo λ Java Agent λ‘ μ€νμ΄ λ©λλ€. μ΄μ λ°λΌ λ°μ΄νΈ μ½λλ₯Ό μ§μ κ΄λ¦¬ν μ μμΌλ©°, ν
μ€νΈ μ€ν μ€μ κ°κ°μ μ½λλ₯Ό 보면μ μ΄λ€ μ½λ λΌμΈμ΄ μνλμλμ§ νμΈνλ©΄μ μ»€λ² λΉμ¨μ μΈ‘μ ν©λλ€. μ΄λ¬ν λ°©μ λλΆμ class νμΌμ @Generated κ° λΆμ λ©μλλ μΈ‘μ μμ μ μΈν μ μλ κ²μ
λλ€.
λΆμ κ²°κ³Ό
PR μ SonarQube λ‘ μ½λ μ μ λΆμκ³Ό ν¨κ» JaCoCo λ‘ μΈ‘μ λ κ²°κ³Όμ λν΄μλ λΆμνμ¬ λΆμ κ²°κ³Όλ₯Ό PR λ§κΈλ‘ μλ €μ€ μ μλλ‘ νμμ΅λλ€. μμ μ€μ ν 4κ°μ κ·μΉμ ν΅κ³Όνμ§ λͺ»νλ©΄ build μ μ€ν¨νκ² λκ³ , SonarQube μμλ μ 체μ μΈ ν΅κ³Ό κΈ°μ€μ 80% λ‘ μ‘μ μ 체 μ½λμ λν΄μλ ν
μ€νΈ 컀λ²λ¦¬μ§μ λν΄ μΈ‘μ ν μ μλλ‘ νμμ΅λλ€.