더 자바 바이트코드 조작
728x90

백기선님의 더 자바 강의를 듣고 정리한 글입니다.

코드 커버리지

코드커버리지란 무엇일까?

내가 작성한 테스트 코드가 내 코드를 얼만큼 커버하고 있냐는 뜻이다. 테스트 코드가 얼만큼 내가 만든 코드를 검사하는지 확인하는 것이다. 커버리지가 높으면 높을 수록 내 테스트 코드가 내가 짠 코드에서 나오는 모든 경우를 커버한다고 생각이 든다.

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

코드 커버리지를 확인하기 위해 JaCoCo 플러그인을 사용해 보도록 하겠다.

일단 Maven 프로젝트를 생성 해주고

자바 버전은 11로 하도록 한다.

프로젝트 생성이 완료되었으면 클래스를 만들어서 몇줄 작성해본다.

Moim.java

public class Moim {

    int maxNumberOfAttendees;

    int numberOfEnrollment;

    public boolean isEnrollmentFull() {
        if(maxNumberOfAttendees == 0){
            return false;
        }
        return numberOfEnrollment >= maxNumberOfAttendees;
    }

}

test 코드 작성을 위해 test 디렉토리에 MoinTest.java로 클래스 생성

MoinTest.java

public class MoimTest {

    @Test
    public void isFull() {
        Moim moim = new Moim();

        moim.maxNumberOfAttendees = 100;

        moim.numberOfEnrollment = 10;

        Assert.assertFalse(moim.isEnrollmentFull());
    }
}

테스트 코드를 실행 해본다.

성공 한 것을 확인 해볼 수 있다.

이렇게 테스트 코드가 얼만큼 내 코드를 얼만큼 확인했는지 보고 싶으면 커버리지 플러그인을 추가 하면 된다.

JaCoCo 플러그인 추가

<plugin>
    <groupId>org.jacoco</groupId> 
    <artifactId>jacoco-maven-plugin</artifactId> 
    <version>0.8.4</version>
    <executions>
        <execution> 
            <goals>
                <goal>prepare-agent</goal> 
            </goals>
        </execution> 
        <execution>
            <id>report</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>report</goal> 
            </goals>
        </execution> 
    </executions>
</plugin>

이미 pom.xml로 가면 pluginManagement로 감싸져 있을 텐데 그 밖에다가 plugins/plugins로 위의 플러그인을 감싸서 놓으면 된다.

pom.xml

<build>
    <pluginManagement>
      <plugins>
         ... 생략 
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.8.4</version>
        <executions>
          <execution>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
          <execution>
            <id>report</id>
            <phase>prepare-package</phase>
            <goals>
              <goal>report</goal>
            </goals>
          </execution>
          <execution>
            <id>jacoco-check</id>
            <goals>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

이제 터미널에서 mvn clean verify 를 한다.
clean 은 target 폴더를 비우고 verify는 verify phase를 실행

...

[INFO] --- jacoco-maven-plugin:0.8.4:report (report) @ classloader-sample ---
[INFO] Loading execution data file /Users/jeong-yeonsang/Desktop/classloader-sample/target/jacoco.exec

...

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

결과물을 보면 알 수 있듯이 JaCoCo 플러그인이 실행된 것을 확인 할 수 있다.

target 디렉토리로 가보면 site라는 폴더가 생성된 것을 볼 수 있는데 그 안에 있는 index.html을 실행시켜보면 현재 코드커버리지가 얼만큼 되 있는지 확인이 가능하고 어느 코드가 실행됬는지 확인 할 수 있다.

/target/site/index.html

이렇듯 JaCoCo는 어떻게 이런 일이 가능할까?

이 글의 제목과 같게 바이트 코드에 접근해서 바이트코드 단위로 프로그래밍을 했기 때문이다.

바이트 코드 조작

바이트 코드 조작은 .class 파일을 수정하는 것과 같다.

전 글에서 말했듯이 자바는 우리가 짠 코드를 바로 실행 시키는 것이 아니라 .class, 즉 바이트코드화를 시켜 JVM에 전달을 한다.

그리하여 최종적으로 바이트코드가 JVM에 도착하여 하나하나 컴파일을 하는 것이다.

그렇다면 이렇게 바이트 코드를 조작하여 얻을 수 있는 것들은 어떤 것일까?

프로그램 분석

  • 코드에서 버그 찾는 툴
  • 코드 복잡도 계산
  • 클래스 파일 생성*
  • 프록시( 어떤 일을 대신 시켜주는 것)
  • 특정 API 호출 접근 제한
  • 스칼라 같은 언어의 컴파일러
  • 그밖에도 자바 소스 코드 건리지 않고 코드 변경이 필요한 여러 경우에 사용할 수 있다.*
  • 프로파일러 (newrelic) ( 메모리 얼마나 쓰는지 , 쓰레드는 몇개인지 성능분석 툴)
  • 최적화
  • 로깅
  • ...
    이러한 일들을 할 수 있다.

일반적으로 우리가 사용하는 툴에서는 바이트 코드를 수정할 수는 없다. 그러므로 라이브러리를 사용해서 바이트코드를 조작해야만 하는데 대표적인 라이브러리 3개가 있다.

● ASM: https://asm.ow2.io/
● Javassist: https://www.javassist.org/
● ByteBuddy: https://bytebuddy.net/#/

ASMJavassist는 사용자 친화적이 아니고 특정 패턴을 이해하고 있어야만 사용이 가능하여 처음 접하는 사람들에게는 많이 어렵다는 단점이 있다.

그에 반에 ByteBuddy는 ASM을 사용한 라이브러리 이지만 위의 2개보다는 사용하기 편리하다.

일단 Mavne 프로젝트를 사용하고 있으므로 pom.xml에 의존성을 부여한다.

pom.xml

<dependency>
      <groupId>net.bytebuddy</groupId>
      <artifactId>byte-buddy</artifactId>
      <version>LATEST</version>
</dependency>

그 후에 더 자바 강의에서 만들었던 예제대로 클래스를 만들어 준다.

Moja.java

 public class Moja {

    public String pullOut() {
        return "";
    }

}

Masulsa.java

 public class Masula {

    public static void main(String[] args) {
        try {
             new ByteBuddy().redefine(Moja.class)
            .method(named("pullOut")).intercept(FixedValue.value("Rabbit!"))
            .make().saveIn(new File("/Users/jeong-yeonsang/Desktop/classloader-sample/target/classes/"));
        } catch (IOException e) {
              e.printStackTrace();
        }

        //System.out.println(new Moja().pullOut());
    }

}

이렇게 Masula에서 출력 부분을 주석처리한 이유는 주석을 해제하고 Masula를 실행하게 되면 Masula.class, Moja.class가 생성되고

메소드 영역에 저장이 된다. 이미 return ""; 이 되 있는 Moja.class가 메소드 영역에 들어가 로딩되고 실행되고 있으므로 바이트코드를 아

무리 수정해도 Sysout에서 보여주는 값은 ""이 되는 것이다.

그러므로 Sysout 부분은 주석 처리 한 상태로 try catch 문을 실행시키고 그 다음에 try catch문을 주석 처리하고 Sysout을 실행시키면 Rabbit이 나오게 된다.

그리고 Moja.class 로 들어가보면 return ""; 이여야 될 부분이 Rabbit! 으로 되어있는 것을 확인 할 수 있다.

만약 이것을 보면서 따라서 하고 있다면 저 순서를 기억해서 해주길 바란다. 자세한 내용은 백기선님의 더 자바 :코드를 조작하는 다양한 방법 에 나와 있으므로 한번 결제해서 보는 것을 추천한다.

출처 : 더 자바: 코드를 조작하는 다양한 방법

 

더 자바, 코드를 조작하는 다양한 방법 질문 & 답변 - 인프런 | 강의

수강생이 남긴 질문과 지식공유자의 답변을 확인할 수 있어요. 질문 & 답변 | 인프런...

www.inflearn.com

 

728x90

'더 자바' 카테고리의 다른 글

더 자바 리플렉션(2)  (0) 2022.04.04
더자바 리플렉션 (1)  (0) 2022.04.01
더 자바 JVM  (0) 2022.03.31