일종의 부가 클래스(Decorator)인
TestSetup 클래스를 이용한다.
...
public class TestAFunction extends TestCase{
public void testXXX( ){ ... }
public void testYYY( ){ ... }
public static Test suite( ){
TestSetup setup = new TestSetup(new TestSuite(TestAFunction.class)){
protected void setup() throws Excepton{
// 한번만 수행될 테스트 설정 코드
}
protected void teardown() throws Exception{
// 한번만 수행될 테스트 마무리 코드
}
};
return setup;
}
}
JUnit에서는 TestDecorator라는 클래스를 두어 부가적인 기능(테스트에 관련된 기능)을 수행할 수 있게 했다. TestSetup은 TestDecorator의 한가지 활용 사례로 볼 수도 있다. JUnitPerf의 각종 테스트 기능들도 이러한 Decoration 패턴을 통해 TestCase에 부가된다. 새로운 기능을 추가하고자 할 때 상속하여 기능을 확장하는 방법과 Decoration을 고려할 수 있는데,
상속을 이용하게 되면 모든 객체가 확장된 기능을 갖아야 하는 반면, Decoration을 이용하는 경우는 필요한 객체만 동적으로 해당 기능을 부가할 수 있다. 아래의 클래스 다이어그램은 TestCase와 TestSetup 클래스의 관계 및 연관된 클래스 및 인터페이스를 나타낸 것이다.
테스트 실행시 JUnit은 가장 먼저 아래의 메소드를 찾는다.
public static Test suite() { ... }
메소드가 없는 경우는 리플렉션을 이용하여 자동으로 모든 testXXX() 메소드를 TestSuite에 추가시켜서 한번에 수행하게 된다. 아래와 같이 해도 기본적인 suite() 메소드의 동작과 동일한 효과를 얻을 수 있다.
public class TestGame extends TestCase{
...
public static Test suite(){
return new TestSuite(TestGame.class);
}
}
모든 테스트를 전부 수행하는 것이 아니라, 특정한 것만 골라서 하거나 순서를 설정하고 싶은 경우에는 다음과 같이 할 수 있다.
public static Test suite(){
TestSuite suite = new TestSuite();
suite.addTest(new TestGame("testCreateFighter");
suite.addTest(new TestGame("testSameFighters");
return suite;
}
JUnit의 테스트는 Composite 패턴을 고려한 것으로 볼 수 있다. 즉, 아래와 같이 TestSuite를 다시 다른 TestSuite에 추가시킬 수도 있다.
public static Test suite(){
TestSuite suite = new TestSuite(TestGame.class);
suite.addTest(new TestSuite(TestPerson.class);
return suite;
}
junit.extensions.RepeatedTest 클래스를 이용하면 특정 테스트를 반복 수행할 수 있다.
public static Test suite(){
return new RepeatedTest(new TestSuite(TestGame.class), 100);
}
junit.extensions.ActiveTestSuite를 이용하여 쓰레드를 활용하여 테스트를 병렬 수행할 수 있다.
public static Test suite(){
TestSuite suite = new ActiveTestSuite();
suite.addTest(new TestGame("testCreateFighter"));
suite.addTest(new TestGame("testGameInitialState")):
}
위의 예제의 경우, 두 개의 테스트 혹은 테스트 그룹은 별도의 쓰레드에서 수행된다.
비동기 메소드를 테스트하는 경우 아래와 같은 테스트 메소드는 문제가 생긴다.
public void testSomething(){
someAsynchronousMethod();
assertXXX(...):
}
someAsynchronousMethod 메소드 호출로 시작되는 쓰레드가 작업을 시작하기도 전에 assertXXX 메소드가 불려질 것이기 때문이다
이러한 테스트를 가능하기 위해 Mock Listener를 사용할 수 있다.
public void testAsynchronousSearch() throws InterruptedException{
MockSearchModelListener mockListener = new MockSearchModelListener();
SearchModel sm = new PersonSearchModel();
// 1. 검색 수행
sm.search("younghoe", mockListener);
// 2. 검색 종료까지 대기
synchronized (mockListener) {
mockListener.wait(2000);
}
// 3. 결과를 얻음
SearchModelEvent evt = mockListener.getSearchModelEvent();
// 3.1 시간 초과시 Fail
assertNotNull("Search Time out", evt);
// 3.2 시간내 결과를 얻으면 결과 확인
...
}
이 경우 Timeout 시간을 적절하게 선택해야 의미있는 결과를 얻을 수 있다.
실제 Listener가 개발되어 있는 상태가 아니라면, 테스트를 위해 Mock Listener가 있어야 하는데 아래 예제 코드가 있다.
class MockSearchModelListener implements SearchModelListener{
private SearchModelEvent evt;
public void searchFinished(SearchModelEvent evt){
this.evt = evt;
synchronized(this){ notifyAll(); }
}
public SearchModelEvent getSearchModelEvent(){
return this.evt;
}
}