造完了这个轮子--Mocker,却让我感到很羞愧|项目复盘
有必要声明,这篇文章不是标题党,内容也不是凡尔赛。
作为一个Android开发者,我这5年造了 很多
轮子, 粗略数一下:
- 基于FastJson修改了一些漏洞,并兼容了ThinkPHP框架
简化JSON结构的机制
-- 单元素数组直接变对象 - MagicBox --
注解
+运行时反射
简化SaveInstanceState样板代码 - JSBridge -- 基于消息队列机制建立Android和WebView桥接
- RetrofitExt --
注解
+APT
+动态代理
实现Retrofit请求生命周期管理 - DDComponentForAndroid(后改为JIMU)组件化套件
- Gradle Plugin 优化
- 路由中间件
- 消息中间件
- Maat -- 代码隔离场景下的模块加载框架
- Pandora套件
- Pandora -- 适用于复杂列表页面的数据结构
- Pandorarv -- RecyclerView 多样式表
- Pandora Plugin -- Intellij 插件,快速生成
数据代理
+ViewHolder
+基础布局
- Reporter -- 注解生成文档
- DaVinCi -- 干掉shape xml
- Mocker -- 注解约束的Mock框架
- UiBinding -- 取代ButterKnife&更方便的使用ViewBinding、DataBinding
至于自定义视图,RecyclerView相关套件等等,也是一大堆。
其中:
- 有一些是项目不得不拥有
- 有一些,是项目中遇到了效率问题,为了解决开发效率问题而开发的,例如Pandora套件
- 还有一些,纯粹是出于
兴趣
,好玩
例如 Reporter,利用注解生成文档。
但是,有这么一个轮子,写完之后,让我自己感觉很羞愧,并且 为团队
感到羞愧。标题中提到的 Mocker
-- 注解约束的Mock框架
为什么我造了那么多轮子却要复盘这个?
- 这个轮子,和其他的有
本质区别
,可以说,别的轮子,都是服务于业务实现
、效率
,而Mocker,服务于测试
。 - 这是复盘系列的第一篇,我希望利用形式来践行:
先思败
,避开失败点,自然走向成功
我属于 极限编程
的极力反对者,毫无疑问,我认为 单元测试
用例长期有效即具有长效性
,并值得 持续维护
。而我们的项目团队,单元测试覆盖量近乎于1%。
创业团队
,价值导向
,快才是好,越快越好
,快也不是出错的理由
这是目前的团队背景标签。
处于这样的背景下,还身在前端团队,大力推行 TDD
阻力很大,但这不是不作为的理由。
把握测试的粒度
前面提到一句:"大力推行 TDD 阻力很大",Why?
对TDD有一定了解的读者不难理解:设计、编写单测用例的时间 > 开发对应模块功能的时间
打工人
和 资本家
难以就此达成理念一致,能够被接受的,只是一个权衡下的结果,这要求我们 把握测试的粒度
。
我们知道:单元测试
的本质是 对软件中的最小可测试单元进行检查和验证。
而在Android客户端代码中,必然有一道分水岭,隔开了 业务
和 框架
。
对于框架,引入的三方框架,进行一定的冒烟
即可,而对于自研的框架,最好严格的遵循TDD。但是注意,这部分工作和 业务层的测试
是无关的。
不见得会天天折腾框架,但一定天天要处理业务。
对于业务,基本可以分为3块:
- 视图
- 业务逻辑
- 数据
一般而言:
- 视图层的,集成测试即可,哪怕自己写完了运行下过过眼。
- 数据层的,对Web-Service进行的测试不需要前端,基于本地DB建立的业务,作为业务逻辑测试。
- 业务逻辑,把握重点。
另外,项目中会存在各种 数据类代理
,数据中介者
,Wrapper
,这些类基于基础数据类进行了读写封装,或者功能装饰,也值得进行逻辑测试
测试时的障碍--为什么造这个轮子
在前面的复盘中,我们已经清楚了对哪些内容进行单测,即粒度问题。而我们项目的分层架构,基本是:
- MVC
- MVP
- MVVM
- 一些变种 如MVI
那么,测试的主要对象就是:C,P,VM,I这些层。而对于这些内容测试时,需要:注入Model
和View
,这一点无法避免。
如果说,设计时考虑了 可测性
,那么至少在这些业务层,考虑了依赖注入
,依赖抽象
。那么在此基础上,我们不用担心 View的复杂性
,
因为View层东西都不参与测试,而且不应当干预到测试。
但是Model层不一样。这些业务层的业务就是:
响应视图层交互 -> 执行附加业务 ->操作Model-> .... -> 响应Model的变化 -> 执行附加业务 -> 反馈到视图层显示
而利用真实的Model来进行测试,太重,并且可能对测试产生干扰项。所以 不可避免
的,我们需要 设计Model的状态
并进行 Mock
,参与业务层测试。
如果能够以 最小的时间
代价,准确地获得期望的 Model实例,那是极好的。
轮子有了,却没有车
非常遗憾,我花费了半个月的个人时间实现了轮子,转过头来,车并没有来。这一点,我 为团队
感到一丝羞愧。
分明大家都知道,一旦开始考虑TDD:
- 设计会变得更加合理、健壮
- 编码会变得目的明确
- bug会变少、交付质量提升
- 用例作为资产,可以检验迭代需求的合理性
除此之外,我更为自己感到羞愧,因为 自己没有极力推行
并 证明这样做给团队带来的改变
。
知耻而后勇
自我鞭策,在项目中真正落地。自我检讨到此为止。
关于项目本身
存在一个和Mock 相对
的一个概念: Bean Validation
,Bean Validation 是契约式编程的一种体现,基于契约
对数据类进行数据、状态校验。
而Mock 是基于契约生成假数据,构建数据类,但往往这个契约的 约束内容较少
。
因为使用场景是单测,所以性能并不是第一要素,我很大胆的尝试了 Kotlin的运行时反射
,并第一次在项目中直接使用Unsafe,收益良多。
设计概要
mock的思路还是比较清晰的,大体上需要处理三类事情:
- 数组和集合
- 结构性
- 泛型
- 元素赋值
- 数据类
- 泛型
- 依赖注入
- 基本数据类型赋值
- 深度问题,举个例子:单链表导致mock死循环
在设计契约约束时,采用了 注解表达
,例如:IntRange 和 IntDef,可以分别表示 取值范围
和 取值枚举
只需要为 基础数据类型
和 String
设计一个 池
,可以表达 范围
和 枚举
,并且可以获知 size
,即可利用Random,得到随机值。
水到渠成地,在反射数据类时,获取 field
信息以及其注解信息, 在为 基本数据类型及其箱体类型
& String
field 赋值时,
先维护类型取值池,再Random取值即可达成:取值受限
和 随机性
。
而结构型和数据类的构建,对Gson项目比较熟悉的读者就 熟稔于心
了,项目采用了类似实现。
但是在 深度问题
上,我 并没有
想到比较好的方案,目前只是尽力规避了死循环,并在绝大多数场景下,复用了先前创建的对象。
总结
在活动的驱使下,这篇复盘思考了 Mocker项目
和 公司商业应用在单测方面的不足
。既然复盘,那就需要 规划改善不足
。
对于Mocker:
- 参考
Jsr303
和Jsr380
,提供更广兼容、并且打通顺势造一个Android中轮子,一套注解服务于Mock 和 Bean Validation。 - 尝试解决深度问题。
对于公司项目,推进好的idea落地。
最后引用一句名人名言:
在需要面前,一切理想主义都是虚伪的 -- 尼采
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情