본문 바로가기
Rx

RxJava 테스팅과 Flowable-3(비동기 테스트)

by 봄석 2019. 1. 1.

본 내용은 필자가 학습한 내용을 정리하는 내용입니다.

대부분 의 내용이 아래 책의 내용이므로 원서를 구매해서 직접보시는걸 추천드립니다!

RxJava 프로그래밍 리액티브 프로그래밍 기초부터 RxAndroid까지 한 번에

유동환 , 박정준 지음 | 한빛미디어 | 2017년 09월 04일 출간

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788968488658&orderClick=LAV&Kc=


저자님의 블로그

https://brunch.co.kr/@yudong#info




비동기 코드 테스트

RxJava는 스케줄러를 활용해 다양한 상황에서 비동기 코드를 직관적으로 작성할 수 있습니다. 하지만 비동기 코드를 테스트 하는 것은 어려우므로 RxJava는 비동기로 동작 하는 코드를 테스트 할 방법을 제공합니다.


먼저Observable의 interval() 함수를 활용하는 코드를 살펴봅니다. 기본적으로 계산스케줄러에서 실행됩니다.

interval() 함수를 활용하는 테스트 예

@DisplayName("test Observable.interval() wrong")
    @Test
    
    //테스트 코드를 비 활성화 시키는 경우에는 @Disabled을 추가합니다
    //@Disabled
    void testIntervalWrongWay(){
        Observable<Integer> source= Observable.interval(100L,TimeUnit.MILLISECONDS)
                .take(5)
                .map(Long::intValue);
        
        source.doOnNext(Log::d)
        .test().assertResult(0,1,2,3,4);
    }



위 코드를 실행하면 아래처럼 테스트가 실패합니다

java.lang.AssertionError: Value count differs; Expected: 5 [0, 1, 2, 3, 4],

 Actual: 0 [] (latch = 1, values = 0, errors = 0, completions = 0)


실제로 아무것도 실행되지 않았습니다. 또한 doOnNext(Log::d)를 통해서 값을 출력하도록 했찌만 아무것도 출력되지 않았습니다.


그 이유는 Observable.interval() 메소드가 메인스레드가 아닌 계산스케줄러에서 실행되기 때문입니다.


그래서 

RxJava 2.x 에서는 TestObserver 클래스의 awaitDone() 함수를 활용하여 비동기 코드를 테스트할 방법을 제공합니다.


awaitDone() 함수를 활용한 테스트 예

@DisplayName("test Observable.interval()")
    @Test
    void testInterval() { 
        Observable<Integer> source = Observable.interval(100L, TimeUnit.MILLISECONDS)
                .take(5)
                .map(Long::intValue);
        
        source.doOnNext(Log::d)
            .test()
            .awaitDone(1L, TimeUnit.SECONDS)
            .assertResult(0,1,2,3,4);
        CommonUtils.exampleComplete();

    } 


awiatDone() 함수가 편리한 이유는 test() 함수가 실행되는 스레드에서 onComplete() 함수를 호출할 때까지 기다려주기 때문입니다. Observable이 실행되는 스케줄러가 무엇인지 고민하지 않아도 됩니다.


실행결과를 보면 interval() 함수가 어떤 스레드에서 실행되었는지 알 수 있습니다

RxComputationThreadPool-1 | debug = 0
RxComputationThreadPool-1 | debug = 1
RxComputationThreadPool-1 | debug = 2
RxComputationThreadPool-1 | debug = 3
RxComputationThreadPool-1 | debug = 4

-----------------------


