Menu Home

RxJava源码分析(一):Observable的几种创建方式

相信写Android的,写Java的人应该都听说过RxJava这个革命性的东西了,很多人也了解这个东西怎么使用。网上也有很多优秀的介绍性的文章,如果你还不了解这个东西,或者是不熟悉它的用法的话,我会建议先看这个系列的文章,然后再看这篇。尤其是后者绝对强烈推荐,其实我个人对RxJava的看法,刚开始只是觉得这个东西写起来语法非常简洁,非常优雅,可以在很大程度上面减少一些外部变量的使用,非常Functional。但是等到我看了那篇文章以后,我才发现,我只是发挥了RxJava不到一成的功力,获得了它不到一成的优点。它还有那么强大的力量,隐藏在冰山水下。另外,国内也有广受好评的扔物线写的给Android开发者的RxJava详解。这些足够让你对RxJava有一个比较深入的了解,此外,扔物线的文章也有一定程度的原理讲解和源码分析,可谓真正的深入浅出。 Anyway,这里我们就不介绍RxJava的用法和特点了,我们简单看看它的源代码。 先看第一部分,Observable的创建。现在大部分介绍RxJava的文章、尤其是介绍RxJava在Android上面的使用的文章,都往往会结合第三方的library,比如Retrofit。里面介绍Observable的使用方式是通过Retrofit的返回结果来获得一个Observable,再操作这个Observable。这容易给我们一种错觉,就好像我们自己从来不用,也不能创建Observable一样。然后其实不是这样的,我们完全可以自己创建Observable。常见的创建Observable的方式有以下几种: Observable.just(…) 比如: Observable.just("xiaochuang"); //Create an Observable that has only one item: "xiaochuang" Observable.just("xiaochuang", "handsome"); //Create an Observable that has 2 items: "xiaochuang" and "handsome" Observable.just("xiaochuang", "is", "handsome"); //Create an Observable that has 3 items: "xiaochuang", "is" and "handsome" //…you can pass in up to 10 items Observable.from(an_array_or_iterable); […]

《Working Effectively with Legacy Code》阅读分享(一)

端午节开始看这本编程领域经典的书《Working Effectively with Legacy Code》,中文版叫《修改代码的艺术》。英文原版豆瓣评分9分,中文版8.2分。Amazon.com评分9.2分。 从评分上看,这是一本质量非常不错的书。中文版的书名翻译虽然少了点感觉,多了点俗气——现在书名动不动什么什么艺术,什么什么之道,甚至什么什么之禅,真是让人不知道该作何感想。但应该说,这个书名翻译得还是很准确的。因为这本书讲的就是如何修改代码! 书的开篇讲了修改代码的四种原因: Adding a feature Fixing a bug Improving the design Optimising resource usage 对于前两种原因来说,改代码自然是理直气壮,没什么可以商量的余地。然而,对于后两种原因,通常就不是那么肯定了,甚至还有种policy叫做“If it’s not broke, don’t fix it”。这种policy之所以会出现,自然是有它的原因的。最大的原因,往往不是因为改代码需要时间,而是因为更改是有风险的,这种风险就是,你不知道你的更改是不是对的,更要命的是,你不知道你的更改会影响到其它的什么地方,会后带来什么后果。出于这种恐惧的心理,我们往往倾向于不到非不得已,就不去改代码。而这里所说的非不得已,就是添加feature和fix bugs。 然而遗憾的是,这种policy被一次又一次的证明,是行不通的,因为很快你会发现,你的每个类会写得越来越大,每个方法越来越长,结构越来越复杂,越来越难以理解,每次加一个新feature,fix一个bug都越来越困难。于是到了某个点,所有东西都必须推倒重写。这种事情在历史上发生了一次又一次。那怕不会到那一步,烂代码和烂结构也会严重的拖后正常的工作进程。 这种结局是应该被避免的,也是可以被避免的,怎么避免?那就是不要只顾着add feature和fix bugs,也要及时的“Improve the design”,也就是传说中的重构。但是话说回来,重构就会有上面提到的那些风险,说得更具体点,我们需要解决好以下三个问题: What changes do we have to make? How will we know that we’ve done them correctly? How […]

关于安卓单元测试,你需要知道的一切

单元测试是个技术活,它有自己的一套知识体系,要做好单元测试,的确是需要学习和不断的在实践中练习的。好消息是,这套知识体系的范围相对来说是有限的,因此,只要耐心的学习和练习一段时间,就可以做好日常大多数的需求。然后国内这大环境和氛围,大家都清楚,大部分时候大家都在赶feature,赶上线,以至于单元测试这件事情,往往被忽略,甚至从来没考虑过,而这一点在移动开发上面则更加严重。这导致的结果就是,网上很难找到比较系统的关于单元测试各个方面的文章。这就让有些童鞋那怕有做单元测试的想法,却不知道从何下手,因此导致一个恶性循环。 出于这点原因,我根据自己的一些经验和想法,写了一个系列的文章,至此为止写了7篇,然而经常发现有朋友对前面的一些概念不是很清楚,从网站或公众号找之前的一些文章又不是很方便,因此整理了这样一篇汇总性的文章,希望能让大家对此有个更整体化的了解,如果某一方面的东西不是很清楚,也能很快找到参考。 以下文章是这个系列文章的总结。 什么是单元测试:介绍了什么是单元测试,里面澄清了一个最常见的误解,同时介绍了有返回值和没有返回值两种不同的函数——对于OO来说,应该叫“方法”,但是中文里面“方法”这个词太容易产生误解了,因此我这里用函数来代替,这就是翻译尴尬的地方。所以很多时候我建议不如直接用英文单词本身,而不是非要硬生生翻译过来。 为什么要做单元测试:对于什么要做单元测试的个人之见。 JUnit单元测试框架的使用:介绍了题目中说的内容,JUnit是最基础的一个单元测试框架。 Mock以及Mockito的使用:mock自古以来就是单元测试不可分割的一部分,这篇介绍了mock的概念和使用。 依赖注入,将mock方便的用起来:为了将mock更方便简洁优雅的用起来,我们需要用到依赖注入。这篇文章介绍了依赖注入的概念和使用。 使用dagger2来做依赖注入,以及在单元测试中的应用:上一篇文章讲了依赖注入,而Dagger2能让我们易如反掌的使用依赖注入的模式,代码的耦合度变得非常低。 Robolectric,在JVM上调用安卓的类:解决在JVM上做单元测试的时候,无法调用和测试安卓的类的问题。 其它的一些文章: Android单元测试在蘑菇街支付金融部门的实践:这个是在InfoQ的移动前线学习分享群里面作的一个分享,首发于”移动开发前线”公众号,也在InfoQ网站上发表过,讲了的单元测试在蘑菇街支付金融部门的实践。 MDay上的分享-关于安卓单元测试的一切:这是在MDay第五季作的一次分享,有PPT和视频,不过视频暂时还没有公开,如果公开了会第一时间更新过来。 安卓单元测试是不是只有这些东西?当然不是,只不过,主要的概念和框架都在这了。剩下的相对来说更多是一些具体的技巧、解决某个具体问题的方法等等。对于这些东西,以后也会有文章介绍,敬请关注。 最后,如果你对安卓单元测试感兴趣,欢迎加入我们的交流群,因为群成员超过100人,没办法扫码加入,请关注下方公众号获取加入方法。 以上,希望能帮助到你。如果觉得有用,请分享一下,以便帮助到其它人。分享是一种美德,这也是我坚持写作的初衷。 (如果提示群满了,请关注一下公众号,在公众号的菜单有加入方法) 关注公众号,加入单元测试交流群

Android单元测试(七):Robolectric,在JVM上调用安卓的类

