如何测试依赖于CountDownTimer的Android类

时间:2018-01-15 23:26:20

标签: android unit-testing testing dependencies tdd

我正在为Android应用编写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();
    }
}

1 个答案:

答案 0 :(得分:4)

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

您当前的代码与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传递给测试主题,并配置所需的行为以允许测试完成。