코드 커버리지는 어떻게 측정할까?
1. 코드 커버리지(Code Coverage)란?
⇒ 소프트웨어의 테스트를 논할 때 얼마나 테스트가 충분한가를 나타내는 지표중 하나다. 말 그대로 코드가 얼마나 커버되었는가이다. 소프트웨어 테스트를 진행했을 때 코드 자체가 얼마나 실행되었냐는 것이다.
코드의 구조를 이루는 것은 크게 구문(Statement), 조건(Condition), 결정(Decision)이다. 이러한 구조를 얼마나 커버했느냐에 따라 코드 커버리지의 측정기준은 나뉘게 된다.
•
구문(Statement) 커버리지
⇒ 일반적으로 많이 사용되는 커버리지이며, 실행 코드라인이 한 번 이상 실행 되면 충족된다.
•
조건(Condition)커버리지
⇒ 각 내부 조건이 참 혹은 거짓을 가지면 충족된다.
•
결정(Decision)커버리지
⇒ 각 분기의 내부 조건자체가 아닌 이러한 조건으로 인해 전체 결과가 참 혹은 거짓이면 충족된다.
•
MC/DC 커버리지
⇒ 조건과 결정을 복합적으로 고려하는 커버리지
2. 코드 커버리지는 어떻게 측정할까?
코드 커버리지 툴은 여러개가 있지만 여기서는 JaCoCo를 사용한다.
1. 의존성 추가 - pom.xml
Code
2. 메이븐 빌드
Code
3. (Optional) 커버리지 만족 못할 시 빌드 실패하도록 설정
Code
실전 예제
1. Moim.java
Code
2. MoimTest.java
Code
3. mvn clean verify 실행
4. target/ site/ index.html 확인
•
Line Color 별 가이드
◦
노란색(Yellow): 분기문에서 일부분만 실행되었을 경우 노란색 다이아몬드 (지나가긴 했지만 완벽히 지나가지 않았다.)
◦
빨간색(Red): 테스트가 이뤄지지 않은 경우 (아예 지나가지 않은 부분)
◦
녹색(Green): 정상적으로 테스트가 된 경우
참고: 코드 커버리지가 어떻게 검증을 하는것인가?
모자에서 토끼를 꺼내는 마술
⇒ 아무것도 없는 Moja에서 "Rabbit"을 꺼내는 마술
이번 시간은 코드위주로 알아본다.
Moja.java
public class Moja{
public String pullOut(){
return "";
}
}
Java
복사
Masulsa.java
public class Masulsa{
public static void main(String[] args){
System.out.println(new Moja().pullOut());
}
}
Java
복사
•
해당 코드를 실행하면 공백만 출력된다. 어떻게 Rabbit! 이 출력될 것인가?
Hint
바이트 조작 라이브러리
•
ASM: https://asm.ow2.io/
•
Javassist: https://www.javassist.org/
•
ByteBuddy: https://bytebuddy.net/#/
ByteBuddy를 사용해보기
1. 의존성 추가
Code
2. ByteBuddy 를 이용해 바이트 조작
Code
javaagent 실습
1. Javaagent JAR파일 만들기
•
붙히는 방식은 시작시 붙히는 premain과 런타임 도중 동적으로 붙히는 agentmain이 있습니다.
•
Instrumentation을 사용합니다.
2. 코드 작성
•
javaagent용 프로젝트 생성(MasulsaAgent)
•
의존성 추가
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.13</version>
</dependency>
XML
복사
•
MasulsaAgent.java
package me.hansol;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
import static net.bytebuddy.implementation.FixedValue.value;
import static net.bytebuddy.matcher.ElementMatchers.named;
public class MasulsaAgent {
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform((builder, typeDescription, classLoader, javaModule) ->
builder
.method(named("pullOut"))
.intercept(value("Rabbit!")))
.installOn(inst);
}
}
Java
복사
•
MANIFEST 작성
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<index>true</index>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<mode>development</mode>
<url>${project.url}</url>
<key>value</key>
<Premain-Class>me.hansol.MasulsaAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
XML
복사
◦
Premmain 모드에 필요한 속성들을 등록해줍니다.
<Premain-Class>me.hansol.MasulsaAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
•
빌드 and 경로 복사
mvn clean package
Shell
복사
•
실행할 기존 프로젝트에 javaagent 추가
바이트코드 조작 정리
1. 프로그램 분석
•
코드에서 버그 찾는 툴
•
코드 복잡도 계산
2. 클래스 파일 생성
•
프록시
•
특정 API 호출 접근 제한
•
스칼라 같은 언어의 컴파일러
3. ETC
•
프로파일러(newrelic)
•
최적화
•
로깅
•
...
참고: 스프링이 컴포넌트 스캔을 하는 방법(asm)
•
컴포넌트 스캔으로 빈으로 등록할 후보 클래스 정보를 찾는데 사용
•
ClassPathScanningCandidateComponentProvider → SimpleMetadataReader
•
ClassReader와 Visitor 사용해서 클래스에 있는 메타 정보를 읽어온다.