从API设置onSubscribe线程的最佳做法

时间:2019-03-05 20:06:45

标签: rx-java2

假设我有一个公开两个方法的API,每个方法都返回一个可观察的

import org.assertj.core.util.VisibleForTesting;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import io.reactivex.Observable;
import io.reactivex.Scheduler;

class SomeApiClass {

    private static final String[] doOnSubscribeThread = new String[1];

    static Observable<Integer> immediatelyDoWork() {
        return Observable.just(1, 2)
                .doOnSubscribe(ignore -> doOnSubscribeThread[0] = Thread.currentThread().getName())
                .flatMap(ignore -> doWork());
    }

    static Observable<Integer> periodicallyDoWork() {
        // interval is using default computation scheduler
        return Observable.interval(1, TimeUnit.SECONDS)
                .doOnSubscribe(ignore -> doOnSubscribeThread[0] = Thread.currentThread().getName())
                .flatMap(ignore -> doWork());
    }

    @VisibleForTesting
    static String getSubscribedOnThread() {
        return doOnSubscribeThread[0];
    }

    private static Observable<Integer> doWork() {
        return Observable.create(emitter -> {
            Random random = new Random();
            emitter.onNext(random.nextInt());
            emitter.onComplete();
        });
    }

大多数API只会让调用应用程序设置subscribeOn线程(假设这些测试是我的应用程序):

import org.junit.Test;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.TestObserver;
import io.reactivex.schedulers.Schedulers;

import static com.google.common.truth.Truth.assertThat;

public class ExampleTest {

    @Test
    public void canSetSubscribeOnThread() {
        Observable<Integer> coloObservable = SomeApiClass.immediatelyDoWork()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread());

        TestObserver<Integer> testObserver = coloObservable.test();
        testObserver.awaitCount(2); // wait for a few emissions

        assertThat(SomeApiClass.getSubscribedOnThread()).contains("RxNewThreadScheduler");
    }

    @Test
    public void canSetSubscribeOnThreadIfApiUsesInterval() {
        Observable<Integer> coloObservable = SomeApiClass.periodicallyDoWork()
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread());

        TestObserver<Integer> testObserver = coloObservable.test();
        testObserver.awaitCount(2); // wait for a few emissions

        assertThat(SomeApiClass.getSubscribedOnThread()).contains("RxNewThreadScheduler");
    }
}
immediate示例中的

IIUC,所有订阅副作用(包括just())都将在新线程上发生。 Karnok explains well here

但是在periodic示例中,interval将使用默认(计算)调度程序。在这种情况下,大多数API会做什么?他们是否让调用者为所有订阅副作用 interval本身设置subscriptionOn线程?在上面的periodic测试中,我们仍然可以为interval以外的所有内容设置subscribeOn线程。还是他们也添加了一个参数来设置subscribeOn:

/**
 * Works like {@link #periodicallyDoWork()} but allows caller to set subscribeOnSchedueler
 */
static Observable<Integer> periodicallyDoWork(Scheduler subscribeOnScheduler) {
    return Observable.interval(1, TimeUnit.SECONDS, subscribeOnScheduler)
            .doOnSubscribe(ignore -> doOnSubscribeThread[0] = Thread.currentThread().getName())
            .flatMap(ignore -> doWork());
}

然后允许调用者忽略subscribeOn()方法:

@Test
public void canSetSubscribeOnThreadIfApiUsesInterval() {
    Observable<Integer> coloObservable = SomeApiClass.periodicallyDoWork(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread());

    TestObserver<Integer> testObserver = coloObservable.test();
    testObserver.awaitCount(2); // wait for a few emissions

    assertThat(SomeApiClass.getSubscribedOnThread()).contains("RxNewThreadScheduler");
}

这太过分了吗?只要调用方还调用subscribeOn(),仅让interval使用默认的计算调度程序就不会有危险吗?

1 个答案:

答案 0 :(得分:1)

我认为,创建观察者链的API 必须提供了注入调度程序的方法。没有这种功能,单元测试将几乎无法管理。

我有很多为实时系统编写测试的经验。仅仅能够向被测单元提供TestScheduler或两个,就可以进行合理的测试与不打扰。考虑一个debounce()方法周期为1秒的子系统。在无法使用TestScheduler和使用advanceTimeBy()来控制时钟的情况下编写几十种情况的单元测试是不可行的。这意味着单元测试可以在10毫秒内完成,如果使用常规的调度程序,则需要几分钟。