【问题标题】:How to test a presenter MVP Android如何测试演示者 MVP Android
【发布时间】:2019-09-27 12:04:46
【问题描述】:

我正在尝试了解我应该如何测试我的应用程序,我仍在学习 mockito 我还看到了 mockk 但无法使其工作,这是我的 Presenter

class MyPresenterImpl @Inject constructor(var myUseCase: MyUseCase) : MyContract.Presenter {

    private var mView: MyContract.View? = null
    private var disposable: Disposable? = null


    override fun attachView(view: MyContract.View) {
        this.mView = view
        this.mView?.showProgressBar(true)
    }

    override fun loadResults() {

        disposable = getList.execute()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { result ->
                    mView?.showProgressBar(false)
                    mView?.showResults(result)
                },
                { error ->
                    mView?.showProgressBar(false)
                    mView?.showError(error.localizedMessage)
                })
    }

    override fun rxJavaUnsuscribe() {
        if (disposable != null && !disposable!!.isDisposed) {
            disposable!!.dispose()
        }
    }

    override fun detachView() {
        this.mView = null
    }

}

我应该如何测试这个演示者?我必须添加所有这些方法吗?

我试图用mockito 来做,但我也可以用mockk。

有些人告诉我,我必须对Schedulers 做一些事情并使用trampoline,但我不清楚你们中的任何人都可以提供一个例子或稍微解释一下吗?

