躺在床上了,突然想到很久没写文章了,写什么呢?要不来个质量提升系列吧,就这样深夜爬起来了。。。
互联网公司发版节奏快,对于兜底(背锅)的测试来说,压力真的很大,不但是考核的问题,还要面对各方指责。
在这浮躁的时代,评价一个优秀的测试人员,标准不是技术有多牛,开发的工具有多炫,职位有多高,收入令人羡慕。而是,如何务实、用心的为提升效率和质量,如何交付高质量的版本而努力工作。还是刚入行那句话,不忘初心,方得始终。
1.开发人员没有进行有效的单元测试,带着一大堆问题提交;
2.代码没有进行 Code Review,带着一大堆问题入库;
3.版本没有提测标准,带着一大堆问题到测试环境;
4.测试人员没有充足的时间,基于黑盒很难覆盖到每条路径;
5.生产部署出纰漏,线上没有进行快速有效的回归。
1~3 都是基于白盒,对于质量就是阿喀琉斯之踵,这部分没做好,缺陷会犹如源头之水,接踵而至。
本文要说的是第 2 点 Code Review。
写自己的代码容易,读别人的代码好难。我想说的是,掌握了方法,其实不难。
1.首先,要了解开发对于需求实现的设计方案,了解架构图、流程图、时序图;
2.其次,了解工程设计的层次结构,找到程序的入口;
3.然后,根据找到的入口,就可以从上往下,层层展开了;
以一个典型的后台服务组件为例,直接对外提供服务的是 Controller 层(程序入口),再往下是 Service 层,负责业务逻辑,再往下是 DAO 层,负责数据操作。当然,也会有一些基于 AOP 的横向切面逻辑,一般作用于 Controller 层,做一些防重、拦截等特殊性处理。还有一些是公共方法、工具类方法等。
在 review 代码前,要有自己的工作重点,并根据常见问题建立常规检查点。
以下 2 点需重点 review
1.影响主流程跑通的类和方法;
2.多个地方调用到的,影响面大的公共方法;
Code Review 常规检查点
以下所列只是常规检查点,实际操作不仅限于以下几点。
1.异常处理:是否需要捕获异常,捕获异常了有没有处理,捕获未处理的有没有往上抛,往上抛的有没有被上层处理;
// 向上抛出异常
public class Demo {
public void testException(Object obj) {
if (null == obj) {
ServiceException serviceException = new ServiceException("666", "test");
throw serviceException; // 向上抛出捕获的异常
}
}
}
// 未捕获异常
public class Test {
public String testDemo(Object obj) {
new Demo().testException(obj); // 未捕获异常
return "test";
}
public static void main(String[] args) {
Test test = new Test();
String str = test.testDemo(null);
System.out.println(str);
}
}
2.补偿机制:是否需要补偿机制,补偿机制是否合理;
举个例子
完成一个方法有 A、B、C 三种方案,正常情况下走默认的 A 方案,如果遇到 xx 问题,走备选方案 B,如果遇到 xx 问题,走备选方案 C。同时,还要鉴定备选方案 B、C 是否合理,是否为最优方案。
// 下段代码的补偿机制使用了默认值,更好的方案是redis遇到异常,channel返回null,channel尝试去数据库取值,db遇到异常再考虑默认值
private static final String DEFAULT_CHANNEL = "default";
public String loadChannel(String key) {
String channel = null;
if (StringUtils.isNotEmpty(key)) {
try {
channel = redisUtil.get(key);
if (StringUtils.isEmpty(channel)) {
channel =mapper.loadKey(key);
if (StringUtils.isNotEmpty(channel)) {
redisUtil.set(key, channel);
}
}
} catch (Exception e) {
// 如果redis服务发生异常无法正常使用时,channel使用默认值
channel = DEFAULT_CHANNEL;
}
}
return channel;
}
3.NPE 问题:某些赋值或条件,如果不进行适当的健壮性处理,会存在空指针的隐患;
// 如果a.getResponseCode()为null,报NPE
if (a.getResponseCode().equals(Constant.CLIENT_RESPONSE_BODY)) {
}
// 常量放前面则不会报NPE
if (Constant.CLIENT_RESPONSE_BODY.equals(a.getResponseCode())) {
}
4.大对象 IO:某些地方如果频繁进行大对象的磁盘 IO,可能对性能造成影响,程序员容易在打日志的时候疏忽这个问题;
log.info("linsence入参:{}",dto); // 此处打印图片的Base64码,耗费磁盘资源,也影响IO性能
5.SQL 问题:业务层面,需检测操作的数据,如查询的数据是否符合业务预期。性能方面,需检测该语句是否存在性能问题;
6.隐私安全:对于金融产品涉及的账密、证件、手机、银行卡等敏感信息是否进行了脱敏处理;
7.资源浪费:浪费内存资源、cpu 资源等情况;
例如:对变量赋值,而该变量从未使用、字符串用+连接、对象无意义的 new 了一个实例等。
Demo demo = new Demo(); // 此处无需new对象
demo = CreateDemo.invoke();
8.参数校验:重点关注 Controller 层的方法入参,是否如接口文档设计进行了必填项、取值范围、长度、类型等校验;
@AccessControl
@NotRepeatable
@ApiOperation(value = "xx", notes = "xx")
@RequestMapping(value = "/test", method=RequestMethod.POST)
public ResponseDto<?> demo(@RequestBody RequestDto<TestDTO> dto) {
String xx= service.save(dto.getData()); // 没有在控制层对参数做校验
......
}
9.内存泄露:使用的资源未释放,可能导致内存泄露,如流文件未正确关闭等;
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); //如果是高频调用的方法,outputStream没有关闭流,存在内存泄漏隐患
10.错误码设计:糟糕的错误码设计和错误描述,不利于今后的问题定位。
ValidationError("100101", "ValidationError"), // 错误描述不明确
1.代码阶段发现和修复问题,效费比是最高的;
2.白盒发现的一些问题,往往是黑盒很难发现的;
3.对研发人员形成隐形的威慑,不能再随意的写代码了;
4.能间接提升版本的提测质量;
5.提升测试人员的核心竞争力。
Gitlab:只适用于 git 仓库。
Phabricator:支持 git、svn 等,真正做到了提交前的在线 code review,后续会单独介绍。
code review 是提升质量的重要环节,一般以研发人员主,测试为辅共同完成,研发不做,测试有条件的也可以做。由测试提出的问题,需要研发人员配合修复,从个人经历而言,自下而上大都不会被重视,自上而下推动,可能落地效果更好。笔者认为,记录下发现的所有问题,问题应验了,自然会改变研发对测试的固化思维。