자바(SE)

junit을 이용한 multi thread 단위테스트

  • -

junit을 이용해서 multi thread를 테스트하면 아무래도 main thread가 빨리 종료해버리기 때문에 원하는 테스트 결과를 얻기 어렵다. 

다음은 @Async를 이용해서 비동기 호출을 구현하고있는 빈의 예이다.

@Component
@Slf4j
public class HeavyWorkBean {
    
    @Async
    public void heavyWork(int i) throws InterruptedException {
        Thread.sleep(1000);
        log.trace("아주 무거운 작업을 {} 번째 진행중.", i);
    }
}

위 코드를 아무 생각 없이 테스트 하면 아래와 같다.

public class AsyncTest {
    
    @Autowired
    HeavyWorkBean hBean;

    @Test
    public void asyncTest() throws Exception {
        
        for (int i = 0; i < 10; i++) {
            hBean.heavyWork(i);
        }
       
       System.out.println("종료");
    }
}

테스트를 실행해보면 콘솔에 바로 '종료'가 출력되고 빈에서 출력한 로그는 출력할 기회를 얻지 못한다.

이때 가장 간단한 대처는 main 스레드를 sleep 시키는 것이다.

    @Test
    public void asyncTest() throws Exception {
        
        for (int i = 0; i < 10; i++) {
            hBean.heavyWork(i);
        }
       Thread.sleep(2*1000);
       System.out.println("종료");
    }

이제 출력 결과를 보면 스레드가 모두 동작한 후 종료됨을 확인할 수 있다.

[05:11:07] [TRACE] [c.e.d.b.HeavyWorkBean.heavyWork-17] 아주 무거운 작업을 4 번째 진행중. 
[05:11:07] [TRACE] [c.e.d.b.HeavyWorkBean.heavyWork-17] 아주 무거운 작업을 1 번째 진행중. 
[05:11:07] [TRACE] [c.e.d.b.HeavyWorkBean.heavyWork-17] 아주 무거운 작업을 3 번째 진행중. 
[05:11:07] [TRACE] [c.e.d.b.HeavyWorkBean.heavyWork-17] 아주 무거운 작업을 5 번째 진행중. 
[05:11:07] [TRACE] [c.e.d.b.HeavyWorkBean.heavyWork-17] 아주 무거운 작업을 0 번째 진행중. 
[05:11:07] [TRACE] [c.e.d.b.HeavyWorkBean.heavyWork-17] 아주 무거운 작업을 7 번째 진행중. 
[05:11:07] [TRACE] [c.e.d.b.HeavyWorkBean.heavyWork-17] 아주 무거운 작업을 2 번째 진행중. 
[05:11:07] [TRACE] [c.e.d.b.HeavyWorkBean.heavyWork-17] 아주 무거운 작업을 6 번째 진행중. 
종료

하지만 heavy thread가 언제 끝날지 가늠할 수 없는 상황에서 무작정 sleep을 거는것도 무리가 있다. 이때 CountdownLatch를 사용해보자. Latch는 걸쇠라는 뜻이다.

먼저 HeavyWorkBean은 아래와 같이 바뀔 수 있다.

@Component
@Slf4j
public class HeavyWorkBean {
    CountDownLatch cl;
    
    public void setCountdownLatch(CountDownLatch cl) {
        this.cl = cl;
    }
    
    @Async
    public void heavyWork(int i) throws InterruptedException {
        Thread.sleep(1000);
        log.trace("아주 무거운 작업을 {} 번째 진행중.", i);
        cl.countDown();
    }
}

즉 외부에서 CountDownLatch를 주입 받고 무거운 작업을 수행할 때마다 카운트다운을 진행한다.

@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncTest {
    
    @Autowired
    HeavyWorkBean hBean;

    @Test
    public void asyncTest() throws Exception {
        CountDownLatch cl = new CountDownLatch(10);	// 10번 count down 설정
        hBean.setCountdownLatch(cl);
        for (int i = 0; i < 10; i++) {
            hBean.heavyWork(i);
        }
        boolean wellDone =  cl.await(20, TimeUnit.SECONDS);	// 10회 동작 또는 20s까지 대기
        System.out.println(String.format("메인스레드 종료 (timeout=%b)", !wellDone));
        assertEquals(true, wellDone);
    }
}

테스트 코드에서는 10번의 동작을 감시할 CountDownLatch를 생성해서 HeavyBean에게 넘겨준다.

이후 주어진 회수만큼 동작이 완료되거나 지정한 시간(20초)가 지나면 main 메서드가 동작한다.

 

좋아보이기는 하지만 테스트를 위해 HeavyBean을 수정해야 한다는 점은 맘에 들지 않는다.

 

이 외에도 GroboUtils 라이브러리를 이용하는 방식이 있는데 역시 특정 클래스를 상속받아야 하는 문제점이 있어보인다.

참조: https://devyongsik.tistory.com/263

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.