前言

读者诸君,今天咱们恰当放松一下,不研究枯燥的常识和源码,共享一套高效的摸鱼绝活。

我有一位程序员朋友,当时在一个团队中开发Android运用,历经多次查核后发现:

在组内以及与iOS团队的比照中:

  • 他的任务量略多
  • 但他的bug数量和严重度均低
  • 但他加班的时刻又少于其他人

不由令人发生猎奇,他是怎么做到代码别的又快,质量又高的

经过多次研究我终于发现了奥妙。

为了行文便利我用”老L”来代指这位朋友。

最常见的客户端bug

“老L,听说昨夜上线,你又坐那摸鱼看测验薅别人,有什么诀窍吗?”

老L:”诀窍?倒也谈不上,你这么说,我却是有个问题,你觉得素日里最常见的bug有哪些?”

“emm,编码上不强健的地方,例如NPE,IndexOutOfBoundsException,UI上的可就海了去了,文本长度纷歧导致显现不下,距离问题,乱七八糟的一大堆”

老L:”哈哈,都是些看起来很天真、愚笨的问题吧?是不是测验挂嘴边的那句:’ 你就不能跑一跑吗,你又不瞎,跑两下不就看到了,这么显着!!!’ “

我忽然来了兴致,”你是说咱们有必要上 TDD(test-driven-develop),依照DevOps思想,在CI(Continuous Integration)的时候,顺带跑自动化测验用例发现问题?”

老L忽然打断了我:”不要拽你那些词了,记住了,工作是要人干的,机器只能替代可重复劳动,现在还不能替代人的主观能动性,拽词并不能处理问题。咱们现已找到了第一个问题的答案,现在换个角度”


素日里最常见的bug有哪些?

  • 编码不强健, 例如NPE,IndexOutOfBoundsException
  • UI细节问题, 例如文本长度纷歧导致显现不下,距离,等

为什么很粗浅的问题没有被发现

问题来了

老L:”那么问题来了,为什么这些粗浅的问题,在交测前没有被发现呢?”

我陷入了思考…

是开发们都很懒吗?也不至于啊!

是时刻很紧来不及吗?的确节奏严重,但也不至于不给调试就拿去测了!

“emm, 或许是迭代的节奏的太频繁,压力较大,并没有整块的时刻用来自测联调”

问题不大

老L接过话茬,”假定你说的是正确的,那么就有两种或许。”

“第一种,自测与联调要比开发还要耗费心思的一件工作。但实践上,你我都知道,这一点并站不住脚!”

“而第二种,便是在开发阶段无法及时测验,拖到开发完,简略测测乃至被敦促着就交差了”

细心的思考后

  • 事务逐步展开,无法在恣意时刻自由地进行有用的集成测验
  • 后端节奏并不比前端快多少,在前端的开发阶段,难以凭借后端接口测验,或许接口也有问题

“的确,这是一个挺麻烦的问题,听你一说,我感觉除了多给几天,开发完会集自测一波才行” 我如是提到。

没有问题

“NO NO NO”,老L又打断了我:”你想的过多了,你想凭借一个牢靠的、现已完备的后端体系来进行自测。关于你的需求来说,这个要求过高了,你这是预备干QA的活”

“我帮你罗列一下状况”

  1. 一些数据处理的算法,这种没有方法,老老实实写单元测验,在开发阶段就能够做好,保障牢靠性
  2. UI呢,咱们现在写的代码,根本都做到了UI与逻辑分层,只要能模仿数据,就能跑起来看页面
  3. 事务层,后端逻辑咱们无法操控,但 Web-API 调用的状况能够剖析下并做一下测验,而关于回来数据的JSON结构校验、束缚性校验也能够考虑做一下测验

总而言之,咱们只需求先排除掉粗浅的过错。而这些粗浅的过错,归于状况2、3

老L接着说道:”你先歇歇吧,我来说,你再插嘴这文章就太长了!”

接下来就能够完成矛盾转移:”怎么模仿数据进行测验”,精确的说,问题分红两个子问题:

  • 怎么生成模仿数据
  • 怎么从接缝中塞入数据,让体系得以运用

或许存在的接缝

问题来了

先看问题2:”怎么从接缝中塞入数据,让体系得以运用”

脑暴一下,能够得出结论:

  • 运用内部
    • 替换调用web-api的事务模块,运用假数据调用事务链,一般替换Presenter、Controller实例
    • 替换Model层,不调用web-api,回来假数据或用假数据调用回调链
    • 侵入网络层完成,不进行实践网络层交互,直接运用假数据
    • 遵从切面,向缓存等机制模块中植入假数据
  • 运用外部
    • 运用署理,回来假数据
    • 假数据服务器

简略剖析:

  • “假数据服务器” ,并且运用逻辑编造假数据的价值太大,过。
  • “运用署理,回来假数据”,能够用于特定问题的调试,不适用广泛状况,过。
  • “替换调用web-api的事务模块”,成本过大,过。
  • “替换Model层”,对项目的依靠注入办理具有较大挑战,备选,或许带来很多冗余代码。
  • “侵入网络层完成”,优选。
  • “向缓存等机制模块中植入假数据”,操作真实的缓存较复杂,但能够考虑添加一个 Mock缓存完成模块,根据SPI等机制,能够处理冗余代码问题,备选。

得出结论:

  • 计划1:”侵入网络层完成”,优选
  • 计划2:”替换Model层”,(项目的依靠注入做得很好时)作为备选,或许带来冗余代码
  • 计划3:”向缓存等机制模块中植入假数据”,添加一个 Mock缓存完成模块,备选。(根据SPI等机制,能够处理冗余代码问题)

