본문 바로가기

4.개발 및 운영 환경

비동기 테스트

주의: 아래 내용은 상업적 용도나 임의 도용은 불가하지만, 개인적 용도로는 마음대로 사용하세요.

대부분의 단위테스트는 동기인 경우가 많다. 그러나 실제 프로그래밍에서는 처리 방식이 비동기 인경우도 많다. 이 비동기 연산인 경우는 단위테스트를 어떻게 할까?
비 동기 연산이라고 하면 실제 결과가 나오는 시간과 처리 요청하는 시간 간에 차이가 있다. 예를 들어 어떤 함수를 호출하면 바로 리턴되지만, 리턴 값은 성공적으로 일을 수행했는지 여부일뿐, 결과 값이 아니다. 결과 값은 일정 시간이 지난 후에 나온다. 바로 이 값에 대한 검증을 해야한다.

그러면 이렇게 생각할 수도 있다.

  • 일정 시간을 강제로 기다렸다가, 결과값을 가져와서 확인하면 된다.
  • 이럴 경우 강제로 일정 시간동안 대기하게 되고, 결과가 빨리 나와도 필요없는 대기시간이 늘어나게 된다.
  • 계속 값을 반복적으로 가져와서 값이 갱신되면 확인한다.
  • 이럴 경우 값이 조금 늦게 나오는 경우 필요없는 연산 작업이 들어가면서 시스템 리소스가 소모된다.

작성: http://ospace.tistory.com/,2010.07.14 (ospace114@empal.com)

간단한 예제

자바를 바탕으로 간단한 예를 보자. 먼저 비동기 객체를 정의해보자

public class FooAsyn {
    public void execute(String name, FooHandle handle) { ... }
}
pubic interface FooHandle {
   public void handler(Integer val);
}

구조는 간단하다. FooAsyn의 execute를 통해서 비동기 연산을 한다. 그리고 FooHandle을 등록해두면 실행이 완료되면 FooHandle의 handler를 호출하여 결과 값을 넘겨주게 된다.
간 단한 테스트 절차를 설명하면 먼저 테스트하기 위한 FooHandle을 작성한다. FooAsyn에 의해서 호출될 경우 결과 값을 저장하기 위한 역활이다. 그리고 결과 값이 반환되었다고 테스트 프로그램에게 알려주는 역활이다. 테스트 프로그램이 작성된 FooHandle에 의해서 값이 도착했다는 것을 알면, 그 값을 가져와서 비교한다.
먼저 FooHandle를 구현해보자.

public class TestFooHandle {
    private Integer val_ = new Integer(0);
    private Object lock = new Object();
    public void handler(int val) {
        val_ = val;
        synchronized(lock) {
            val_.notify();
        }
    }
    public void wait(long timeout) {
        synchronized(lock) {
            val_.wait(timeout);
        }
    }
    public Integer getValue() {
        return val_;
    }
}

이정만 작성해도 감이 오시는 분도 있을 것이다. 실제 테스트 프로그램을 작성해보자.

public class TestProgram
{
    public void testFooAsyn {
        FooAsyn foo = new FooAsyn();
        TestFooHandle fooHandle = new TestFooHandle();
        foo.execute("foo", fooHandle);
        fooHandle.wait(3000); // 3초대기
        assert(1 == fooHandle.getValue());
    }
}

이 것으로서 비동기 테스트가 완료되었다. 위의 코드는 testFooAsyn에 모두 집어 넣을 수도 있다. 좀더 명확한 이해를 돕고자 TestFooHandle을 별도 클래스로 작성했다. 이렇게 구분해야 코드 읽기가 쉬워지기 때문이다. (물론 저의 기준으로.. ^^;)

발전적인 주제

역시 세상은 녹녹하지 않다. 위의 테스트도 완벽하지 않다. 몇가지 이슈가 있다.

  • 테스트 프로그램에서 wait하기 전에 벌써 FooAsyn에서 수행이 완료되어 TestFooHandle의 handler를 호출할 수 있다.
  • 이렇게 되면 wait전에 notify하기 때문에 테스트 프로그램에서는 쓸데 없는 대기 시간만 나올 수 있다.
  • 다른 하나는 wait가 타임아웃되어서 종료되는데, 그때 FooAsyn의 수행이 종료된 경우이다.
  • 이때 결과를 인정한다면 상관없지만, 타임아웃된 상황에서 나온 값이게 오류로 처리한다면, 문제가 있을 것이다.

첫번째 문제는 java에서는 Doug Lea가 만든 Latch라는 라이브러리가 있다. 그리고, 이를 간단히 구현하고자 하면,
TestFooHandle 에 플래그를 하나 두어서 수행결과를 저장해두고, notify한다. 그리고 TestFooHandle의 wait에서 플래그를 체크하여 값이 설정되어 있으면, 바로 리턴하고, 설정되어 있지 않다면 wait하면 된다.

결론

앞의 발전적인 주제에서 2번재 내용은 따로 설명하지 않았다. 이부분은 별도로 쓰레드의 타이머 구현에서 다룰려고 한다. 즉, 타이머 객체가 필요하다는 의미가 된다. 조금더 설명하면, 타이머 객체에서 시간 내에 수행된다면 정상적인 처리를 호출하지만, 타임아웃이 되면 실패 처리를 하도록 하는 것이다.
이런 타이머는 비동기 처리에서 자주 사용되는 형태로 가끔씩 자주 필요했었다. 실제 구현하려는 것은 아니고, 필요충분 조건만 다룰려고 한다.
아무튼, 지금까지 비동기 테스트를 다뤄보았다. 그러나 실제 테스트에서는 더 난해한 테스트들도 많다. 예를 들어, 통신연결이 갑자기 끝이지는 상황에서 반응, UI에서 사용자가 값을 입력하는 경우가 있다. 물론 이런 것에도 테스트는 가능하다. 그러나 쉽지 않다. 물론 100% 모두 가능한 것은 아니라고 본다. 어쩔 수 없이 사람의 손이 거쳐야되는 부분도 있을 것이다.
테스트 길은 험난하고 끝을 알 수 없는 길이다.

참조

[1] Joe Walnes, Unit Testing Asynchronous Code, 2005, http://joe.truemesh.com/blog/000279.html

[2] Class Object, http://download.oracle.com/docs/cd/E17476_01/javase/1.4.2/docs/api/java/lang/Object.html

2010.07.14.Ospace

반응형