【问题标题】:How to test an Android class that depends on CountDownTimer如何测试依赖于 CountDownTimer 的 Android 类
【发布时间】:2018-06-24 14:55:03
【问题描述】:

我正在为一个安卓应用程序编写一个 ViewModel,它应该实现并启动一个 CountDownTimer 以便不时更新一些 UI。

我最近开始练习 TDD,我想知道我应该做出哪些架构决策才能使 ViewModel 可测试(我希望测试能够快速运行,而不依赖于真正的计时机制)。 我无法将 CountDownTimer 作为依赖项提供,因为它是在 ViewModel 本身中实现的抽象类,所以我“不知道”要给出什么实现。

一般来说,在使用具有严格约束的框架和不可测试的框架代码时,编写测试的最佳实践是什么?

这是我目前拥有的代码。如何让它可测试?

import android.arch.lifecycle.ViewModel;
import android.os.CountDownTimer;

class MyViewModel extends ViewModel {

    private MyView myView;

    public void init(MyView myView) {
        this.myView = myView;
        new CountDownTimer(0, 1000) {
            @Override
            public void onTick(long l) {
                myView.updateUi(l);
            }

            @Override
            public void onFinish() {
                myView.updateUiFinished();
            }
        }.start();
    }

    public interface MyView {
        void updateUi(long l);

        void updateUiFinished();
    }
}

【问题讨论】:

  • 不要模拟/测试你不拥有的代码。将它们视为应封装在您控制的抽象后面的第 3 方依赖项。在可测试性方面,这将具有更大的灵活性。您当前的代码与CountDownTimer 紧密耦合,因此在测试时更难以控制所需的行为。

标签: android unit-testing testing dependencies tdd


【解决方案1】:

不要模拟/测试你不拥有的代码。将它们视为应封装在您控制的抽象后面的第 3 方依赖项。在可测试性方面,这将具有更大的灵活性。

您当前的代码与CountDownTimer 紧密耦合,因此在测试时更难以控制所需的行为。

import android.arch.lifecycle.ViewModel;
import android.os.CountDownTimer;

class MyViewModel extends ViewModel {
    private MyView myView;
    private MyCountDownTimer timer;

    public MyViewModel(MyCountDownTimer timer) {
        this.timer = timer;
    }

    public void init(MyView myView) {
        this.myView = myView;
        timer.attach(this.myView);
        timer.start();
    }

    public interface MyView {
        void updateUi(long l);
        void updateUiFinished();
    }

    public interface MyCountDownTimer { 
        void attach(MyView view);
        void start();
        void cancel();
    }
}

public class DefaultUiUpdateTimer extends MyViewModel.MyCountDownTimer {
    private CountDownTimer timer;

    public void attach(MyViewModel.MyView myView) {
        timer = new CountDownTimer(0, 1000) {
            @Override
            public void onTick(long l) {
                myView.updateUi(l);
            }

            @Override
            public void onFinish() {
                myView.updateUiFinished();
            }
        };
    }   

    public void start() {
        timer.start();
    }

    public void cancel() {
        timer.cancel();
    }
}

MyViewModel 现在与CountDownTimer 分离,因为实例的创建已被反转,并且显式依赖注入到构造函数中。您可以在 init 方法中轻松地将它与视图一起传递

public void init(MyView myView, MyCountDownTimer timer) {
    this.myView = myView;
    this.timer = timer;
    timer.attach(this.myView);
    timer.start();
}

匿名CountDownTimer 子类在技术上是一个实现关注点,现在它已被提取并封装在其关注点中,允许您执行任何您认为适合视图的操作。

为了测试视图模型,可以将 mock/stubs/fakes 传递给测试的主体,并配置所需的行为以允许执行测试以完成。

【讨论】:

  • 非常好。准确,好听。太好了!
  • @Nkosi 你如何测试 myView.updateUi(l) 被调用? ps:我喜欢你包装 3rd 方的东西以使其更容易测试的想法。
  • @sudocoder 取决于测试用例。您能否澄清您所指的案例。视图是被测对象还是视图模型?
猜你喜欢
  • 1970-01-01
  • 2017-07-08
  • 2016-01-20
  • 2018-12-27
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多