前言
读者诸君,今天咱们恰当放松一下,不研究枯燥的常识和源码,共享一套高效的摸鱼绝活。
我有一位程序员朋友,当时在一个团队中开发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的活”
“我帮你罗列一下状况”
- 一些数据处理的算法,这种没有方法,老老实实写单元测验,在开发阶段就能够做好,保障牢靠性
- UI呢,咱们现在写的代码,根本都做到了UI与逻辑分层,只要能模仿数据,就能跑起来看页面
- 事务层,后端逻辑咱们无法操控,但 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,但生成适宜的数据需求花费必定的精力”
咱们来扒一扒第三种方法,其中心思想为:
- 获取类信息,得到属性集
- 遍历属性填充
>
- 根底类型、箱体类型,枚举,确定取值范围,运用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中得到创意,我优化了注解规则,这个问题现已被处理了。得空你能够顺着这个图看看:
或许去看看代码和运用说明: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+网页端东西 用以完成操控
但我好像遗漏了问题的开始
是否本意做 用于束缚假数据生成规则的根底建设工作呢??? 例如维护注解
工作终究是人干的,人本意做,方法总比困难多。
最终一个小问题: