单元测试
Action1:好的单元测试必须遵守 AIR 原则。
说明: 单元测试在线上运行时, 感觉像空气(AIR) 一样感觉不到, 但在测试质量的保障上, 却是非常关键的。 好的单元
测试宏观上来说, 具有自动化、 独立性、 可重复执行的特点。
- A: Automatic(自动化)
- I: Independent(独立性)
- R: Repeatable(可重复)
Action2:单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。不准使用 System.out 来进行人肉验证,单元测试必须使用 assert 来验证。
Action3:保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
反例:method2 需要依赖 method1 的执行,将执行结果作为 method2 的输入。
Action4:单元测试是可以重复执行的,不能受到外界环境的影响。
说明:单元测试通常会被放到持续集成中,每次有代码 push 时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。
正例:为了不受外界环境影响,要求设计代码时就把 SUT(System under test) 的依赖改成注入,在测试时用 Spring 这样的 DI 框架注入一个本地(内存) 实现或者 Mock 实现。
Action5:对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。
说明:测试粒度小才能在出错时尽快定位到出错的位置。单元测试不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。
Action6:核心业务、核心应用、核心模块的增量代码确保单元测试通过。
说明:新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。
Action7:单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。
说明:源码编译时会跳过此目录,而单元测试框架默认是扫描此目录。
Action8:单测的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都要达到 100%
说明:在工程规约的应用分层中提到的 DAO 层,Manager 层,可重用度高的 Service,都应该进行单元测试。
Action9:编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
- B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
- C:Correct,正确的输入,并得到预期的结果。
- D:Design,与设计文档相结合,来编写单元测试。
- E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。
Action10:对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。
反例:删除某一行数据的单元测试,在数据库中,先直接手动增加一行作为删除目标,但是这一行新增数据并不符合业务插入规则,导致测试结果异常。
Action11:和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。 或者对单元测试产生的数据有明确的前后缀标识。
正例:在基础技术部的内部单元测试中,使用 FOUNDATION_UNIT_TEST_的前缀来标识单元测试相关代码。
Action12:对于不可测的代码在适当的时机做必要的重构,使代码变得可测避免为了达到测试要求而书写不规范测试代码。
Action13:在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例(UC)。
Action14:单元测试作为一种质量保障手段,在项目提测前完成单元测试,不建议项目发布后补充单元测试用例。
Action15:为了更方便地进行单元测试,业务代码应避免以下情况:
- 构造方法中做的事情过多。
- 存在过多的全局变量和静态方法。
- 存在过多的外部依赖。
- 存在过多的条件语句。
说明: 多层条件语句建议使用卫语句、 策略模式、 状态模式等方式重构。
Action16:不要对单元测试存在如下误解:
- 那是测试同学干的事情。 本文是开发手册, 凡是本文内容都是与开发同学强相关的。
- 单元测试代码是多余的。 系统的整体功能与各单元部件的测试正常与否是强相关的。
- 单元测试代码不需要维护。 一年半载后, 那么单元测试几乎处于废弃状态。
- 单元测试与线上故障没有辩证关系。 好的单元测试能够最大限度地规避线上故障
前后端规约
Action1:前后端交互的 API, 需要明确协议、 域名、 路径、 请求方法、 请求内容、 状态码、 响应体。
说明:
- 1) 协议: 生产环境必须使用 HTTPS。
- 2)路径: 每一个 API 需对应一个路径, 表示 API 具体的请求地址:
- a) 代表一种资源, 只能为名词, 推荐使用复数, 不能为动词, 请求方法已经表达动作意义。
- b) URL 路径不能使用大写, 单词如果需要分隔, 统一使用下划线。
- c) 路径禁止携带表示请求内容类型的后缀, 比如".json", “.xml”, 通过 accept 头表达即可。
- 3) 请求方法: 对具体操作的定义, 常见的请求方法如下:
- a) GET:从服务器取出资源。
- b) POST: 在服务器新建一个资源。
- c) PUT: 在服务器更新资源。
- d) DELETE: 从服务器删除资源。
- 4) 请求内容: URL 带的参数必须无敏感信息或符合安全要求; body 里带参数时必须设置 Content-Type。
- 5) 响应体: 响应体 body 可放置多种数据类型, 由 Content-Type 头来确定。
Action2:前后端数据列表相关的接口返回, 如果为空, 则返回空数组[]或空集合{}。
- 说明: 此条约定有利于数据层面上的协作更加高效, 减少前端很多琐碎的 null 判断。
Action3:服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、errorMessage、 用户提示信息四个部分。
说明: 四个部分的涉众对象分别是浏览器、 前端开发、 错误排查人员、 用户。其中输出给用户的提示信息要求: 简短清
晰、 提示友好, 引导用户进行下一步操作或解释错误原因, 提示信息可以包括错误原因、 上下文环境、 推荐操作等。
- errorCode: 参考 。
- errorMessage:简要描述后端出错原因,便于错误排查人员快速定位问题,注意不要包含敏感数据信息。
正例: 常见的 HTTP 状态码如下
- 1) 200 OK: 表明该请求被成功地完成,所请求的资源发送到客户端。
- 2) 401 Unauthorized: 请求要求身份验证,常见对于需要登录而用户未登录的情况。
- 3) 403 Forbidden:服务器拒绝请求,常见于机密信息或复制其它登录用户链接访问服务器的情况。
- 4) 404 NotFound: 服务器无法取得所请求的网页,请求资源不存在。
- 5) 500 InternalServerError:服务器内部错误。
Action4:在前后端交互的 JSON 格式数据中, 所有的 key 必须为小写字母开始的 lowerCamelCase
风格, 符合英文表达习惯, 且表意完整。
正例: errorCode / errorMessage / assetStatus / menuList / orderList / configFlag
反例: ERRORCODE / ERROR_CODE / error_message / error-message / errormessage
Action5:errorMessage 是前后端错误追踪机制的体现, 可以在前端输出到 type=“hidden” 文字类控件中, 或者用户端的日志中, 帮助我们快速地定位出问题。
Action6:对于需要使用超大整数的场景, 服务端一律使用 String 字符串类型返回, 禁止使用 Long 类型。
说明: Java 服务端如果直接返回 Long 整型数据给前端, Javascript 会自动转换为 Number 类型(注: 此类型为双精度浮点数, 表示原理与取值范围等同于 Java 中的 Double)。 Long 类型能表示的最大值是 2^ 63 -1, 在取值范围之内, 超过 2^ 53(9007199254740992) 的数值转化为 Javascript 的 Number 时, 有些数值会产生精度损失。
扩展说明,:在 Long 取值范围内, 任何 2 的指数次的整数都是绝对不会存在精度损失的, 所以说精度损失是一个概率问题。 若浮点数尾数位与指数位空间不限, 则可以精确表示任何整数, 但很不幸, 双精度浮点数的尾数位只有 52 位。
- 反例: 通常在订单号或交易号大于等于 16 位, 大概率会出现前后端订单数据不一致的情况。
- 比如, 后端传输的 “orderId”: 362909601374617692, 前端拿到的值却是: 362909601374617660
Action7:HTTP 请求通过 URL 传递参数时,不能超过 2048 字节。
说明: 不同浏览器对于 URL 的最大长度限制略有不同, 并且对超出最大长度的处理逻辑也有差异, 2048 字节是取所
有浏览器的最小值。
反例: 某业务将退货的商品 id 列表放在 URL 中作为参数传递, 当一次退货商品数量过多时, URL 参数超长, 传递到后端的
参数被截断, 导致部分商品未能正确退货。
Action8:HTTP 请求通过 body 传递内容时,必须控制长度,超出最大长度后,后端解析会出错。
说明: nginx 默认限制是 1MB, tomcat 默认限制为 2MB, 当确实有业务需要传较大内容时, 可以调大服务器端的限制。
Action9:在翻页场景中, 用户输入参数的小于 1, 则前端返回第一页参数给后端;
- 后端发现用户输入的参数大于总页数,直接返回最后一页。
Action10:服务器内部重定向必须使用 forward; 外部重定向地址必须使用 URL 统一代理模块生成, 否则会因线上采用 HTTPS 协议而导致浏览器提示“不安全” ,并且还会带来 URL 维护不一致的问题。
Action11:服务器返回信息必须被标记是否可以缓存,如果缓存,客户端可能会重用之前的请求结果。
说明: 缓存有利于减少交互次数, 减少交互的平均延迟。
正例: http1.1 中, s-maxage 告诉服务器进行缓存,时间单位为秒,用法如下,response.setHeader("Cache-Control", "s-maxage=" + cacheSeconds);
Action12:服务端返回的数据, 使用 JSON 格式而非 XML。
说明: 尽管 HTTP 支持使用不同的输出格式, 例如纯文本, JSON, CSV, XML, RSS 甚至 HTML。如果我们使用的面
向用户的服务, 应该选择 JSON 作为通信中使用的标准数据交换格式, 包括请求和响应。此外, application/JSON 是
一种通用的 MIME 类型, 具有实用、 精简、 易读的特点。
Action13:前后端的时间格式统一为"yyyy-MM-dd HH:mm:ss", 统一为 GMT。
Action14:在接口路径中不要加入版本号, 版本控制在 HTTP 头信息中体现, 有利于向前兼容。
说明: 当用户在低版本与高版本之间反复切换工作时, 会导致迁移复杂度升高, 存在数据错乱风险。