Search
Duplicate

바이트코드 조작

코드 커버리지는 어떻게 측정할까?

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
바이트 조작 라이브러리

ByteBuddy를 사용해보기

1. 의존성 추가

Code

2. ByteBuddy 를 이용해 바이트 조작

Code

javaagent 실습

참고: 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 사용해서 클래스에 있는 메타 정보를 읽어온다.