【问题讨论】:

    标签: android unit-testing kotlin mockito mockk


    【解决方案1】:

    如果我正确理解了您的问题,那么您正在尝试了解如何使用单元测试(使用 Mockito)实现完整的 MVP 模式。

    我编写了一个示例代码(显示书籍列表的应用程序),它解释了与一个 JUnit 测试用例相同的基本 MVP 实现:https://github.com/harneev/AndroidMVPkt

    让我们在这里谈谈类:

    1. ViewContract.kt - 定义引导哑视图执行操作的方法的接口。
    2. ModelContract.kt - 接口定义获取数据的方法(从数据库或从服务器),这些数据将封装在实现类下。
    3. Presenter.kt - 处理所有业务逻辑并通过作为参数注入的具体视图和模型来编排此逻辑。

    注意:Presenter 是一个普通的类,业务逻辑编排器依赖于模型和视图。一些开发者喜欢在View 接口中添加Presenter 引用,但这样更简洁。

    现在为这个 MVP 设计提供单元测试用例 (PresenterTest.kt)。

    我使用 mockito-kotlin 作为更好的 kotlin 的模拟框架 支持。

    我只在这个场景中添加了一个名为test if books are displayed() 的测试用例,它模拟ViewContract 和ModelContract 并初始化Presenter。最后Mockito.verify方法验证view是否收到了model生成的书籍列表。

    为了更好的单元测试用例,我总是将其分解为以下三个场景,解释如下:

    // 1. given
    how mocks will behave when their respective methods are called
    // 2. when
    when execution method is called
    // 3. then
    verify / assert if required action is performed
    

    希望这会有所帮助。

    【讨论】:

    • 你没有使用RxJava,我的问题是如何测试这个presenter...比如如何测试attachView()、loadResult()、rxJavaUnsuscribe()、dettachView()...
    • @StuartDTO 您可以使用 roboelectric 搜索一次性和观察者 junit 测试。但是我建议创建一个执行模拟操作并返回您可以断言的内容的单元测试。不需要测试某个类中的所有方法。
    • 您能否提供我的演示者的示例,以便我可以在您的答案中添加赏金?
    • 你能帮我解决这个问题吗?
    • @StuartDTO 我已经更新了我的 git 库 develop 分支,支持 rx java。我在 rx java 上工作不多,但我尝试添加一个测试 TrampolineScheduler 和 SimpleScheduler 以便更好地理解何时使用哪个。看看测试用例,因为它有模拟服务,当被调用时会返回所需的值。
    【解决方案2】:
    1. 为您的测试包中的junit测试创建自定义规则TestSchedulerRule.kt

      class TestSchedulerRule(private val scheduler: Scheduler = Schedulers.trampoline()) : TestRule {
              override fun apply(base: Statement, d: Description): Statement {
                  return object : Statement() {
                       override fun evaluate() {
                           RxJavaPlugins.setIoSchedulerHandler { scheduler }
                           RxJavaPlugins.setComputationSchedulerHandler { scheduler }
                           RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
                           RxJavaPlugins.setSingleSchedulerHandler { scheduler }
                           RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
                           RxAndroidPlugins.setMainThreadSchedulerHandler { scheduler }
      
                           try {
                               base.evaluate()
                           } finally {
                               RxJavaPlugins.reset()
                               RxAndroidPlugins.reset()
                           }
                       }
                  } 
            }
      }
      
    2. 为您的演示者创建 MyPresenterImplTest,并使用创建的规则编写您需要的单元测试。例如,我使用 kotlin-mockito 和 junit4 添加了一项演示者逻辑测试。

      @RunWith(MockitoJUnitRunner::class)
      class MyPresenterImplTest {
      
          @Rule
          @JvmField
          val testRule = TestSchedulerRule()
      
          private val view: MyContract.View = mock()
          private val myUseCase: MyUseCase = mock()
      
          private val presenter = MyPresenterImpl(myUseCase)
      
          @Before
          fun setUp() {
              presenter.attachView(view)
          }
      
          @Test
          fun `WHEN btnLoad clicked EXPECT load and show results`() {
              //create needed results
              val results = listOf<Any>()
      
             //mock the result of execution myUseCase.invoke()
              whenever(myUseCase.invoke()).thenReturn(Single.just(results))
      
             //trigger the needed action
              presenter.loadResults()
      
              //verify that all needed actions were called
              verify(myUseCase).invoke()
              verify(view).showResults(results)
          }
      }
      

    规则说明。

    我们需要创建自定义测试规则,因为 AndroidSchedulers.mainThread() 返回的默认调度程序(当您在演示者中编写 .observeOn(AndroidSchedulers.mainThread() 时)是 LooperScheduler 的一个实例,并且依赖于在 JUnit 测试中不可用的 Android 依赖项。

    所以我们需要在测试运行之前使用不同的调度器初始化 RxAndroidPlugins。使用规则允许您跨多个测试类重用初始化逻辑。

    【讨论】:

      【解决方案3】:

      要为演示者编写单元测试,您应该: 1.在创建presenter对象进行测试时模拟myUseCase:private val myUseCase = mock<MyUseCase>() 2. addSchedulers.io()andAndroidSchedulers.mainThread()to the constructor of the presenter then you can setSchedulers.trampoline()`:

          class MyPresenterImpl @Inject constructor(
              val myUseCase: MyUseCase,
              val ioScheduler: Scheduler = Schedulers,
              val uiScheduler: Scheduler = AndroidSchedulers.mainThread()
          ) : MyContract.Presenter
      

      然后在您的测试中seUp():

          presenter = MyPresenterImpl(myUseCase, Schedulers.trampoline(), Schedulers.trampoline())
      
      1. 存根用例的函数execute():
          myUseCase.stub {
              on { execute() }.thenReturn(Single.just(xyz))
          }
      
      1. 验证您的代码

      注意:我更喜欢使用 Nhaarman 的 Mockito 版本

      【讨论】:

      • 感谢您的回答,但为什么我必须将这些参数添加到演示者的构造函数中?
      • 您可以创建 2 个变量而不是添加这些参数,但您必须在创建演示者后将 Schedulers.trampoline() 分配给这些变量。然后在您的函数中,您应该使用那些调度程序subscribeOn(ioScheduler) 和observeOn(uiScheduler)。目的是在运行测试时使用 Schedulers.trampoline()
      • 而且没有其他方法可以在不更改的情况下进行测试?
      • @StuartDTO 您不想更改调度程序?
      • 我的意思是在不接触演示者本身的情况下不能在其他课程中完成?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多