【问题标题】:How to unit test complex logic in RxJava operators?如何在 RxJava 运算符中对复杂逻辑进行单元测试?
【发布时间】:2017-08-02 16:38:14
【问题描述】:

我正在努力解决如何在具有许多运算符的 RxJava 流中测试复杂逻辑。我发现,如果我有复杂的逻辑并且将许多具有复杂逻辑的运算符串在一起,那么单元测试流将成为一场噩梦,因为要涵盖的案例组合太多。

这是我所描述的一个例子:

        Observable<ObjectA> observable = myRepository
            .fetchA()
            .flatMap(new Func1<ObjectA, Observable<ObjectA>>() {
                @Override
                public Observable<ObjectA> call(final ObjectA object) {
                    if (object.isAvailable()) {
                        //do something
                        return myRepository
                                .fetchAnotherThing();
                    } else {
                        //do something else
                        return myRepositor
                                .fetchADifferentThing();
                    }
                }
            })
            .flatMap(new Func1<ObjectA, Observable<ObjectA>>() {
                @Override
                public Observable<ObjectA> call(final ObjectA object) {
                    if (object.isFull()) {
                        //do something
                        return myRepository
                                .doThingA();
                    } else {
                        //do something else
                        return myRepository
                                .doThingB();
                    }
                }
            });

如果我想为此编写一个单元测试,我需要编写至少 4 种测试方法,以便涵盖条件逻辑的所有组合。

通常当发生这种情况时,我会编写一个自定义类来处理逻辑,然后对该类进行单元测试。所以上面的代码可能会变成这样:

public class MyFunc implements Func1<ObjectA, Observable<ObjectA>> {
    @Override
    public Observable<ObjectA> call(final ObjectA object) {
        //The logic that was formerly in an anonymous Func1 is now here
        if (object.isAvailable()) {
            //do something
            return myRepository.fetchAnotherThing();
        } else {
            //do something else
            return myRepository.fetchADifferentThing();
        }
    }
}

public class MyOtherFunc implements Func1<ObjectA, Observable<ObjectA>> {
    @Override
    public Observable<ObjectA> call(final ObjectA object) {
        //The logic that was formerly in an anonymous Func1 is now here
        if (object.isFull()) {
            //do something
            return myRepository.doThingA();
        } else {
            //do something else
            return myRepository.doThingB();
        }
    }
}

现在 MyFunc 和 MyOtherFunc 现在可以自行进行单元测试,我可以单独验证逻辑是否正确。然后我的原始代码可以转换成这样的:

Observable<ObjectA> observable = myRepository
            .fetchA()
            .flatMap(myFunc)
            .flatMap(myOtherFunc);

这当然看起来更简洁,并且减轻了我对链的单元测试的需要,以像以前一样处理许多情况,但现在的问题是我如何测试剩下的内容?

如果我模拟 myFunc 和 myOtherFunc 的输出,那么看起来我根本没有真正测试任何东西。另一种选择是使用 myFunc 和 myOtherFunc 的实际实现,但现在我不再进行单元测试,而是进行集成测试,这带来了额外的麻烦。

之前有没有其他人为此苦恼过,如果有,您是如何解决可测试性问题的?

【问题讨论】:

  • 如果您为myFuncmyOtherFunc 创建模拟,那么您可以测试a) 时序、b) 错误条件、c) 并行性、d) 组合的变化。例如,当fetchA() 发出多个项目时会发生什么?没有项目?

标签: android unit-testing rx-java rx-android


【解决方案1】:

您的方法似乎是合理的,特别是如果您想将被测单元分离到它们自己的类中。但是只要保留代码原样也可以,只要它不比您拥有的更复杂。

但是,如果您的 flatMap 内部的逻辑确实变得更加复杂,您可能会考虑将该代码移动到单独的方法中,如下所示:

class ClassUnderTest {
  private MyRepository myRepository;

  public void doSomething() {
    Observable<ObjectA> observable = myRepository
      .fetchA()
      .flatMap(new Func1<ObjectA, Observable<ObjectA>>() {
        @Override
        public Observable<ObjectA> call(final ObjectA object) {
          return getMoreThings(object);
        }
      })
      .flatMap(new Func1<ObjectA, Observable<ObjectA>>() {
        @Override
        public Observable<ObjectA> call(final ObjectA object) {
          return doThings(object);
        }
      });
  }

  Observable<ObjectA> doThings(ObjectA object) {
    if (object.isFull()) {
      //do something
      return myRepository.doThingA();
    } else {
      //do something else
      return myRepository.doThingB();
    }
  }

  Observable<ObjectA> getMoreThings(ObjectA object) {
    if (object.isAvailable()) {
      //do something
      return myRepository.fetchAnotherThing();
    } else {
      //do something else
      return myRepository.fetchADifferentThing();
    }
  }

或者您可以以这种方式链接可观察对象:

class ClassUnderTest2 {
  private MyRepository myRepository;

  public void doSomething() {
    Observable<ObjectA> fetchAsync = myRepository.fetchA();
    Observable<ObjectA> fetchMoreAsync = getFetchMoreAsync(fetchAsync);
    Observable<ObjectA> doThingsAsync = getDoThingsAsync(fetchMoreAsync);
  }

  Observable<ObjectA> getFetchMoreAsync(Observable<ObjectA> fetchAsync) {
    return fetchAsync
      .flatMap(new Func1<ObjectA, Observable<ObjectA>>() {
        @Override
        public Observable<ObjectA> call(final ObjectA object) {
          if (object.isAvailable()) {
            //do something
            return myRepository.fetchAnotherThing();
          } else {
            //do something else
            return myRepository.fetchADifferentThing();
          }
        }
      });
  }

  Observable<ObjectA> getDoThingsAsync(Observable<ObjectA> fetchMoreAsync) {
    return fetchMoreAsync
      .flatMap(new Func1<ObjectA, Observable<ObjectA>>() {
        @Override
        public Observable<ObjectA> call(final ObjectA object) {
          if (object.isFull()) {
            //do something
            return myRepository.doThingA();
          } else {
            //do something else
            return myRepository.doThingB();
          }
        }
      });
  }
}

在这两个示例中,您都可以独立于完整的 Observable 流来测试行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-27
    • 1970-01-01
    • 1970-01-01
    • 2014-06-27
    • 2015-06-22
    相关资源
    最近更新 更多