读者诸君,今日我们适当放松一下,不钻研枯燥的知识和源码,分享一套高效的摸鱼绝活。
我有一位程序员朋友,当时在一个团队中开发Android应用,历经多次考核后发现:
在组内以及与iOS团队的对比中:
不禁令人产生好奇,他是如何做到代码别的又快,质量又高的
经过多次研究我终于发现了奥秘。
为了行文方便我用"老L"来代指这位朋友。
平日里最常见的bug有哪些?
老L:"那么问题来了,为什么这些浅显的问题,在交测前没有被发现呢?"
我陷入了思考...
是开发们都很懒吗?也不至于啊!
是时间很紧来不及吗?确实节奏紧张,但也不至于不给调试就拿去测了!
"emm, 可能是迭代的节奏的太频繁,压力较大,并没有整块的时间用来自测联调"
老L接过话茬,"假定你说的是正确的,那么就有两种可能。"
"第一种,自测与联调要比开发还要耗费心思的一件事情。但实际上,你我都知道,这一点并站不住脚!"
"而第二种,就是在开发阶段无法及时测试,拖到开发完,简单测测甚至被催促着就交差了"
仔细的思考后
"确实,这是一个挺麻烦的问题,听你一说,我感觉除了多给几天,开发完集中自测一波才行" 我如是说到。
"NO NO NO",老L又打断了我:"你想的过多了,你想借助一个可靠的、已经完备的后端系统来进行自测。对于你的需求来说,这个要求过高了,你这是准备干QA的活"
"我帮你列举一下情况"
总而言之,我们只需要先排除掉浅显的错误。而这些浅显的错误,属于情况2、3
老L接着说道:"你先歇歇吧,我来说,你再插嘴这文章就太长了!"
接下来就可以实现矛盾转移:"如何模拟数据进行测试",准确的说,问题分成两个子问题:
先看问题2:"如何从接缝中塞入数据,让系统得以使用"
脑暴一下,可以得出结论:
应用内部
应用外部
简单分析:
得出结论:
再仔细分析: 方案1和方案3可以合并,形成一个完整的方案,但未必需要限定在缓存机制中
OK 我们先搁置一下这个问题,看前一个问题。
简单脑暴一下,无非三种:
人工介入,手动编写 -- 成本过大
人工介入,逻辑编码
"第一种代价过大,暂且抛弃"
"第二种可以采用,但是人力成本不容忽视! 一个可以说服我使用它的理由是:"可以精心设计单测数据,针对性的发现问题"
"第三种很轻松,例如使用Mockito,但生成合适的数据需要花费一定的精力"
我们来扒一扒第三种方式,其核心思想为:
获取类信息,得到属性集
遍历属性填充 >
不难得出结论,这一方法虽然很强大,但 创建高度定制化的数据 是一件有挑战的事情。
举个例子,模拟字符串时,一般会使用语料集作为枚举,进行取值。要得到“地址”、“邮箱”等特定风格的数据,需要结合框架做配置,客观上存在较高地学习、使用门槛。
你也知道,前几年我图好玩,写了个 mock库 。
必须强调的一点:“我并不认为我写的库比Mockito等库强大,仅仅是在我们开发人员够用的基础上,做到尽可能简单!”
所以,我能使用它便捷的生成合适的假数据,在开发阶段及时的进行 “伪集成”
此刻,我再也忍不住要发言了:“且慢,老L,你这个做法有一定的侵入性吧。而且,如果数据类在不同业务下复用的话,是否存在问题呢?”
老L顿了顿,“确实,google的annotations是源码级注解,并不是运行时,我为了保持简单,使用了运行时反射而非代码生成。所以确实存在一定的代码侵入性”。
但是,我们可以基于此建立一套简单的MOCK-API,这样就不存在代码侵入了。
另外,也可以增加一套Annotation-Processor 实现方案,这样就可以适当沿用项目中的注解约束了,但我个人认为华而不实。
看你的第二个问题,Mocker一开始确实存在这个问题,有一次从Spring的JSR380中得到灵感,我优化了注解规则,这个问题已经被解决了。得空你可以顺着这个图看看:
或者去看看代码和使用说明:github.com/leobert-lan…
此时我已经有点云里雾里,虽然听起来很牛,如何用起来呢?我还是很茫然,简直人麻了!不得不再次请教。
老L笑着说:“你问的是一个实践方案的问题,而这类问题没有银弹.不同的项目、不同的习惯都有最适宜的方法,我只能分享一下我的想法和做法,仅做参考”
在之前的项目中,我自己建了一个Mock-API,利用我的Mocker库,写一个假数据接口就是分分钟的事情。
测试机挂上charles代理,有需要的接口直接进行mapping,所以在客户端代码中,你看不到我做了啥。
当然,这个做法是在软件外部。
如果要在软件内部做,我个人认为这也是一个华而不实的事情。不过不得不承认是一件好玩的事情,那就提一些思路。
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处理流程中靠后,此时在 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
思路基本类似,不再展开。
听完老L的思路,我若有所思,若有所悟。他的方案似乎很有效,而且直觉告诉我,这些方案中还有很多留白空间,例如:
但我似乎遗漏了问题的开始
是否原意做 用于约束假数据生成规则的基础建设工作呢??? 例如维护注解
事情终究是人干的,人原意做,办法总比困难多。