실습예제 : HTTP 서버와 통신하는 코드 테스트하기
위 코드를 좀 더 확장하여 서버와 통신하는 코드를 테스트 해보겠습니다.
아래 코드는 GitHub API를 활용한 HTTP통신 예제입니다. IO 스케줄러와 뉴스레드 스케줄러를 활용했을 때도 동일한 방법으로 테스트 할 수 있습니다 .
@DisplayName("test Github v3 API on HTTP")
    @Test
    void testHttp() { 
        final String url = "https://api.github.com/users/yudong80";
        Observable<String> source = Observable.just(url)
                .subscribeOn(Schedulers.io())
                .map(OkHttpHelper::get)
                .doOnNext(Log::d)                
                .map(json -> GsonHelper.parseValue(json,"name"))
                .observeOn(Schedulers.newThread());
        
        String expected = "Dong Hwan Yu";
        source.doOnNext(Log::i)
            .test()
            .awaitDone(3, TimeUnit.SECONDS)
            .assertResult(expected);
        
}


먼저 GitHub API V3를 활용하여 책의 저자 (yudong80)의 GitHub 정보를 가져옵니다 

HTTP 호출은 IO 스케줄러를 활용하므로 subscribeOn() 함수를 호출하여 IO 스케줄러를 지정했습니다. GitHub API를 호출하면 JSON 정보를 리턴하므로 doOnNext() 함수를 호출하여 서버에서 응답한 JSON 정보를 로그로 출력합니다.


Gson을 활용해 여러 정보중 'name' 정보를 가져오겠습니다. map() 함수에 GsonHelper.parseValue() 메소드를 호출합니다. 

public class GsonHelper {
    public static String parseValue(String json, String key) { 
        return new JsonParser().parse(json)
                .getAsJsonObject()
                .get(key)
                .getAsString();
    }
}
 




'name' 정보를 추출한 후에는 최종 결과를 뉴 스레드 스케줄러에서 출력하게 해야합니다. 그러므로 observeOn() 함수를 호출하여 뉴스레드 스케줄러를 지정했습니다 .source 변수의 doOnNext(Log::i) 함수를 호출하면 결과로그를 출력합니다.

RxCachedThreadScheduler-1 | debug = {"login":"yudong80","id":19287357,"node_id":"MDQ6VXNlcjE5Mjg3MzU3","avatar_url":"https://avatars0.githubusercontent.com/u/19287357?v=4","gravatar_id":"","url":"https://api.github.com/users/yudong80","html_url":"https://github.com/yudong80","followers_url":"https://api.github.com/users/yudong80/followers","following_url":"https://api.github.com/users/yudong80/following{/other_user}","gists_url":"https://api.github.com/users/yudong80/gists{/gist_id}","starred_url":"https://api.github.com/users/yudong80/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yudong80/subscriptions","organizations_url":"https://api.github.com/users/yudong80/orgs","repos_url":"https://api.github.com/users/yudong80/repos","events_url":"https://api.github.com/users/yudong80/events{/privacy}","received_events_url":"https://api.github.com/users/yudong80/received_events","type":"User","site_admin":false,"name":"Dong Hwan Yu","company":"LG Electronics","blog":"https://www.facebook.com/koreacio","location":"Seoul","email":null,"hireable":null,"bio":"집필: \"RxJava 프로그래밍\", \"안드로이드를 위한 Gradle\" /\r\n번역: \"Java 9 모듈 프로그래밍\", \"자바로 배우는 핵심 자료구조와 알고리즘\" 외 2권 ","public_repos":18,"public_gists":0,"followers":27,"following":0,"created_at":"2016-05-10T13:35:31Z","updated_at":"2018-11-14T15:33:50Z"}
RxNewThreadScheduler-1 | value = Dong Hwan Yu



Http 호출은 IO 스케줄러에서 실행되었고 JSON 파싱 겨로가는 뉴 스레드 스케줄러에서 출력합니다. UI 프로그래밍을 할 때는 뉴 스레드 스케줄러 대신에 UI 스레드로 변경하면 됩니다.

앞 비동기 프로그래밍을 한눈에 선언적으로 테스팅 할 수 있다는 것이 기존 자바 개발자 에게는 놀라운 부분입니다 .

댓글