测试开发栈

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

2018-06-04  本文已影响14人  测试开发栈

背景

今天收到测试的一个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";
            HttpResponse response = new HttpRequest(url) //使用的是之前接口自动化的http请求框架,不清楚可以参考之前的文章
                    .setHeaders(null)
                    .setParams(null)
                    .doGet();
            try {
                String result = EntityUtils.toString(response.getEntity());
                System.out.println("Thread " + name + " response: " + result);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

看下运行效果:


原文来自下方公众号,转载请联系作者,并务必保留出处。
想第一时间看到更多原创技术好文和资料,请关注公众号:测试开发栈

上一篇 下一篇

猜你喜欢

热点阅读