在我做接口测试的工作中,有段时间负责产品的每星期活动的接口测试,需求一般比较简单,但是有一批活动很特殊,在之前的工作中都没遇到过,就是概率型业务。常见的抽奖、随机奖励、用户分组等等。都涉及到概率,接口单次返回结果并不可预期,只有经过大量统计得到的结果才具有参考性,也不具有决定性。对于这类接口的测试,当时存在着两个难点:一、如何判断业务实现结果符合预期;二、如何才算发现其中BUG。总结来说就是如何判断概率准确性。
我以一个之前负责过的🐯机的活动为例,分享一下当时的解决方案和实践经验。
需求:用户花费一定量的金币可以得到一次抽奖机会,每次抽奖有4种(可配置)类型的礼物,以及一定概率的未中奖概率(等于1-中奖概率之和),为了简化过程每种礼物的中奖概率以1%位单位。
接口:三个接口:一、抽奖接口;二、获取活动配置接口(包括各类礼物配置和信息);三、个人活动详情(个人信息、抽奖次数、获奖情况)
测试工具:Java(不唯一),通过把三个接口提供的功能封装为方法,然后通过方法调用去获取数据,进而统计得到的结果。
测试时间:一天。
两个难点的解决方案:
* 如何判断业务实现结果符合预期
业务实现结果即是大量测试统计结果,预期是后台配置的各类事件的概率。举个简单的例子,如果A事件发生概率为10%,那么在10000次测试中,发生N次A事件,就认为A事件发生的概率为10%,求N的取值范围。
首先在本次举例中,事件概率的颗粒度为1%,那么问题就转化为如何区别两个事件发生概率1%的事件。在经过一些讨论和尝试,我们采取了一个公式:如果独立事件发生概率颗粒度a,那么进行a平方的倒数次测试,事件实际发生的次数与理论值误差小于颗粒度的1/2,即认为该事件实际发生概率等于理论值。
本例中如果A时间设置的发生概率等于10%,那么进行10000次测试,如果A时间发生概率在950-1050次之间,那么A时间发生概率等于10%。
* 如何才算发现其中的BUG
解决了第一个难点,第二个难点就有了一部分答案,设置发生概率,然后进行测试,统计结果,对比数据。如果发现不符合预期,则视为BUG,当然保险起见,还是要进行足够多的测试。因为概率学告诉我们:如果进行足够多的测试,那么测试结果实际值会更接近理论值,参考投针试验。
另外一个答案就是构造不可能发生的事件和一定会发生的事件来进行足够多的测试,发现BUG。这个比较简单,比如我们把某一个事件的概率设置为0%或者100%,然后不停地做测试,如果该事件偶尔发生了一次/偶尔没有发生,那么这绝对是一个BUG。这里计算足够多的次数,依然采取了上面的公式,使用概率颗粒度平方倒数。
在实际的测试当中,由于接口测试时间比较短,执行单次测试用例的时间会很长,需要采取其他的手段来达到缩短时间的目的。当时才用了一个思路:多线程。一、使用多线程去跑不同的用例,达到整体测试时间缩短的目的;二、使用多线程跑同一个用例,达到单次用纸执行时间缩短的目的。在第二个方面,需要用到Java多线程中线程安全的知识,不然很难统计到真实的数据。
* 总结一下,这类需求其实应该直接白盒测试的。
下面分享一下当时测试代码,比较糙乱,多线程测试类代码找不到了,见谅!
import com.fission.najm.base.NajmBase; import net.sf.json.JSONObject; import org.apache.http.client.methods.HttpGet; public class Tiger extends NajmBase { public long aa = getDate().getTime(); public String url = "http://haahi.7najm.com/service/activity/v3/32"; // public String url = "http://www.7najm.com/service/activity/v3/32"; public String loginKey; // public String userId = "898542";// 中东用户 id public int pay = 4; public int freeType = 2; // public String userId = "879136";// 土耳其用户 id public static int feather;// 羽毛 public static int addFeather;// 增加羽毛数 public static int addGift;// 增加礼物数 public static int times;// 次数 public static int type;// 结果类型 public static int giftId;// 礼物 id public static int code;// 礼物 id public static JSONObject gifts = new JSONObject(); public static JSONObject types = new JSONObject(); public Tiger() { this.loginKey = loginNajm(1); } public static void main(String[] args) { Tiger tigger = new Tiger(); // tigger.refresh(); tigger.getUserInfo(); // int sss = 0; // while (times < 100) { // output(times); // if (code == 1101) // sss++; // times++; // sleep(1); // tigger.testLucky(); // // countCount(types, type); // // if (feather != 0) // // addFeather++; // // if (giftId != 0) { // // countCount(gifts, giftId); // // addGift++; // // } // } outputLine(); output(types); output(gifts); output(addFeather); output(times); // output(sss); // tigger.getLuckys(); testOver(); } public JSONObject getUserInfo() { JSONObject args = new JSONObject(); args.put("loginKey", loginKey); args.put("type", 1); HttpGet httpGet = getHttpGet(url, args); JSONObject response = getHttpResponseEntityByJson(httpGet); output(response); return response; } public JSONObject testLucky() { JSONObject args = new JSONObject(); args.put("loginKey", loginKey); args.put("pay", pay); args.put("freeType", freeType);// 是否是免费1免费 2付费 args.put("type", 2); HttpGet httpGet = getHttpGet(url, args); HttpGet httpGet = getHttpGet(url, args); JSONObject response = getHttpResponseEntityByJson(httpGet); if (response != null && response.containsKey("dataInfo")) { feather = response.getJSONObject("dataInfo").getInt("feather"); type = response.getJSONObject("dataInfo").getInt("type"); giftId = response.getJSONObject("dataInfo").getInt("giftId"); } else { feather = 0; type = 0; giftId = 0; } code = response.getInt("code"); output(response); return response; } public JSONObject getLuckys() { JSONObject args = new JSONObject(); args.put("loginKey", loginKey); args.put("type", 3); HttpGet httpGet = getHttpGet(url, args); JSONObject response = getHttpResponseEntityByJson(httpGet); return response; } public JSONObject refresh() { JSONObject args = new JSONObject(); args.put("loginKey", loginKey); args.put("type", 4); HttpGet httpGet = getHttpGet(url, args); JSONObject response = getHttpResponseEntityByJson(httpGet); return response; } }
原作者: https://mp.weixin.qq.com/s?__biz=MzU4MTE2NDEyMQ==&mid=2247483813&idx=1&sn=e57b0ab92fbef6977d5990aa8fbaabbe&scene=21#wechat_redirect
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南