자바 애플리케이션을 구현하고 테스트 케이스를 짜면서 IntelliJ의 'Coverage' 기능을 사용했다. IntelliJ의 'Coverage' 기능은 테스트 케이스가 애플리케이션 코드의 모든 메소드와 클래스, 코드 라인을 수행했는지 알려준다. (링크 : IntelliJ Coverage 기능)
테스트 케이스를 작성하고 코드 커버리지를 확인하는데 이상하게 클래스 항목이 100%를 찍지 못하고 있었다.
다음 자바 소스코드를 보자.
public class Coverage {
private String name;
private String address;
public static class Builder {
private String name;
private String address;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAddress(String address) {
this.address = address;
return this;
}
public Coverage build() {
return new Coverage(this.name, this.address);
}
}
private Coverage(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return this.name;
}
public String getAddress() {
return address;
}
}
일반적인 빌더 패턴을 사용한 클래스 생성 코드다. 이 소스코드를 테스트하기 위해서 다음과 같은 테스트 케이스를 작성하였다.
import org.junit.Test;
import static org.junit.Assert.*;
public class CoverageTest {
@Test
public void testTest() {
Coverage test = new Coverage.Builder().setAddress("a").setName("b").build();
assertEquals("a", test.getAddress());
assertEquals("b", test.getName());
}
}
이 테스트 케이스를 "Run 'CoverageTest' with Coverage" 로 실행시켜보면 코드 커버리지 정보를 얻을 수 있다.
실행 결과는 "66% classes, 100% lines covered in 'all classes in scope'".
분명 Coverage.java 파일에 있는 모든 메소드와 모든 라인이 커버되었다. 하지만 뭔가 하나의 클래스가 테스트되지 않고 있었다.
생성된 클래스를 보면
Coverage.class와 Coverage$Builder.class 그리고 Coverage$1.class 라는 이상한 클래스가 하나 생성되었다. 저 클래스를 테스트 할 수 없어서 클래스 커버리지 100%를 달성할 수 없었던 것이다.
커버리지 100%를 달성하려면 저 클래스가 생성되면 안된다. 해법은 Builder 클래스의 build() 메소드에서 호출하는 Coverage() 생성자를 private에서 package private으로 변경하면 된다. 즉, 'private' 접근 제어자를 제거하면 된다.
Java의 Synthetic Method, Class, Field
자바 소스코드에서는 보이지 않는 클래스, 메소드, 멤버 변수들이 컴파일러에 의해서 컴파일 타임에 생성될 수 있다. 자바 언어의 스펙을 구현하기 위해서 컴파일러가 생성하는 이런 종류의 'construct'를 'synthetic'이라고 부른다. 컴파일 타임에 생성한 이 construct는 당연히 소스코드에서 확이할 수 없다.
위 예제에서 생성된 'Coverage$1'이라는 클래스도 컴파일러가 생성해 놓은 클래스로 코드에서는 알 수가 없는 클래스다.
자바 언어의 스펙상 "Enclosing" 클래스는 "Nested" 클래스의 private 멤버 변수에 접근 할 수 있다. (그 반대도 된다) 이 때, private 멤버 변수에 대한 'getter' 메소드가 없으면 자바 컴파일러가 'synthetic' 메소드를 만들어 준다. JVM 에서는 접근 제어를 엄격하게 해야하기 때문이다.
위 예제의 경우 Coverage.Builder 클래스가 Coverage 클래스의 Private 생성자(Constructor)를 호출했다. 자바 컴파일러는 이를 지원하기 위해서 Coverage$1 이라는 클래스를 만들어 준 것이다. (Default 생성자의 경우 컴파일러가 묵시적으로 생성하지만 Reflection의 isSynthetic() 메소드에서 true로 리턴되지 않는다. 마찬가지로 Enum 클래스의 values(), valueOf() 메소드 역시 isSynthetic() 메소드가 true를 리턴하지 않는다)
테스트 코드를 작성할 때, 클래스 커버리지가 100%로 되지 않는 케이스가 이러한 케이스가 아닌지 확인하자.
댓글