'package private class 테스트'에 해당되는 글 1건

  1. 2016.11.18 package private class에 대한 unit test 방법

설계를 하다보면 종종 상위의 인터페이스나 상위 클래스는 외부에 public으로 노출을 시키되, 하위의 구체 클래스들은 외부에 노출시키지 않아야 하는 경우가 있다. 이는 정보 은닉을 위한 좋은 설계 방법 중 하나이다. 이를 위해서 보통 하위의 구체 클래스들을 package private class(JAVA에서 접근자 키워드가 없이 선언되는 클래스)로 선언한다. 이렇게 하면 같은 패키지 내에서는 해당 클래스에 접근할 수 있어도 다른 패키지에서는 package private 클래스를 접근할 수 없으므로 오직 인터페이스나 상위 클래스를 이용할 수 밖에 없다. 따라서 하위 클래스들의 변경이나 추가와 같은 유연성이 보장된다.

하지만 문제는 package private class에 대한 Unit Test를 작성하는 일은 쉽지 않다는 점이다. 일반적으로 제품 코드와 유닛 테스트 코드는 분리되는 것이 좋다. 제품 코드와 유닛 테스트 코드가 같은 곳, 즉 같은 패키지 내에 존재하면 제품의 출시를 위해서 유닛 테스트 코드를 골라내는 작업이 필요하다. 또 제품 코드를 이해하기 위해서 소스를 뒤적이다 보면 유닛 테스트 코드가 함께 섞여 있어서 불편함을 겪게 된다. 그래서 일반적으로 제품 코드와 유닛 테스트 코드를 분리하여 두는데, 이렇게 되면 제품 코드와 유닛 테스트 코드의 패키지가 분리되면서 package private인 요소들에 대해서는 유닛 테스트가 접근할 수 없게 된다. 이렇게 되면 간접적으로 해당 요소들을 테스트해야 하는데 이는 매우 번거로운 작업이 된다.

이클립스에서는 이 문제를 해결할 수 있는 좋은 솔루션을 제공해 준다. 바로 소스 폴더를 여러개 등록하는 것이다.

일단 해결하고자 하는 문제를 명확하게 보여주기 위해서 다음과 같은 예제를 작성해 보겠다.


Shape 및 ShapeFactory 소스 코드

// Shape.java

public interface Shape {

    public void draw();

}

class Triangle implements Shape{

    public void draw(){ System.out.println("draw triangle"); }

}

class Circle implements Shape{

    public void draw(){ System.out.println("draw circle"); }

}

// ShapeFactory.java

public class ShapeFactory {

    public Shape createTriangle(){ return new Triangle(); }

    public Shape createCircle(){ return new Circle(); }

} 

우선 외부에 노출시키고자 하는 인터페이스인 Shape을 정의한다. 그리고 이 인터페이스는 외부에 노출될 것이므로 public interface로 선언하였다. 그리고 하위 클래스인 Triangle과 Circle 클래스들을 구현한다. 이들 클래스는 외부에 노출시키지 않을 것이다. 따라서 접근자가 없는 클래스, 즉 package private 클래스로 선언이 된다.

그리고 외부에 Triangle 클래스와 Circle 클래스를 노출시키지 않을 수 있으려면 생성자 호출을 대신할 Factory 클래스를 제공해 주어야 한다. ShapeFactory 클래스는 Shape 클래스와 같은 패키지 내에 있으면서 외부의 요청에 따라 Triangle 객체와 Circle 객체를 생성하여 제공해 준다. 이 때 구체적인 타입을 감출 수 있도록 Shape 타입으로 상위 타입 캐스팅을 한 후에 제공한다. 이를 통해 Triangle과 Circle 클래스가 외부로 노출되는 것이 차단된다.


이제 유닛 테스트를 구현해야 할 차례다. ShapeFactory가 각 API에 따라서 제대로 된 Triangle 객체와 Circle 객체를 생성하는지를 테스트 해보고자 한다. 그렇다면 아래와 같은 테스트 클래스를 구현해 볼 수 있다.


ShapeFactoryTest.java

public class ShapeFactoryTest {

    ShapeFactory sut;

    @Before

    public void setUp(){

        sut = new ShapeFactory();

    }

    @Test