今天讲讲Android上做单元测试的最后一个难点,那就是在JVM上无法调用安卓相关的类,不然的话,会报类似于下的错误: java.lang.RuntimeException: Method isEmpty in android.text.TextUtils not mocked. 关于这个话题,其实我以前是写过的,也许今天我回过头来写这个话题,会采用不一样的形式,不一样的心态来写,然而,作为我写过的第一篇关于单元测试的文章,而且看看时间,是去年的6月15号,再过几天,刚好一周年。想想这篇文章是在我刚开始探索,尝试在安卓上面写单元测试的时候,写的一篇文章,如今因为安卓单元测试的原因,我认识了很多同行,甚至不时有人叫我“大牛大神”之类的,虽然知道大家是客气,我也受之有愧,但怎么滴心里也有点虚荣的开心,哈哈哈。。因此现在回过头去看看当时自己写的东西,不禁觉得有点那啥。。。因此,我决定把之前的文章稍作补充和修改,作为这个系列的第七篇。 ———————-以下文字写于去年今天———————– 作为一只本科非计算机专业的程序猿,手动写单元测试是我从来没接触过的东西,甚至在几个月前,我都不知道单元测试是什么东西。倒不是说没听过这个词,也不是不知道它的大概是什么东西——“用来测试一个方法,或者是一小块代码的测试代码”。然而真正是怎么做的?我并没有一个概念,或者说并没有一个感觉。 记得第一份工作在创新工场的时候,听当时的boss说,公司有个神级的程序员,他会写大量的单元测试,甚至50%以上的代码都是单元测试。当时崇拜之极,却仍然觉得写单元测试是很麻烦的一件事情。 扯远了,话说回来,当你接触多了国外的技术博客,视频之后,你会发现,单元测试甚至TDD,在国外是非常流行的事情。很多人甚至说离开了单元测试,他们便没有办法写代码。这些都让我对单元测试的好感度逐渐的上升。然而,真正让我下定决心,一定要研究一下这个东西的,是前段时间看大名鼎鼎的《重构:改善现有代码的艺术》里面的一段话: I’ve found that writing good tests greatly speeds my programming, even if I’m not refactoring. This was a surprise for me, and it is counterintuitive for many programmers… –Martin Fowler 《Refactoring: Improving the Design of Existing Code》 […]

Android单元测试(六):使用dagger2来做依赖注入,以及在单元测试中的应用

注: 代码中的 //<= 表示新加的、修改的等需要重点关注的代码 Class#method表示一个类的instance method,比如 LoginPresenter#login 表示 LoginPresenter的login(非静态)方法。 问题 在前一篇文章中,我们讲述了依赖注入的概念,以及依赖注入对单元测试极其关键的重要性和必要性。在那篇文章的结尾,我们遇到了一个问题,那就是如果不使用DI框架,而全部采用手工来做DI的话,那么所有的Dependency都需要在最上层的client来生成,这可不是件好事情。继续用我们前面的例子来具体说明一下。 假设有一个登录界面,LoginActivity,他有一个LoginPresenter,LoginPresenter用到了UserManager和PasswordValidator,为了让问题变得更明显一点,我们假设UserManager用到SharedPreference(用来存储一些用户的基本设置等)和UserApiService,而UserApiService又需要由Retrofit创建,而Retrofit又用到OkHttpClient(比如说你要自己控制timeout、cache等东西)。 应用DI模式,UserManager的设计如下: public class UserManager { private final SharedPreferences mPref; private final UserApiService mRestAdapter; public UserManager(SharedPreferences preferences, UserApiService userApiService) { this.mPref = preferences; this.mRestAdapter = userApiService; } /**Other code*/ } LoginPresenter的设计如下: public class LoginPresenter { private final UserManager mUserManager; […]

Android单元测试(五):依赖注入,将mock方便的用起来

