「雙花」一詞我是從區塊鏈領域的聽到的,查了一下資料,基本全部的引用都是基於區塊鏈,可是今天所講的「雙花」不是區塊鏈領域,而是普通的接口測試中遇到的BUG,因爲概念一致,因此採用「雙花」一詞。 雙花,顧名思義,花了兩次,一分錢或者交換流通的物品。下面分享一下本身在工做中遇到的一個雙花的BUG的測試方案和緣由解釋。java
場景:有一個兌換活動,大概金幣兌換禮物,金幣是整個平臺流通的貨幣,禮物價格不等。用戶登陸活動頁後,選擇不一樣的禮物輸入數量,點擊兌換。 接口:活動接口兩個:1、獲取活動詳情以及禮物詳情;2、兌換必定數量禮物。兌換記錄和消費記錄以及我的物品都是老接口,再也不贅述。 測試工具:Java(不惟一),把接口提供的功能封裝爲方法,而後經過多線程調用封裝號的方法,完成多線程請求兌換接口。apache
解決方案: 在常規測試場景之外,利用多線程併發去測試雙花BUG。主要利用了寫好的性能測試框架去併發去發送某一個httprequestbase對象,經過構造對應的測試數據,檢查測試完成後的測試數據,對比發現是否存在雙花的BUG。 用戶A,設置用戶餘額100,000,兌換價值100的禮物,併發1,010次。最終結果,用戶餘額爲零,兌換的1,000個改禮物,各類記錄正常。最後10次響應結果爲用戶餘額不足。編程
在兌換接口中,業務邏輯以下:獲取用戶餘額,判斷是否足以支付禮品總價,(大於等於時),發起扣幣以及記錄相關封裝模塊功能。json
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; } }