FunTester “双花” BUG 的测试分享

FunTester · 2020年03月30日 · 781 次阅读

“双花” 一词我是从区块链领域的听到的,查了一下资料,基本所有的引用都是基于区块链,但是今天所讲的 “双花” 不是区块链领域,而是普通的接口测试中遇到的 BUG,由于概念一致,所以采用 “双花” 一词。
双花,顾名思义,花了两次,一分钱或者交换流通的物品。下面分享一下自己在工作中遇到的一个双花的 BUG 的测试方案和原因解释。

场景:有一个兑换活动,大概金币兑换礼物,金币是整个平台流通的货币,礼物价格不等。用户登录活动页后,选择不同的礼物输入数量,点击兑换。
接口:活动接口两个:一、获取活动详情以及礼物详情;二、兑换一定数量礼物。兑换记录和消费记录以及个人物品都是老接口,不再赘述。
测试工具:Java(不唯一),把接口提供的功能封装为方法,然后通过多线程调用封装号的方法,完成多线程请求兑换接口。

解决方案:
在常规测试场景以外,利用多线程并发去测试双花 BUG。主要利用了写好的性能测试框架去并发去发送某一个 httprequestbase 对象,通过构造对应的测试数据,检查测试完成后的测试数据,对比发现是否存在双花的 BUG。
用户 A,设置用户余额 100,000,兑换价值 100 的礼物,并发 1,010 次。最终结果,用户余额为零,兑换的 1,000 个改礼物,各种记录正常。最后 10 次响应结果为用户余额不足。

在兑换接口中,业务逻辑如下:获取用户余额,判断是否足以支付礼品总价,(大于等于时),发起扣币以及记录相关封装模块功能。

BUG 描述:在完成测试时,用户获取到的礼物数量大于 1000,余额为零。最后 10 次请求,有一些是响应成功的。

BUG 复盘,在获取完用户余额和判断完总价之后,发起扣费等业务时,并没有重新校验用户余额(或者说改过程是非原子操作不安全),这样导致了最后扣费的时候,使用的用户余额是旧的数值,其他线程也尚未完成扣费,造成了用户的一份金币,被当做两份金币消费了,也就是双花。

下面是测试代码,主要用到了自己写的测试框架,把 HttpRequestBase 对象组装好之后丢到 trhead 对象里面,设置请求次数和线程数。

package com.fission.najm.activity.before.workPractise;

import com.fission.najm.base.NajmBase;
import com.fun.frame.excute.Concurrent;
import com.fun.frame.thead.RequestThread;
import net.sf.json.JSONObject;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;

public class Exchange extends NajmBase {
    public String loginKey;
    public String exchangeCode = "";
    public int balance;
    public int coin;
    public HttpRequestBase Rechargerequest;
    public HttpRequestBase exchangeRequest;


    public static void main(String[] args) {
        NajmBase base = new NajmBase();
        Exchange exchange = new Exchange(base);
        exchange.recharge();
        RequestThread requestThread = new RequestThread(exchangeRequest, 101);
        new Concurrent(requestThread,10).start();
        allOver();
    }

    /**
     * 充值
     *
     * @return
     */
    public JSONObject recharge() {
        JSONObject response = null;
        String url = "http://www.7najm.com/cash/exchangecrecharge";
        JSONObject params = new JSONObject();
        params.put("loginKey", loginKey);
        params.put("exchangeCode", exchangeCode);
        params.put("requestType", "We");
        Rechargerequest = getHttpPost(url, params);
        //response = getHttpResponseEntityByJson(Rechargerequest);
        //output(response);
        return response;
    }

    /**
     * 获取充值记录
     *
     * @return
     */
    public JSONObject getRechargeRecord() {
        JSONObject response = null;
        String url = "http://www.7najm.com/cash/getecrrecord";
        JSONObject args = new JSONObject();
        args.put("loginKey", loginKey);
        args.put("page", 1);
        args.put("pageSize", 10);
        args.put("requestType", "Web");
        HttpGet httpGet = getHttpGet(url, args);
        response = getHttpResponseEntityByJson(httpGet);
        output(response);
        return response;
    }

    /**
     * 获取渠道商余额
     *
     * @return
     */
    public JSONObject getBalance() {
        JSONObject response = null;
        String url = "http://www.7najm.com/cash/exchangebalance";
        JSONObject args = new JSONObject();
        args.put("loginKey", loginKey);
        args.put("requestType", "Web");
        HttpGet httpGet = getHttpGet(url, args);
        response = getHttpResponseEntityByJson(httpGet);
        if (response.containsKey("dataInfo"))
            balance = response.getInt("dataInfo");
        output(response);
        return response;
    }

    /**
     * 获取充值码
     *
     * @return
     */
    public JSONObject getRechargeCode() {
        JSONObject response = null;
        String url = "http://www.7najm.com/cash/getexchangecode";
        JSONObject params = new JSONObject();
        params.put("loginKey", loginKey);
        params.put("requestType", "0");
        params.put("balance", coin);
        exchangeRequest = getHttpPost(url, params);
        response = getHttpResponseEntityByJson(exchangeRequest);
        if (response.containsKey("dataInfo")) {
            exchangeCode = response.getJSONObject("dataInfo").getString("exchangeCode");
        }
        output(response);
        return response;
    }

    /**
     * 获取充值码列表
     *
     * @return
     */
    public JSONObject getCodeRecord() {
        JSONObject response = null;
        String url = "http://www.7najm.com/cash/getecrecord";
        JSONObject args = new JSONObject();
        args.put("loginKey", loginKey);
        args.put("page", 1);
        args.put("pageSize", 20);
        args.put("requestType", "Web");
        args.put("codeType", 1);
        HttpGet httpGet = getHttpGet(url, args);
        response = getHttpResponseEntityByJson(httpGet);
        output(response);
        return response;
    }
}

往期文章精选

  1. java 一行代码打印心形
  2. Linux 性能监控软件 netdata 中文汉化版
  3. 接口测试代码覆盖率(jacoco)方案分享
  4. 性能测试框架
  5. 如何在 Linux 命令行界面愉快进行性能测试
  6. 图解 HTTP 脑图
  7. 写给所有人的编程思维
  8. 测试之 JVM 命令脑图
  9. 将 json 数据格式化输出到控制台
  10. 如何测试概率型业务接口
  11. “双花” BUG 的测试分享

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册