一个多线程bug引发的测试思考

Posted 测试开发栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个多线程bug引发的测试思考相关的知识,希望对你有一定的参考价值。


背景

今天收到测试的一个bug反馈,bug描述:author表中存在重复的作者信息,在提测时我有说明task解析页面后获得作者信息会按authorId 判断DB中是否已经存在,存在了则不往DB中插入,这本是一个很简单的点,可是代码部署到测试环境后,发现还是插入了2条重复authorId 的数据……

调试

1、mysqlauthor表中数据结构:

fid authorId name ……
10025 a600456 张三 ……
10698 a600456 张三 ……

2、走读对应代码发现没毛病啊,基本逻辑代码如下:

public void parseResult() {
       String html = getHttpResponse();
       Author author = new Author();
       Document document = Jsoup.parse(html);
       String authorId = document.select("a[data-id]").attr("data-id");
       boolean isNotExisted = authorDao.checkExistedById(authorId ) == 0;//查询DB指定ID的记录数
       if(isNotExisted) {
           author.setAuthorName(name);
           author.setAuthorId(authorId );
           ……
           authorDao.insertAuthor(author);
       }

authorDao.checkExistedById(authorId ) 对应的查询SQL:

<select id="checkExistedById" resultType="Integer">
       SELECT COUNT(*)
       FROM T_GALAXY_AUTHOR
       WHERE FAUTHOR_ID
= #{authorId}
</select>

后来考虑到并发场景,在多线程环境下task之间是并行的,那么当同一时点有多个task在处理同一个作者时就可能出现重复插入,如下图:

如何解决?

既然是在异步并发场景下出现的数据状态不一致问题,那么就可以利用上一篇文章讲的synchronized关键字来处理,比如用synchronized定义上面的方法为同步方法,这样在多线程场景下,执行到该方法时就变成同步了(即同一时点只有一个线程执行该方法);

public synchronized void parseResult() {
   ……
}

或者对authorId加锁,用synchronized设置同步代码块,也是一样的。

public void parseResult() {
   ……
   String authorId = document.select("a[data-id]").attr("data-id");
   synchronized (authorId) {
       boolean isNotExisted = authorDao.checkExistedById(authorId ) == 0;//查询DB指定ID的记录数
       if(isNotExisted) {
           author.setAuthorName(name);
           author.setAuthorId(authorId );
           ……
           authorDao.insertAuthor(author);
       }
   }
}

又或者我不想再改代码了,从数据库的角度,给author表设置唯一索引,这样也可以保证记录的唯一性了,当然这些问题最好是在建表时就要考虑好,SQL:

ALTER TABLE `T_AUTHOR` ADD UNIQUE KEY UNIQ_AUTHOR_ID (`authorId`);

思考

上面的这些问题,是从开发的角度分析并提供的解决方法,考虑到实际工作中很多测试童鞋对多线程并不了解,也很少主动去学习这一部分,我希望通过这篇文章的分析和思考引起测试童鞋对多线程学习的重视(老实说学会了它你测试的深度可以深深深很多!!!)……那么反过来从测试的角度,我们应该如何发现这样的问题?
1、首先当然是要分析系统中哪些点可能会出现并发问题:
比如常见的往数据库增删改数据的操作,这个肯定得保证事务一致性,所以这些点都是需要并发验证的(你们平时有下意识的去测试这些点吗?)。

2、然后模拟并发场景:
1)对于UI界面,我们可以借助自动化测试来模拟多用户并发场景;
2)对于接口,我们可以借助jmeter来模拟并发场景或者自己直接写代码开多线程模拟并发请求等;
3)其他服务或操作,写代码开多线程模拟;

考虑到实际应用中,很多测试童鞋还不知道如何使用多线程,我就举个简单的应用例子供大家依葫芦画瓢,利用多线程去并发多次请求一个接口:

/**
* Created by alany on 2018/5/17.
*/

public class MultiThreadsDemo {
   public static void main(String[] args) throws InterruptedException {
       RunnerThread[] threads = new RunnerThread[10];
       for (int i = 0; i < threads.length; i++) {
           threads[i] = new RunnerThread("Runner"+ i);
           threads[i].start();
       }
       for (int i = 0; i < threads.length; i++) {
           threads[i].join(); //等所有子线程都执行完,主线程才停止
       }
   }
   static class RunnerThread extends Thread{
       String name;
       public RunnerThread(String name){
           this.name = name;
       }
       @Override
       public void run() {
           String url = "http://api.fixer.io/latest?base=CNY";
           //使用的是之前接口自动化的http请求框架,不清楚可以参考之前的文章
           HttpResponse response = new HttpRequest(url)
                   .setHeaders(null)
                   .setParams(null)
                   .doGet();
           try {
               String result = EntityUtils.toString(response.getEntity());
               System.out.println("Thread " + name + " response: " + result);
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
}

看下运行效果:


题外话:以后不保证更新的频率会很快,但是尽量一周至少有一篇,我一直觉得文章的质量比数量更重要,没必要为了每日一篇去凑数量,那样完全可以去其他地方转一些或一篇文章拆开几篇来发,感觉没意义……


测试开发栈

软件测试开发合并必将是趋势,不懂开发的测试、不懂测试的开发都将可能被逐渐替代,因此前瞻的技术储备和知识积累是我们以后在职场和行业脱颖而出的法宝,期望我们的经验和技术分享能让你每天都成长和进步,早日成为测试开发栈上的技术大牛~~

长按二维码/微信扫描关注


欢迎加入QQ群交流和提问:427020613

互联网测试开发一站式全栈分享平台


以上是关于一个多线程bug引发的测试思考的主要内容,如果未能解决你的问题,请参考以下文章

两个多线程笔试问题引发的思考

setTimeout(fn, 0)引发的JavaScipt线程的思考

转腾讯高级工程师:一道面试题引发的高并发性能调试思考

一个Bug事件,引发我对 LiveData 与 协程 的思考

一块抹布引发的关于测试策略的思考

单例模式线程安全问题引发的思考