软件开发历史大潮中,我们做了许许多多的努力让代码变得更加优雅。但是「优雅」这一件事,并不是简单的让代码变得漂亮而已,我们的「优雅」是有目的的:可维护
、可扩展
、可测试
。从实用性角度来讲,我认为「可测试」这是我们在产品开发中追求的第一要素,毕竟质量
是我们坚持的第一要素,没有质量背书,许多的工作都是空想。今天我们就来讲讲,如何通过Google
新发布的Android Architecture Components
去写我们的代码,使得我们的项目变得更加「Testable」。
让「可测试」变得有意义
写测试,在圈子里似乎并不是那么的流行,我曾经在几次聚会中问身边的 Android 开发:「你们会自己去写白盒测试么?」,回答都是否定的,如果再问为什么的话,就是「太麻烦了」、「不知道怎么写,不知道写什么」。当我们说 MVC/MVP 的时候,我们究竟是为了什么而实现这些模式的,我们为什么要为一些模块写大量的接口,仅仅是看上去酷炫?最后为了赶工期进度,胡乱写一通的时候,好像也没发生什么问题吧。那么这时候,其实写出来的所谓的「架构」其实并没有什么用处 ———— 当你去重构看起来烦的不行的业务代码的时候,依然需要花大量进行黑盒测试。开发说,架构不可以随便动,因为线上已经跑了很长时间了,至少证明了它暂时是可行的。
我们期望改变这样的状况,我们希望随时随地能真正把我们的产出掌握在自己手里 ———— 哪怕哪一天真的忘记了具体实现,哪怕新同事入职接手了这一份代码,我们也能胸有成竹的说,你随便跑,只用跑一下我写的测试,通过了就好。
首先我们能达成的共识是:我们的业务代码,很多都是和 UI 耦合在一起,很多都是和「生产环境」绑定在一起,所以我们的代码才变得非常的不可测试。我们使用分层架构模型,也只是为了一个目的:使得下层依赖的模块可替换。那么,基于我们所拥有的条件和所碰到的困难,
我们是否可以把我们的分层架构模型用于替换
生产环境呢,这样就可以轻松测试我们的业务逻辑了?答案是肯定的。只要我们把我们的生产环境轻松的替换成我们 Mock 的数据环境,我们的「服务器」就变得可控了,我们可以返回我们期待的结果,看我们的后续业务逻辑流程是否处理正确。
同理,如果我们依赖的 View 层,只用统一的数据流和这一层通信的话,我们的测试只用控制这一个数据流,然后用 Espresoo 等 UI Test 框架去检查 UI 的反应是否正确即可。
Android Architecture Components
提供了一套自顶向下的分层解决方案,很适合我们的场景,使得我们写测试变得更加的容易。
LiveData
其实我更愿意称呼LiveData
为「ViewModel」,如果你对react.js
有了解,它更像是我们 Component 中的props
概念。我们可以为 Activity 或者 Fragment(以下统称 View)提供多个 LiveData。在support-v4
升级到27.0.0
之后,AppCompatActivity
已经自动集成了Lifecycle
相关的接口和实现,LiveData
本身使用「观察者模式」为View
提供数据,如果View
的生命周期销毁,那么它会自动解除注册,不需要我们在onStop
或者onDestroy
中去调用它的生命周期。我们可以使用LiveData
彻底对 View 的配置进行解耦。View 层的代码大致就可以如下
class ExampleActivity extends AppCompatActivity {
@Inject
Presenter presenter;
TextView textView;
Button btn;
public void onCreate(Bundle savedBundle) {
Inject.inject(this);
super.onCreate(savedBundle);
// 一些初始化
...
// 初始化完毕
LiveData<String> textLive = presenter.getTextLive().observe(this, new Observer {
public void onChanged(String value) {
textView.setText(value);
}
})
btn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
presenter.fetchText();
}
});
}
}
那么 View 这层实际就干了两件事:
- 响应事件
- 响应 LiveData 的通知
它的全部职责就是获取到真正的Presenter
(业务实现),并处理好视图相关的所有事情。这样的模式比起以前的「万能 Activity」不知道清晰多少倍。那么如何对这个 View 进行测试呢?很显然,我们只要提供一个受我们控制的Presenter
类即可,后续控制 Presenter 中的 LiveData,通过推送不同的数据,来观察我们的 View 各个字段显示是否正确,测试用的TestPresenter
可以由Dagger
等框架进行注入。
ViewModel
这里的 ViewModel 指的是 ViewModel 这个类的本身。它并不是我们传统理解的那个「ViewModel」,Google 在这是把他定义为存储 View 相关数据的,有生命周期意识的类 —— 它保证了在屏幕翻转等情况下,数据的存续。众所周知,Android 中四大组件的生命周期都不是由我们自己控制的,因此对一些对象的生命周期控制,有时候会显得非常蠢,试想一个场景:
你的一个 Activity 中,有两个 Fragment,他们共享某个共同的状态,不仅对这个状态产生一些响应,同时又都可以改变这个状态。也许其中一个 Fragment 还拥有一个子 Fragment,可能也会改变该状态。
这个场景下,我们想快速写出简单漂亮的代码几乎是不可能的事,那么使用「ViewModel」能让我们在 Activity 或者 Fragment 中获取一个准确的生命周期的内存缓存对象,其中可以存有我们需要的 LiveData,然后只要订阅了这个 LiveData,所有的 View 状态都会一致!
如果看一下ViewModel
源码的话,你会发现,它的本质是在Activity
或者Fragment
中,生成一个看不见的Fragment
———— 也就是ViewProviders.of()
这个函数传的第一个参数中。然后把 ViewModel 存入这个Fragment
里,所有的变量的生命周期跟随这个隐藏的Fragment
。
这么看下来,我更宁愿称呼这个ViewModel
为Presenter
,事实上我们内部项目也是这么命名的,它的职责就是和数据仓库打交道,然后把组装回来的数据通过LiveData
post 到 View 层给开发者。
我们使用RxJava2
来实现数据仓库,测试该层,我们首先可以使用Mockito
来 mock 需要的 LiveData,然后使用MockServer
或者其他的方式,获取我们期望的数据,然后看 LiveData 的行为是否和我们期望的一致(此处的测试更像是 BDD)。
Repository
我们期望把所有IO(DB and Network)封装成 Repository 层供给 Presenter 去调用,表达形式是 Observable,Presenter 层完全不需要知道数据的存储形式是怎么样的,是缓存还是线上的数据,只需要从 Repository 去取就对了。对于这层的测试可以使用 okhttp 提供的MockWebServer
组件去测试相关的代码行为。
Clean Architecture
如果你之前看过「Clean 架构」这个概念,那么你一定对这张图非常熟悉 事实上,我们的整个架构组织也是按照「Clean」的模式来的,在使用 Clean 的过程中,有一个原则非常重要,那就是单向数据流。为了保证逻辑的简单和统一,数据流的「单向」必须保证,必须可控,这和 flux 的世界观非常像:
如果有条件的话,非常建议可以看看 flux 相关的视频 https://facebook.github.io/flux/docs/in-depth-overview.html 加深理解。
结语
做一次架构优化或者做一次架构设计前,我们一定要仔细考虑清楚我们的目的是什么,不能盲目的直接下手,否则我们根本无法对架构设计者的一些理念做出深刻的理解,可能只会徒增烦恼。在质量要求越来越高的当下,我们做出的产品一定是以质量为第一要义的,因此以上是我对Android Architecture Components
在可测试领域上的思考。如果有任何的建议和意见,欢迎留言,欢迎指点。
PS:有赞共享移动技术岗位急缺 iOS / Android,最近有想法的同学快来我们的怀抱吧
Android / iOS 开发工程师
岗位职责
- 参与需求讨论
- 编写代码文档
- 业务模块开发
- 公共组件开发
- 编写单元测试
我们不仅写测试,还写构建系统,还写 WebView 加速模块,还写 Xposed 等等各种好玩的等着你! 简历速来 wenshengzhang@youzan.com