在上一篇文章中,我们讲了要将mock出来的dependency真正使用起来,需要在测试环境下通过某种方式set 到用到它的那个对象里面进去,替换掉真实的实现。我们前面举的例子是: public class LoginPresenter { private UserManager mUserManager = new UserManager(); public void login(String username, String password) { //。。。some other code mUserManager.performLogin(username, password); } } 在测试LoginPresenter#login()时,为了能够将mock出来的UserManager set到LoginPresenter里面,我们前面的做法是简单粗暴,给LoginPresenter加一个UserManager的setter。然而这种做法毕竟不是很优雅,一般来说,我们正式代码里面是不会去调用这个setter,修改UserManager这个对象的。因此这个setter存在的意义就纯粹是为了方便测试。这个虽然不是没有必要,却不是太好看,因此在有选择的情况下,我们不这么做。在这里,我们介绍依赖注入这种模式。 对于依赖注入(Dependency Injection,以下简称DI)的准确定义可以在这里找到。它的基本理念这边简单描述下,首先这是一种代码模式,这个模式里面有两个概念:Client和Dependency。假如你的代码里面,一个类用到了另外一个类,那么前者叫Client,后者叫Dependency。结合上面的例子,LoginPresenter用到了UserManager,那么LoginPresenter叫Client,UserManager叫Dependency。当然,这是个相对的概念,一个类可以是某个类的Dependency,却是另外一个类的Client。比如说如果UserManager里面用到了Retrofit,那么相对于Retrofit,UserManager又是Client。DI的基本思想就是,对于Dependency的创建过程,并不在Client里面进行,而是由外部创建好,然后通过某种方式set到Client里面。这种模式,就叫做依赖注入。 是的,依赖注入就是这么简单的一个概念,这边需要澄清的一点是,这个概念本身跟dagger2啊,RoboGuice这些框架并没有什么关系。现在很多介绍DI的文章往往跟dagger2是在一起的,因为dagger2的使用相对来说不是很直观,所以导致很多人认为DI是多么复杂的东西,甚至认为只能用dagger等框架来实现依赖注入,其实不是这样的。实现依赖注入很简单,dagger这些框架只是让这种实现变得更加简单,简洁,优雅而已。 DI的常见实现方式 下面介绍DI的实现方式,通常来说,这里是大力介绍dagger2的地方。但是,虽然dagger2的确是非常好的东西,然而如果我直接介绍dagger2的话,会很容易导致一个误区,认为在测试的时候,也只能用dagger来做依赖注入或创建对应的测试类,因此,我这边刻意不介绍dagger。先让大家知道最基本的DI怎么实现,然后在测试的时候如何更方便高效的使用。 实现DI这种模式其实很简单,有多种方式,上一篇文章中提到的setter,其实就是实现DI的一种方式,叫做 setter injection 。此外,通过方法的参数传递进去(argument injection),也是实现DI的一种方式: public class LoginPresenter { //这里,LoginPresenter不再持有UserManager的一个引用,而是作为方法参数直接传进去 public void login(UserManager userManager, String username, String […]

2016年第一次百日计划总结

今年年初,1月2日,我拉了一个群,跟其他24个小伙伴一起做了件事,叫100天计划。这是在“warfalcon”这个公众号看到的一种做法,基本大意就是在100天之内,每天坚持做同一件事情。关于100天计划的具体说明可以在这里跟这里找到。其实这也不是我第一次做这件事情了,去年的时候就执行过一次,当时给自己定的项目是每天看英文书局1个小时。但是那次执行的比较混乱,最后花了130多天才完成,也没有记录得很好。 这次我给自己定的项目是“每天阅读或写作一个小时”,依然不是100%的坚持,很多时候也会偷懒,最后花了114天完成。最后坚持下来的有10个人,其中有3个小伙伴做到了100%坚持,每一天都完成了,也没有请过一天假,真是神一样的存在。 Anyway,我在这期间做完的事情如下: ## 看书 看完了的(后面是评分) Learned Optimism ☆☆☆☆☆ 被讨厌的勇气 ☆☆☆☆☆ 楚亡 ☆☆☆☆☆ 野火集 ☆☆☆☆☆ Matz程序世界 ☆☆☆☆ 旅行的意义 ☆☆☆☆ 月亮与六便士 ☆☆☆☆ 领导梯队 ☆☆☆☆ 亲爱的安德烈 ☆☆☆☆ 没看完的(后面是看完的部分) Android框架解密(40%) Clean code(40%) 说话的魅力(50%) 貌似都是非技术类的,而技术类的,额。。。 ## 写文章### 非技术文章 读《亲爱的安德烈》– 想要这样一段不煽情,不鸡汤,不说教的家书来往 读《野火集》– 民主和文明,需要我们自己去争取 【7分钟乐读会】第24期书籍分享小结 Life is short ### 技术文章 android单元测试在微信群的分享 文章版 FactoryGirl在Rails中的使用 《Clean Code》阅读:在大师们的眼中,什么样的Code才是Clean Code? […]

Android单元测试(四):Mock以及Mockito的使用

几点说明: 代码中的 //<== 表示跟上面的相比,这是新增的,或者是修改的代码,不知道怎么样在代码块里面再强调几行代码 T_T。。。 很多时候,为了避免中文歧义,我会用英文表述 在第一篇文章里面我们提到,返回类型为 void 方法的单元测试方式,往往是验证里面的某个对象的某个方法是否得到了调用。在那篇文章里面,我举的例子是 activity 里面的一个 login 方法: public void login() { String username = …//get username from username EditText String password = …//get password from password EditText //do other operation like validation, etc … mUserManager.performLogin(username, password); } 对于这个 login 方法的单元测试,应该是调用 Activity 里面的这个 login 方法,然后验证mUserManager的performLogin方法得到了调用。但是如果使用 […]

Android单元测试在蘑菇街支付金融部门的实践

大家好,我是蘑菇街支付金融部门的邹勇,花名叫小创。今天很高兴跟大家分享一下安卓的单元测试在蘑菇街支付金融的实践。下面,我们从为什么开始。 为什么要写单元测试 首先要介绍为什么蘑菇街支付金融这边会采用单元测试的实践。说起来比较巧,刚开始的时候,只是我一个人会写单元测试。后来老板们知道了,觉得这是件很有价值的事情,于是就叫我负责我们组的单元测试这件事情。就这样慢慢的,单元测试这件事情就成了我们这边的正常实践了。再后来,在公司层面也开始有一定的推广。 要说为什么要写单元测试的话,我相信大部分人都能承认、也能理解单元测试在保证代码质量,防止bug或尽早发现bug这方面的作用,这可能是大家觉得单元测试最大的作用。然而我觉得,除了这方面的作用,单元测试还能在很大程度上改善代码的设计,同时还能节约时间,让人工作起来更自信、更开心,以及其他的一些好处。这些都是我的切身感受,我相信也是多数真正实践过单元测试的人的切身感受,而不是为了宣传这个东西而说的好听的大话。 说到节约时间,大家可能就会好奇了,写单元测试需要时间,维护单元测试代码也需要时间,应该更费时间才对啊? 这就是在开始分享之前,我想重点澄清的一点,那就是,单元测试本身其实不会占用多少时间,相反,还会节约时间。只是:1. 学习如何做单元测试需要时间;2. 在一个没有单元测试的项目中加入单元测试,需要一定的结构调整的时间,因为一个有单元测试跟没有单元测试的项目,结构上还是有较大不同的。 打个比方,开车这件事情,需要很多时间吗?我相信很少人会说开车这件事情需要很多时间,而是:1. 学习开车,需要一定的时间;2. 如果路面不平的话,那么修路需要一定的时间。单元测试也是类似的情况。 那为什么说单元测试可以节约时间呢?简单说几点:1. 如果没有单元测试的话,就只能把app运行起来测试,这比运行一次单元测试要慢多了。2. 尽早发现bug,减少了debug和fixbug的时间。3. 重构的时候,大大减少手动验证重构正确性的时间。 所以,我希望大家能去掉"没时间写单元测试"这个印象,如果工作上安排太紧,没有时间学习如何做单元测试的话,可以自己私底下学,然后在慢慢应用到项目中。 单元测试简单介绍,以及void方法怎么测 接下来介绍我们这边是怎么做安卓单元测试的。首先澄清一下概念,在安卓上面写测试,有很多技术方案。有JUnit、Instrumentation test、Espresso、UiAutomator等等,还有第三方的Appium、Robotium、Calabash等等。我们现在讲的是使用JUnit和其他的一些框架,写可以在我们开发环境的JVM上面直接运行的单元测试,其他的几种其实都不属于单元测试,而是集成测试或者叫Functional test等等。这两者明显的不同是,前者可以直接在开发用的电脑,或者是CI上面的JVM上运行,而且可以只运行那么一小部分代码,速度非常快。而后者必须要有模拟器或真机,把整个project打包成一个app,然后上传到模拟器或真机上,再运行相关的代码,速度相对来说慢很多。 单元测试的定义相信大家都知道,就是为我们写的某一个代码单元(比如一个方法)写的测试代码。一个单元测试大概可以分为三个部分: setup:即new 出待测试的类,设置一些前提条件 执行动作:即调用被测类的被测方法,并获取返回结果 验证结果:验证获取的结果跟预期的结果是一样的 然而一个类的方法分两种,一种是有返回值的方法。一种是没有返回值的方法,即void方法。对于有返回值的方法,固然测试起来是很容易的,但是对于没有返回值的方法,该怎么测试呢?这里的关键是,怎么样获取这个方法的“返回结果”? 这里举一个例子来说明一下,顺便澄清一个十分常见的误解。比如说有一个Activity,管他叫DataActivity,它有一个public void loadData()方法, 会去调用底层的DataModel类,异步的执行一些网络请求。当网络请求返回以后,更新用户界面。 这里的loadData()方法是void的,它该怎么测试呢?一个最直接的反应可能是,调用loadData()方法(当然,实际可能是通过其他事件触发),然后一段时间后,验证界面得到了更新。然而这种方法是错的,这种测试叫集成测试,而不是单元测试。因为它涉及到很多个方面,它涉及到DataModel、网络服务器,以及网络返回正确时,DataActivity内部的处理,等等。集成测试固然有它的必要性,但不是我们应该最关注的地方,也不是最有价值的地方。我们应该最关注的是单元测试。关于这一点,有一个Test Pyramid的理论: Test Pyramid理论基本大意是,单元测试是基础,是我们应该花绝大多数时间去写的部分,而集成测试等应该是冰山上面能看见的那一小部分。 那么对于这个case,正确的单元测试方法,应该是去验证loadData()方法调用了DataModel的某个请求数据的方法,同时传递的参数是正确的。“调用了DataModel的方法,同时参数是。。。” 这个才是loadData()这个方法的“返回结果”。 Mock的概念以及Mockito框架 要验证某个对象的某个方法得到调用了,就涉及到mock的使用。这里对mock的概念做个简单介绍,以免很多同学不熟悉,mock就是创建一个虚假的、模拟的对象。在测试环境下,用来替换掉真实的对象。这样就能达到两个目的:1. 可以随时指定mock对象的某个方法返回什么样的值,或执行什么样的动作。 2. 可以验证mock对象的某个方法有没有得到调用,或者是调用了多少次,参数是什么等等。 要使用mock,一般需要使用mock框架,目前安卓最常用的有两个,Mockito和JMockit。两者的区别是,前者不能mock static method和final class、final method,后者可以。我们依然采用的是Mockito,原因说起来惭愧,是因为刚开始并不知道JMockit这个东西,后来查了一些资料,看过很多对比Mockito和JMockit的文章,貌似大部分还是很看好JMockit的,只是有一个问题,那就是跟robolectric的结合也有一些bug,同时使用姿势跟Mockito有较大的不同,因此一直没有抽时间去实践过。这个希望以后能够做进一步的调查,到时候在给大家分享一下使用感受。 但是使用Mockito,就有一个问题,那就是static method和final class、final method没有办法mock,对于这点如何解决,我们稍后会介绍到。 […]

Android单元测试(三):JUnit单元测试框架的使用

我们写单元测试,一般都会用到一个或多个单元测试框架,在这里,我们介绍一下 JUnit4 这个测试框架。这是 Java 界用的最广泛,也是最基础的一个框架,其他的很多框架,包括我们后面会看到的 Robolectric,都是基于或兼容 JUnit4 的。 然而首先要解决的问题是。。。 为什么要使用单元测试框架 或者换句话说,单元测试框架能够为我们做什么呢? 从最基本的开始说起,假如我们有这样一个类: public class Calculator { public int add(int one, int another) { // 为了简单起见,暂不考虑溢出等情况。 return one + another; } public int multiply(int one, int another) { // 为了简单起见,暂不考虑溢出等情况。 return one * another; } } 如果不用单元测试框架的话,我们要怎么写测试代码呢?我们恐怕得写出下面这样的代码: public class CalculatorTest { […]