    public void testCreateRectangle(){

        Shape shape = sut.createTriangle();

        assertTrue(shape instanceof Triangle); // --- 1

    }

    @Test

    public void testCreateCircle(){

        Shape shape = sut.createCircle();

        assertTrue(shape instanceof Circle);   // --- 2

    }

} 

1과 2에서와 같이 Triangle 객체와 Circle 객체가 제대로 생성되었는지를 확인할 필요가 있다. 그런데 Triangle과 Circle은 모두 package private 클래스들이다. 따라서 이 유닛 테스트 코드와 제품 코드가 분리되어 있더라도 이들에 접근 할 수 있는 방법이 필요하다.


제품코드와 테스트 코드의 소스 폴더 분리하기

Proeject Explorer / New / New JavaProject를 클릭하면 아래와 같은 다이얼로그가 나올 것이다. 


여기서 바로 finish를 누르지 말고 Configure default 버튼을 누른다. 그러면 아래와 같은 다이얼로그가 생성된다.

붉은색 박스처럼 src 대신 src/java/main을 입력하고 확인을 누른다. 그리고 나서 Next 버튼을 누르면 아래와 같이 화면이 바뀌게 된다.


여기서 오른쪽 클릭을 하고 New Source Folder 버튼을 누른다. 그러면 아래와 같은 다이얼로그가 생성된다.


여기에 Folder name에 src/test/main을 입력하고 확인을 누른다.

그리고 나서 Finish를 누르면 프로젝트 생성이 완료된다. 폴더 구성이 아래와 같다면 제대로 생성이 된 것이다.


일반적으로는 src라는 폴더 하나만 있는데 우리가 만든 프로젝트는 src/java/main과 src/test/main 폴더가 더 추가되어 있다. 그리고 이들 모두가 새로운 패키지를 생성하는 것이 가능하다. 이 두 폴더 중에서 src/java/main 폴더에는 제품 코드를 추가하고 src/test/main에는 유닛 테스트 코드를 추가하면 된다.

이렇게 소스 폴더를 둘로 나눠 놓은 이유는 물론 제품 코드와 유닛 테스트 코드를 분리시키기 위해서이다. 그런데 다른 방법으로도 둘을 분리할 수 있음에도 굳이 방법을 사용한 이유는 저 두 폴더에서 생성한 동일한 경로의 패키지는 같은 패키지로 취급되기 때문이다. 같은 패키지로 취급된다는 말은 위에서 package private 클래스에 대한 유닛 테스트와 Shape 및 ShapeFactory에 대한 소스 코드를 각각의 폴더에 분리하여 넣어도 둘의 패키지 경로만 같게 설정하여 주면 테스트가 정상 동작한다는 것을 의미한다.

이를 확인하기 위해서 두 폴더에 각각 com 이라는 경로의 패키지를 만들어 본다. 그러면 아래와 같이 될 것이다.


이제 위의 패키지에는 Shape.java와 ShapeFactory.java를 넣고, 아래 패키지에는 ShapeFactoryTest.java를 넣는다. 그리고 유닛 테스트를 실행해 보면 된다.

같은 테스트 코드라고 해도 다른 패키지 경로에 넣으면 Triangle 과 Circle 클래스가 등장하는 곳에서 에러가 발생한다.


사실 이러한 소스 폴더 분리 방식은 이미 Spring Framework에서도 적용하고 있는 방식이다. 만일 이와 같이 package private class들에 대한 테스트가 필요하다면(일반적으로 모든 프로젝트에서 그렇겠지만) 꼭 위와 같이 같은 패키지 경로들을 만들어 테스트 코드를 넣는 것이 좋다. 그래야만 보다 손쉽게 테스트 코드를 작성할 수 있다. 만일 Spring이 아닌 일반 프로젝트를 생성한다고 해도 유닛 테스트를 작성해야 하는 경우라면 미리 위와 같은 형태로 소스 폴더를 구성해 두는 편이 유닛 테스트를 작성하기에 용이할 것이다.

한가지만 더 이야기 하자면, protected 메소드 역시 같은 방식으로 테스트가 가능하다. protected 메소드는 package private과 같이 같은 패키지 내의 다른 클래스에서도 접근이 가능하다. 따라서 위와 같은 형태로 소스 경로를 분리한 후 테스트를 수행해 볼 수 있다.



Posted by 이세영2
,