TDD for android(based on Mvp)

前言

hacker.png
  • 学习一样东西的时候,从是什么开始,然后会去探究为什么以及这么做之后的好处是什么,当经历了这个过程之后。才能够说是知道这个东西。
  • 笔者在刚开始接触单元测试的时候,只知道这个是测试用的,只知道调用这个类的这个方法是返回这个东西的,但是不知道怎么去测,为什么要去这样子测,会觉得增加代码量和工作负担。
  • 这个时候,就会被卡主。但是,如果是从更高一点的视角去看的话,就会知道怎么去测,我为什么要这样子去测。
  • 开发一个功能,首先从需求的提出,然后针对这个需求设计各种使用的用例。我们开发出一个功能无非就是为了满足用户各种使用场景的需求,根据各种不同的使用场景产生各种结果。
  • 我们身为开发者,所写的一些代码也是为了实现功能。
  • 如果可以在开发的同时,可以顺便把每个用户的使用场景都测试一遍,那该是多么爽的事情啊。这样子写出来的代码就有依据。知道是Cover了什么的一些测试用例。
  • Yeah! We have a thing for the shit! It is call the Test Drive Development(TDD)。It is born for this!

没有测试之前

not_happy.png

测试Pass之后

happy.png

准备

新建类以及接口
LoginContract

LoginContract.png

LoginPresenter

LoginPresenter.png

LoginActivity

LoginActivity.png

LoginPresenter和LoginActivity各自实现了自己的接口,在上面的接口中,我们是没有定义任何一种方法,只是定义了一个模板。里面具体的方法都由接下来的用例所决定。

对于登录这个功能来说,会有以下的测试用例:

  • 用户名为空的时候,显示错误信息

  • 密码为空的时候,显示错误信息

  • 用户名和密码输入成功,登录验证成功后进行跳转

  • 用户名和密码有输入,但是验证不通过,显示错误信息

接下来,根据上面的测试用例,我们开始写我们的单元测试代码
下面只会选取第一个测试用例进行讲解,完整的例子会分享在github上,戳这里

Go For the TDD

前期准备

加入Mockito

testCompile 'org.mockito:mockito-core:2.5.4'

在MVP中,业务逻辑都是写在Presenter中,所以这里我们对LoginPresenter进行一个测试。右键点击LoginPresenter自动生成对应的测试类。

CreateTestClass.png

在LoginPresenterTest类中的setup方法里做一些初始化的操作

public class LoginPresenterTest {

    private LoginContract.View mView;
    private Api api;
    private LoginPresenter mPresenter;
    @Before
    public void setUp() throws Exception {
        mView = mock(LoginContract.View.class);
        api = mock(Api.class);
        mPresenter = new LoginPresenter(mView, api);
    }
}

主要是初始化presenter,presenter需要注入两个对象来完成初始化。
我们把需要注入的两个对象都Mock掉,被Mock掉的对象我们就可以自由操控它,让他返回我们希望要的值。因为我们这里测试的是LoginPresenter,所以,与这个无关的对象的具体逻辑我们不用去关注。
关于Mockito的更多用法可以参考这篇文章Android单元测试(四):Mock以及Mockito的使用

测试第一个用例(用户名为空的时候,显示错误信息)

在LoginPresenterTest中新增一个@Test方法,新增后的代码如下

public class LoginPresenterTest {

    private LoginContract.View mView;
    private Api api;
    private LoginPresenter mPresenter;
    @Before
    public void setUp() throws Exception {
        mView = mock(LoginContract.View.class);
        api = mock(Api.class);
        mPresenter = new LoginPresenter(mView, api);
    }

    @Test
    public void testWhenTheUserNameIsEmpty() throws Exception {
        

    }
}

接下来,我们要为这个测试用例写上一些具体的测试逻辑,并感受TDD的美妙
首先,来写一个伪“中文代码”

presenter调用处理具体逻辑的方法
验证当用户名为空的时候,mView有没有调用到我们期望的方法

以上就是这个测试用例对应的测试逻辑,是不是觉得上面这两句话没啥用,说了相当于没说。
我们只需要去写针对当前测试用例的测试逻辑,并验证。
但是我觉得这才是单元测试美妙的地方,他可以把测试粒度分得很小,以至于我们能够cover到每个case。

No code say a ***, show me the fucking source code

testWhenTheUserNameIsEmpty.png

我们可以看到,很多方法是没有的(上图红色的),我们只需要关注我们需要调用到哪一些方法,只需要关注我们的逻辑,一些模板代码让IDE帮我们生成。
按下快捷键(mac是option+enter, windows是alt+enter)

LoginContract_imp.png

最终的结果就是,在LoginContract中的对应的接口中自动生成了我们想要的方法。
最后,在我们实现了这个接口的类中实现这些方法,在给每个方法写上具体的逻辑。

LoginActivity_实现接口方法.png
LoginPresenter_实现接口方法.png

最终LoginActivity如下

LoginActivity_final.png

最终LoginPresenter如下

LoginPresenter_final.png

以上就是完成了我们第一个测试用例的业务逻辑,接下来就Run一下我们的测试方法,如果能够Pass,说明我们已经Cover了这个用例。那么就可以继续开始写下一个用例的测试方法,然后再去完善我们的逻辑代码,再Run一下我们新的用例的测试方法~如此循环。最后达到Cover每个测试用例的目的。

testpass.png

看到绿色的真高兴,说明我们的第一个测试用例已经通过了。
接下来只需要重复上面的步骤,把剩下的测试用例Cover到了就ok

总结

个人觉得在写单元测试的时候,最难的地方在于测试用例的择写。

在看了前面的例子之后,有人会疑惑,我为什么要把获取用户名和获取用户密码的那一部分抽出一个方法getInputUsername()getInputPwd(),为什么不直接把用户名和密码当做参数传给presenter的方法。
这样子做的原因如下:

  • 对Mock对象有一个更加深刻的理解,笔者刚接触单元测试的时候,基本上都是被Mock对象给搞晕了,特别是类似这样子的代码
when(mView.getInputUsername()).thenReturn("louiszgm");

一开始不知道这样子做的意义是什么,为什么要手动指定返回值,只知道这个方法是指定了Mock对象的特定方法返回了一个特定的值。

这个例子主要是用来去感受TDD的好处,很多地方都没有去完善,比如:

  • 在正式项目中,MVP的使用当然没有这么简陋,需要考虑view的泄漏之类的问题,也会根据每个公司的业务抽出一些基接口
  • Mockito的一些高级用法
  • 结合Dagger2让单元测试来的更爽
  • 这些已经有人写了很多优质的文章,又在这里打一波广告。关于单元测试框架的使用,推荐创神的单元测试系列的文章。
    Android 单元测试: 首先,从是什么开始

个人感觉用TDD这个模式去开发的好处是:

对自己写出来的代码有信心,毕竟cover到了每个用例。
很多模板代码都不用自己写,让关注点更多的关注到我们的逻辑

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容