在安卓上如何使用DataBinding测试MVVM模式

6

我一直在网上搜索,但是找不到如何通过MVVM改进测试的方法。我知道有一个ViewModel与视图进行交互的想法,但我不知道如何使用MVVM编写良好的测试用例。我已经在Android中拥有以下ViewModel:

public class ViewModel extends BaseObservable {
    private long countDownTime;
    private MyCountDownTimer mCountDownTimer;
    private final String TAG = getClass().getSimpleName();

    @Bindable    
    public long getCountDownTime() {
        return countDownTime;
    }


    public void setCountDownTime(long countDownTime) {
        this.countDownTime = countDownTime;

        notifyPropertyChanged((int) BR.countDownTime);
        Log.d(TAG,"prime tick:"+countDownTime);
    }

    public void startCounting(Long milli){
        mCountDownTimer.restartTimer(milli);
    }
}

然后我有一个使用它的XML视图。我还有一个活动,实际上将XML绑定到该视图。这个活动看起来像这样:

public class MainActivity extends FragmentActivity {
    CountdownBinder mCountdownBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //setContentView(R.layout.activity_main);
        mCountdownBinder = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //Lets reference our textview just for fun
        mCountdownBinder.tvGreen.setText("initial text");
        ViewModel viewModel = ViewModel.instance();

        //now tell databinding about your viewModel below
        mCountdownBinder.setViewModel(viewModel);
        viewModel.startCounting(200000L);
    }
}

现在我很困惑如何使测试变得更好。我已经阅读了相关资料,但我需要一个真实世界的例子。如果有必要,这段代码来自这篇博客

显然,我可以更轻松地测试我的单元测试,对吗?在MVVM中,我只需要测试viewModel吗?主要需要测试什么?

1个回答

5
您的假设是正确的,您只需要对ViewModel和Model进行单元测试。UI本身不会通过单元测试进行测试,尽管您也可以进行自动化UI测试,这与单元测试不同。
目前为止,您的示例类本身并不太适合进行单元测试。MVVM试图实现的主要目标之一(除了关注点分离)是解耦代码。您的ViewModel应该只包含演示逻辑,而没有业务逻辑。这些都应该放在MVVM的Model层中。
由于您在ViewModel内部实例化了MyCountDownTimer,所以您的ViewModel紧密耦合。因此,每次测试ViewModel类时,您也将测试MyCountDownTimer。这将把您的单元测试转换为集成测试(测试多个组件一起工作)。
按定义,单元测试应仅测试特定类型/类或某个代码块。换句话说,它测试的是代码单元,因此称为单元测试。为了实现这一点,您需要解耦要测试的对象的依赖项。
您可以通过将其拆分为接口和实现,然后通过构造函数注入将具体实现注入到对象中来解耦对象。
例如:
public class ViewModel extends BaseObservable {
    private long countDownTime;
    // Use final keyword here, so mCountDownTimer can only be set in the constructor and never changed
    // this enforces the the classes invariants and once initialized, you'll be sure
    // that it never can be null, so no need to do null checks before using
    private final MyCountDownTimerInterface mCountDownTimer;
    private final String TAG = getClass().getSimpleName();

    public ViewModel(MyCountDownTimerInterface mCountDownTimer) {
        if(countDownTimer == null) {
            throw new IllegalArgumentException("countDownTimer can't be null. ");
        }

        this.mCountDownTimer = countDownTimer;
    }

    @Bindable
    public long getCountDownTime() {
        return countDownTime;
    }


    public void setCountDownTime(long countDownTime) {
        this.countDownTime = countDownTime;

        notifyPropertyChanged((int) BR.countDownTime);
    }

    public void startCounting(Long milli) {
        this.mCountDownTimer.restartTimer(milli);
    }
}

现在,您可以在没有具体实例的情况下测试您的ViewModel。由于您的示例只包含行为而没有结果测试,因此您必须在其中进行行为测试,例如:如果我调用startCounting(10L),那么MyCountDownTimerInterface中必须调用restartTimer(10L),并且getCountDownTime()必须返回10L。为了做到这一点,您需要模拟MyCountDownTimerInterface接口并将模拟对象传递进去。模拟对象可以设置以验证模拟接口的某个方法是否使用某个参数调用。由于我不熟悉Java/Android Mock框架,无法为此提供任何代码。我是C#/.NET开发人员。但是,如果您不知道如何为行为驱动的单元测试模拟接口,请在StackOverflow上提出新问题 :)。

非常感谢您的反馈。您测试的是CountDownTimerInterface,这很有道理。但在我的情况下,模型应该是什么? - j2emanue
1
不,我没有测试 CountDownTimerInterface。它被模拟了。我正在测试 ViewModel 的行为(因为在这个例子中没有数据需要测试)。在你的例子中,你使用了 MyCountDownTimer,但是无法隔离地测试你的 ViewModel,因为每当你调用 this.mCountDownTimer.something 时,你将会测试计数器而不是你的视图模型。我通过用接口替换具体的计数器实现来消除这种耦合,并且在测试期间将模拟这个接口(不使用真正的 MyCountDownTimer 类)。 - Tseng
1
@j2emanue:这里是行为驱动设计(BDD)的链接 https://zh.wikipedia.org/wiki/%E8%A1%8C%E4%B8%BA%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91 - Tseng

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接