做为测试人,开发BUG频出,我该最怎样避免线上事故......

Posted 百度测试开发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了做为测试人,开发BUG频出,我该最怎样避免线上事故......相关的知识,希望对你有一定的参考价值。

目录:导读


前言

测试的目的不仅仅是为了发现软件缺陷与错误,而且也是对软件质量进行度量和评估,以提高软件的质量。

测试的原则
基于测试是为了寻找软件的错误与缺陷,评估与提高软件质量。

一个产品的研发过程中,bug是不可避免的。bug很难彻底消除,只能避免,尤其避免那些线上影响范围大的,涉及关键功能点的高级事故。今天说几点我们日常工作中能有效避免线上事故的拙见。

1、上线流程规划
不完整的发布和迭代是隐患出现的一大原因。成熟而完整的产品线应该严格按照需求->开发->测试->运维发布的持续集成过程,除此之外,还应该附带有标准的hotfix流程&灰度流程,最大程度从根源预防风险。

很多时候,临时需求加塞,慌忙上线,线上直接修代码等一些过火操作是导致重大事故的直接原因,一定要避免。

除此之外,一些常用的事件追踪管理工具比jira/mantis,建立的相关sprint版本,hotfix版本,临时上线版本,正式版本号等,这些发布要走的必要流程,看起来比较偏行政化的工作,却是必须要有人来管理和维护的;记录清楚事件的流转,这样即使线上出现了事故也能快速回滚或及时止损。

2、前后端分离的重要性
公司一般都有明确的前端开发人员,后端开发人员;但测试可能没有前端测试人员和后端测试人员,这就要求测试同学在工作的时候分清楚前后端需求,对于出现的问题,能够通过抓包等方式迅速定位到。

怎么做到前后端分离呢?
一般认为:
API自动化(first 后端自动化)
UI自动化(second 前端自动化)

对于前端bug,比如一些控件,元素,特效等,js报错等相对比较容易定位。

对于后端,尤其是service相关的bug,定位可从以下几方面入手:
关键日志抓取/请求抓包
API情况,关键error code&message
数据库数据情况

前后端分离是清晰化测试任务的有效方法,也是及时响应bug/处理bug/分类bug的有效方法。

3、做好单元测试和集成测试
单元测试一般在工程代码里同时存在,由开发同学来完诚。做好底层单元测试,提升分支覆盖情况,提升代码质量,最大限度避免一些低级却致命的错误。单元测试-大指标可以说是覆盖率,有些人认为覆盖率太片面,但覆盖率却是最直观,最容易做,收益比较高的一个工程。

不过,如果不做覆盖率的话,现在SonarQube比较火,它是一个代码质量管理的平台,通过插件机制,Sonar 可以集成不同的测试工具,进行代码质量分析工具和报告,像类似这种工具也可以联系起来。

那集成测试呢,就是系统级别的测试。这种测试可以发现,联调时候的问题,前后端不一致,接口返回数据等系统性的问题,集成测试有时候和回归测试一起做,目的是保障之前的功能正常运行。

4、做好bug管控
有些公司内部,bug还是比较乱的。遗留的新建的去年的今年的,各种bug越积越多,没有推进,会直接导致一系列的连锁反应,所以测试同学联合开发同学最好有对bug的把控或认知,分清重要级紧急级优先级,哪些应该优先解决,哪些需要配合解决,哪些需要重构,哪些需要改表,都要十分清楚,把控把控,怎么把控呢,就是一不要事故牵着鼻子走。

5、认真对待每一次需求
每个产品及产品线,只要正常运转,都会不可避免的面临持续迭代和发版。每次的需求,都需要正规走需求评审,用例设计,开发自测等环节,认真对待每次需求,再小的需求也要做到五脏俱全,指不定哪里就会爆雷。

有的时候说开发紧张或者排期紧张,只能说前期规划并没做好,导致整个团队跟着一起紧张,这样的事情尽量避免。但如果实在不行,那就要临时更换上线方案,走一些hotfix等等。

我们测试人员,是为了质量保障而存在。线上事故的数量直接衡量了这个产品的稳定性。QA做的每个措施,下的每个决定,都可能在这个产品线起到影响。所以,认真对待上线,认真对待产品本身,Deadline is deadline,认真对待排期,才能做到持续且长久的质量保证。

下面是我整理的2022年最全的软件测试工程师学习知识架构体系图

一、Python编程入门到精通

二、接口自动化项目实战

三、Web自动化项目实战

四、App自动化项目实战

五、一线大厂简历

六、测试开发DevOps体系

七、常用自动化测试工具

八、JMeter性能测试

九、总结(尾部小惊喜)

成人的世界,天黑可以矫情,天亮就要拼命,岁月不会安逸,无论你当下经历了什么,天亮以后依然踏着荆棘前行!

生活中,应对困境,咱们常常会有走投无路的感觉。不好气馁,坚持下去,要坚信年轻的人生没有绝路,困境在前方,期望在拐角。只要咱们有了正确的思路,就必须能少走弯路,找到出路!

成功不是属于跑得最快的人,而是属于一直在跑的人。

恐怖!ThreadLocal引起的一次线上事故

前言

不知道什么时候年轻的我曾一度认为Java没啥难度,没有我实现不了的需求,没有我解不了的bug直到我遇到至今难忘的一个bug 。 线上用户存储数据后查看提示无权限

初次定位

明明自己添加的数据,为什么提示自己没有权限呢?我一开始自信地认为是我们的客户操作有问题、或者是我们权限配置有问题

但是带我自己亲自验证了一下之后发现这个问题时现时不现,属于一个偶发的问题。这个在开发阶段还真的不容易发现。

问题升级

  • 经过自己的测试后让我更加怀疑人生了,你要么就有问题要么就没问题。一会有一会没有到底又是几个意思呢?偶先的问题真的很难解决啊。问题定位到这里我已经精疲力竭了。然后就放弃了定位
  • 但是问题还是得解决,第二天我又硬着头皮开始研究了。可能第二天头脑比较清醒我发现我们系统中在插入数据的时候会自动获取到当前登录用户并在数据库中记录次数据的创建者及最新的修改者。这更应该说明我们的问题离谱,但是问题在我们获取当前登录用户的时候出现了问题
  • 对此,我将问题追踪了一下,终于将问题本质找到了。我们获取当前登录用户是通过ThreadLocal 来实现的。那么问题就是``ThreadLocal` 获取用户信息有问题
  • 我们分布式开发系统。我们会在每个模块里添加一个aop拦截器,通过请求头的token再去user模块查询用户基本信息。然后放到``ThreadLocal中。这样我们的系统中随处都可以通过ThreadLocal` 这个对象是获取我们的登陆用户。
  • 别问我为什么要在每个模块都这样做?别问我为什么用ThreadLocal?别问我为什么是分布式还要这样做? 因为今天我们的重点是解决bug
    在这里插入图片描述

开门见山

问题就出现在getUser那块逻辑里。因为我们的设计就是在系统中随处都可以获取到User对象。当然我们这里指的是任何请求里。对于MQ、定时器这些模块里肯定是没有User的。因为这些没法走AOP拦截
ThreadLocal获取用户信息乱串,导致用户新增数据权限异常

最终定位

我们的ThreadLocal 是个对象,我们系统中是通过一个工具类获取这个对象的属性的。在这个对象我们提供set、get方法。
在这里插入图片描述
上面的流程展示了在获取到User用户之后就会加入到工厂。如果工厂已经存在了就不会加入。否则就不会加入我们的用户

这样也是避免我们不断加入重复的用户信息。因为同一个线程对应的只可能是一个用户。

思考

public static UserInfo getUser() {
    return userThreadLocal.get();
}

上面是我们工具类的get方法。这就是将ThreadLocal对象存储的内容返回出去。这一步应该不会出现问题。

在getUser中很明显没有问题,我们利用排除法只剩下了setUser了。虽然排除了别人的嫌疑但是setUser我还是看不出有什么问题。经过一阵debug断点跟踪后我发现我们setUser逻辑的确有问题

setUser是将用户信息保存到``Threadlocal 对象中,但是前提是ThreadLocal`中没有用户。对就是这个问题,如果已经有了用户呢?那么我们真正的用户就会无法添加进去到了这里问题逐渐地明朗起来。使我们ThreadLocal对象管理的有问题。导致保存了上次的用户信息从而导致用户信息乱串的现象

解决问题

既然我们已经定位到ThreadLocal的管理问题,那么我们就好办了。

ThreadLocal简单梳理

在这里插入图片描述

  • ThreadLocal 将对象保存在线程中。换句话说就是每个线程的数据会相互隔离。基于这个特性我们可以将用户信息存储在这里,这样我们能保证我们的当前线程下执行分各种方法都能通过他获取到用户信息

  • ThreadLocal内部是将已自己为key, 存储对象为value存储到当前线程中的map中。这个map会随着线程的销毁而被JVM回收。

  • 但是在我们实际开发中经常会使用线程池来避免线程的重复创建及销毁。那么线程往往是不会被销毁的

  • 在Spring中集成的类似Tomcat、JBoss等web容器中都是默认使用的一定数量的线程数的。而我们在-spring中使用的线程复用功能就导致了我们在获取当前线程的用户时因为此线程被别人使用过从未导致用户信息没有被更新成功。从而引发我们上面提到的奇怪的问题

  • 那么既然是没有被更新,到这里我们就很好解决了,要么每次使用完成后都将ThreadLocal中的数据remove。因为他内部是弱引用在下次回收就会将对象回收这样也不会造成内存泄漏的问题

  • 或者我们在我们的AOP中setUser之前先将用户ThreadLocal清空。两种方式都可以完美解决我们的问题

具体代码实现

/**
 * 请求生命周期最后一步销毁是做的回调事件
 * 用于销毁在线用户信息,防止在线用户信息互相干扰(在多线程复用时)
 */
@WebListener
@Primary
public class SysServletRequestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent requestEvent) {
        UserInfoUtil.clearUserInfo();
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {

    }
}
  • 我们可以通过spring提供的监听器,监听一个请求的生命周期在这个请求完成之后将我们的ThreadLocal进行remove。 为什么我推荐这种做法呢。因为请求结束就清空可以快速地让出内存让他去做更加有用的事情。

  • 如果是第二种方法那么如果我们没有人在登录,或者说在下一次登录之前这块不需要的内存永远被占着

总结忠告

这次问题出现得很是奇怪,一度让我怀疑人生,但是永远相信程序是不会无缘无故地出问题的。

出问题的只能是我们的代码有问题,要善于解析问题,将问题细化,细化到我们代码层面而不是业务层面

使用一个技术时最好能先了解他内部的一个原理。或者最起码先了解他的大概逻辑

别看这篇文章寥寥几字就解决了我们的问题,但是实际上我在解决他的过程中吃了不少的苦。好几个夜晚都是我在陪他战斗

我在定位到时ThreadLocal后就花了一个小时学习了下他的逻辑并跟踪了他的源码。最后结合我们的业务才发现了眉目

总之有问题是好事情,有了问题我们才能成长。至少在这次的问题中我学习到了ThreadLocal。我的这次问题也是使用他的典型问题,另外还有一个内存泄漏的问题这是在学习他源码的过程领悟到的一点。关于内存泄漏我们有时间再看吧。问题解决。终于可以继续happy了。

以上是关于做为测试人,开发BUG频出,我该最怎样避免线上事故......的主要内容,如果未能解决你的问题,请参考以下文章

自动驾驶事故频出后,小鹏汽车说要做「中国化的自动驾驶」

腾讯云物理硬盘固件版本Bug,导致用户线上「生产数据完全丢失」

恐怖!ThreadLocal引起的一次线上事故

多线程之死锁定位及故障分析,尽可能避免线上事故(十三)

Java开发面试技能介绍,教你解决线上频出MySQL死锁问题

如何进行bug总结