再细心剖析: 计划1和计划3能够兼并,形成一个完整的计划,但未必需求限定在缓存机制中

没有问题

OK 咱们先放置一下这个问题,看前一个问题。

满是问题

创造假数据

简略脑暴一下,无非三种:

  • 人工介入,手动编写 — 成本过大
    • 或许在前期预备好,根本是纯文本
    • 或许运用一个交互东西,在需求数据时介入,经过图形化操作和输入发生数据
  • 人工介入,逻辑编码
  • 根据反射等自省机制,并完全随机或许根据束缚生成数据

“第一种价值过大,暂且扔掉”

“第二种能够选用,可是人力成本不容忽视! 一个能够说服我运用它的理由是:”能够精心设计单测数据,针对性的发现问题”

“第三种很轻松,例如运用Mockito,但生成适宜的数据需求花费必定的精力”

咱们来扒一扒第三种方法,其中心思想为:

  1. 获取类信息,得到属性集
  2. 遍历属性填充
    >
  1. 根底类型、箱体类型,枚举,确定取值范围,运用Random取值,赋值
2. 普通类、泛型类,创立实例,回归步骤1
3. 调集、数组等,创立实例,回归步骤1,搜集填充

不难得出结论,这一方法尽管很强壮,但 创立高度定制化的数据 是一件有挑战的工作。

举个例子,模仿字符串时,一般会运用语料集作为枚举,进行取值。要得到“地址”、“邮箱”等特定风格的数据,需求结合框架做配置,客观上存在较高地学习、运用门槛。

你也知道,前几年我图好玩,写了个 mock库 。

有必要着重的一点:“我并不认为我写的库比Mockito等库强壮,仅仅是在咱们开发人员够用的根底上,做到尽或许简略!”

你也知道,Google 在Androidx(前身为support)中供给了一套注解包: annotations。但Google并未供给bean validation 完成
,我之前也根据此做过一套JSR303完成,有一次突发创意,这套注解的含义相同适用于 声明假数据取值范围 !!!

所以,我能运用它便捷的生成适宜的假数据,在开发阶段及时的进行 “伪集成”


此刻,我再也不由得要发言了:“且慢,老L,你这个做法有必定的侵入性吧。并且,如果数据类在不同事务下复用的话,是否存在问题呢?”

老L顿了顿,“的确,google的annotations是源码级注解,并不是运行时,我为了保持简略,运用了运行时反射而非代码生成。所以的确存在必定的代码侵入性”。

可是,咱们能够根据此树立一套简略的MOCK-API,这样就不存在代码侵入了。

别的,也能够添加一套Annotation-Processor 完成计划,这样就能够恰当沿袭项目中的注解束缚了,但我个人认为虚有其表。


看你的第二个问题,Mocker一开始的确存在这个问题,有一次从Spring的JSR380中得到创意,我优化了注解规则,这个问题现已被处理了。得空你能够顺着这个图看看:

Mocker.png

或许去看看代码和运用说明:github.com/leobert-lan…

再次审视怎么处理接缝

此刻我现已有点云里雾里,尽管听起来很牛,怎么用起来呢?我仍是很茫然,简直人麻了!不得不再次请教。

老L笑着说:“你问的是一个实践计划的问题,而这类问题没有银弹.不同的项目、不同的习惯都有最适宜的方法,我只能共享一下我的想法和做法,仅做参考”

在之前的项目中,我自己建了一个Mock-API,利用我的Mocker库,写一个假数据接口便是分分钟的工作。

测验机挂上charles署理,有需求的接口直接进行mapping,所以在客户端代码中,你看不到我做了啥。

当然,这个做法是在软件外部。

如果要在软件内部做,我个人认为这也是一个虚有其表的工作。不过不得不承认是一件好玩的工作,那就提一些思路。

根据Retrofit的CallAdapter

public interface CallAdapter<R, T> {
    Type responseType();
    T adapt(Call<R> call);
    abstract class Factory {
        public abstract @Nullable
        CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
                              Retrofit retrofit);
        protected static Type getParameterUpperBound(int index, 
                                                     ParameterizedType type) {
            return Utils.getParameterUpperBound(index, type);
        }
        protected static Class<?> getRawType(Type type) {
            return Utils.getRawType(type);
        }
    }
}

很显着,咱们能够追加注解,用以区分是否需求考虑mock;

可选:关于有或许需求mock的接口,能够继续追加切面,完成在软件外部操控运用 mock数据真实数据

而Retrofit现已运用反射确定了方法的 return Type ,在Mocker中也有习惯的API直接生成假数据

根据Retrofit的Interceptor

相比于上一种,拦截器现已在Retrofit处理流程中靠后,此刻在 Chain 中能够得到的内容现已归于Okhttp库的范畴。

所以需求必定的前置措施用于确定 “return Type”、”是否需求Mock” 等信息。能够凭借Tag机制:

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Tag {
}
@GET("/")
Call<ResponseBody> foo(@Tag String tag);

最终从 Request#tag(type: Class<out T>): T? 方法获取,并接入mock,并生成 Response

其他针对Okhttp的封装

思路根本类似,不再展开。

写在最终

听完老L的思路,我若有所思,若有所悟。他的计划好像很有用,并且直觉告诉我,这些计划中还有很多留白空间,例如:

  • 借用SPI等技能思路,能够容易的处理 “Mock 模块集成与移除” 的问题
  • 提早外部操控是否Mock的接缝,能够在加一个东西APP、或许Socket+网页端东西 用以完成操控

但我好像遗漏了问题的开始

问题来了

是否本意做 用于束缚假数据生成规则的根底建设工作呢??? 例如维护注解

工作终究是人干的,人本意做,方法总比困难多。

最终一个小问题: