测试分享之抽奖【三】

本文为博主原创,未经授权,严禁转载及使用。
本文链接:https://blog.csdn.net/zyooooxie/article/details/108440488

之前分享过2期 抽奖1:多用户抽奖、抽奖结果、积分的变动
抽奖2:中奖概率(单一奖品)、奖品库存

最近我遇到一个需求:实际某抽奖活动 发的奖品是多件;
接到需求后,想着要去校验发的奖品,和之前类似 我的想法还是大数据量地抽 。
此外,遇到一些抽奖失败的情况,有些新的想法,就来再做一次分享。

【实际这篇博客推迟发布N个月】

个人博客:https://blog.csdn.net/zyooooxie

【以下所有内容仅为个人项目经历,如有不同,纯属正常】

需求3

某抽奖活动,奖品分4类,总共40种;每次开奖,随机中奖;抽奖机会可以通过做任务获得;每天最多可参与抽奖99次;

隐藏规则:

  1. 固定第X次、第XX次、第XXX次、第XXXX次 … 会抽中 某类奖品(此类奖品共4种,此规则会随机发3种);发放以三次为一个循环;
  2. 固定第a次、第b次、第c次、第d次 … 抽中 某奖品;

对比 前2期分享中的活动,不同点在于:

  1. 当天抽奖次数多;
  2. 奖品种类多;
  3. 某奖品库存不足时,会发兜底奖品;
  4. 发奖有隐藏规则;

测试点:兜底奖品(兜底礼包)

兜底礼包:其他礼包库存不足后,会发放的礼包;一般都会把库存很足很足的作为兜底礼包;【不会出现兜底礼包也没库存的情况】

此外,兜底礼包也会作为普通奖品发放,有自己本身的中奖概率;

我的用例:

  1. 所有礼包 库存都足够,看兜底礼包本身的概率;
  2. 某礼包(概率很高) 库存即将没有,多位用户同时来抢,看实际在其 库存=0前后 发放的礼包;
  3. 某礼包 库存已经为0了,后续再发放此礼包时 会走兜底礼包,看此时兜底礼包的概率;

测试点:抽奖失败

前端用例 看展示

  1. 无抽奖次数,Fiddler改 查询抽奖次数的接口返回值:有M次抽奖次数;再去抽奖,看前端;
  2. 有抽奖次数,正常抽奖,Fiddler改 抽奖的接口返回值:抽奖失败;看前端;

后台用例 看数据

  1. 抽奖次数不足;
  2. 并发抽奖、重复请求;
  3. 超过当天参与次数;
  4. 发奖失败1:奖品配置得 有问题;
  5. 发奖失败2:调用发奖系统 超时;
  6. 特殊情况:这个需求,后台做了防并发,大批量抽奖时 接口有时返回的是 “errorMessage”:“系统异常”

后三条用例 必须留意的是 抽奖次数是否扣除+是否有奖品发放。

测试点:中奖概率-多个奖品

前面说了本期活动的不同点,细说下 我想要 脚本实现的:要做抽奖后 剩余次数的校验;要做抽奖后 发放奖品的校验;要做某奖品中奖概率的校验;隐藏规则的校验;

【脚本思路:准备用户、清理相关数据、增加抽奖次数、抽奖、获取抽奖结果、检查抽奖结果、统计抽奖结果、得出概率】

实际执行:

  1. 设置此次抽奖的用户数;
  2. 设置抽奖的次数;
  3. 【for循环】每位用户 清数据,加抽奖次数,登录,抽奖、检查抽奖结果、统计当前中奖信息;
  4. 统计所有用户的数据,和实际配置做对比,得出结论;

four_card_times, ty_times 是隐藏规则1 和 隐藏规则2;

(代码有删改)

"""
@blog: https://blog.csdn.net/zyooooxie
"""


class NewYear(object):

    def __init__(self, txt_read_rows: int, txt_skip_row: int):
        """
        TXT文件读取的数量、跳过的数量
        :param txt_read_rows:
        :param txt_skip_row:
        """
        self.__txt_read = txt_read_rows
        self.__txt_skip = txt_skip_row
        Log.info('txt_read:{};txt_skip:{}'.format(self.__txt_read, self.__txt_skip))

    @property
    def use_rows(self):
        """
        返回__txt_read;设置成属性,方便子类继承
        :return:
        """
        return self.__txt_read

    def get_user_phone_nickname_list(self):
        """
        获取TXT文件的数据,返回list:userId、phone
        :return:
        """
        user_id_phone = read_txt_user(nrows=self.__txt_read, skiprows=self.__txt_skip)
        userId_mobile_nickName = list(map(lambda x: (x[0], x[1], '****'.join([str(x[1])[:3], str(x[1])[-4:]])), user_id_phone))

        return userId_mobile_nickName

    @staticmethod
    def get_session_id(user_id: str, user_phone: str):
        """
        获取session
        :param user_id:
        :param user_phone:
        :return:
        """
        session, user = get_session(user_phone)
        assert user_id == user                  # 部分手机号注销过
        Log.info('user_id: {} ,user_session: {}'.format(user, session))
        return session

    @staticmethod
    def send_request(url: str, session: str, params_dict: dict, add_header: dict = None):
        """
        发请求
        :param url:
        :param session:
        :param params_dict:
        :param add_header:
        :return:
        """
        header = {'Cookie': 'sessionId={}'.format(session), 'Content-Type': 'application/json;charset=utf-8'}

        if add_header is not None:
            header.update(add_header)

        res = requests.post(url, json=params_dict, headers=header, verify=False)
        res_dict = res.json()

        Log.info(res_dict)

        return res_dict

    @staticmethod
    def join_url(url: str):
        """
        URL拼接
        :param url:
        :return:
        """
        if url.find('zyooooxie') != -1:

            return url

        base_url = 'https://blog.csdn.net/zyooooxie/commonPost/'
        url = urljoin(base_url, url)

        return url

    def check_draw_results(self, response_dict: dict, draw_successfully_time: str, statistics_list_dict: list or dict,
                           sum_dict: dict):
        """
        检查抽奖结果
        :param response_dict:res.json()
        :param draw_successfully_time:成功请求的次数
        :param statistics_list_dict:推荐使用dict;统计结果dict-不包含 隐藏规则的那些结果
        :param sum_dict:统计结果dict-包含全部结果
        :return:
        """
        # 读取抽奖请求的响应值;对隐藏规则的2种 做校验;
        # 其他普通抽奖的 计入statistics_list_dict;
        # 所有抽奖的 计入sum_dict;

        d1 = response_dict.get('obj')[0]

        # 隐藏规则-直接写死
        four_card_times, ty_times = ["1","14","18","38"], ["11","28","42"]

        if draw_successfully_time in four_card_times:

            self.update_result_dict(sum_dict, d1['productName'])

        elif draw_successfully_time in ty_times:
            assert d1['productName'] == 'TY'

            self.update_result_dict(sum_dict, d1['productName'])

        else:
            key_ = d1['productName']
            self.update_result_dict(sum_dict, key_)

            if isinstance(statistics_list_dict, list):
                # 每个类型为一个dict
                C_dict, P_dict, W_dict, XN_dict = statistics_list_dict

                if d1['productType'] == 'P':              # P
                    self.update_result_dict(P_dict, d1['productName'])

                elif d1['productType'] == 'XN':              # XN
                    self.update_result_dict(XN_dict, d1['productName'])
					
                elif d1['productType'] == 'W':              # W
                    self.update_result_dict(W_dict, d1['productName'])
					
                elif d1['productType'] == 'C':              # C
                    self.update_result_dict(C_dict, d1['productName'])
					
                else:
                    raise Exception('传参有误:{}'.format(d1['productType']))

            elif isinstance(statistics_list_dict, dict):
                # 不区分类型,直接写入一起
                self.update_result_dict(statistics_list_dict, key_)
				
            else:
                raise Exception('statistics_list_dict传参有误:{}'.format(statistics_list_dict))

    @staticmethod
    def update_result_dict(test_dict: dict, test_key: str):
        """
        更新 统计结果dict
        :param test_dict:
        :param test_key:
        :return:
        """
        # key存在,value的值+1;key不存在,value设置为1;

        if test_dict.get(test_key) is None:
            test_dict.update({test_key: 1})
        else:
            test_dict.update({test_key: 1 + test_dict[test_key]})

    def draw(self, draw_num: int, phone: str, user_id: str = 'zyooooxie',
             session: str = None, db: Connection = None, cur: Cursor = None, every_inspect: str = None):
        """
        抽奖
        :param draw_num:【特殊设计】为1时,不去请求抽奖次数接口,抽完直接 返回session;
        :param phone:
        :param user_id:
        :param session:
        :param db:
        :param cur:
        :param every_inspect:是否每次抽奖都做 抽奖次数+发放的奖品 校验;非None 校验;默认 不校验;
        :return:
        """
        # 清除参与的数据;删除、设置缓存:抽奖次数 random.randint(95, 98);
        # 获取用户的总抽奖次数;
        # 【for循环】抽奖:发请求、检查结果、统计中奖信息;
        # 响应异常情形:抽奖失败、异常返回值;

        draw_url = 'zyooooxie/draw'

        if db is None:
            ld_db, ld_cur = connect_db(zy_db='yes')
        else:
            ld_db, ld_cur = db, cur

        self.delete_data(user_id, dd_db_info=ld_db, dd_cur_info=ld_cur)
        test_kn_redis1(user_id)

        if session is None:
            session = self.get_session_id(user_id, phone)

        # 特殊设计:=1时 不获取抽奖总次数
        if draw_num != 1:
            remain_times = self.get_user_draw_times(session=session)
            Log.debug('可抽次数:{}'.format(remain_times))
            Log.info('准备抽{}次'.format(draw_num))

        else:
            remain_times = None

        four_card_data = list()
		
		# 隐藏规则-直接写死
        four_card_times = ["1","14","18","28"]

        # statistics_list_dict = [dict() for _ in range(4)]            # 4种 奖品,分4个dict 来统计
        statistics_list_dict = dict()       # 统计非隐藏规则的奖品

        sum_dict = dict()                   # 统计所有奖品

        error_time = draw_num
        success_time = 0
        for n in range(draw_num):

            try:
                res_dict = self.send_request(self.join_url(draw_url), session=session, params_dict={})

                if res_dict.get('success') is False:
                    continue

                success_time = success_time + 1
                # Log.debug('{}:Response success'.format(success_time))

                self.check_draw_results(res_dict, str(success_time), statistics_list_dict, sum_dict=sum_dict)

                # if n >= 3 + error_time:          # 响应异常的请求后 打印后3次的抽奖结果
                #     Log.debug(statistics_list_dict)

                if str(success_time) in four_card_times:
                    Log.info('four_card_times:当前次数为 {}'.format(success_time))
                    four_card_data.append(res_dict.get('obj')[0]['productName'])
                    Log.info(four_card_data)

                    if len(four_card_data) <= 3:               # 前3次 不可重复
                        assert len(set(four_card_data)) == len(four_card_data)

                    else:
                        assert len(set(four_card_data)) == 3    # 只3种

                        self.check_four_card(four_card_data)

                if every_inspect is not None:
                    Log.info('请求成功,校验:{}'.format(n))

                    self.result_check(success_time=success_time, user_id=user_id, statistics_dict=sum_dict,
                                      session=session, error_time=error_time, draw_num=draw_num,
                                      initial_times=remain_times, db=ld_db, cur=ld_cur)

            except (JSONDecodeError, TypeError) as e:

                Log.info('响应异常:{}'.format(e.args))
                Log.info(traceback.format_exc())

                error_time = n
                time.sleep(12)          # 异常响应后,强制等待12s

                break           # break是 舍弃掉此次的抽奖结果【无法确定‘失败’这次是否发奖品】 + 此账号停止抽奖

            # time.sleep(0.1)

        # 特殊设计:=1时 返回session
        if draw_num == 1:
            return session

        # 2种情形:无异常到抽完draw_num次,结束时 总校验 + 异常抽奖,break后 校验
        Log.info('用户:{} 抽奖结束,开始校验'.format(user_id))
        self.result_check(success_time=success_time, user_id=user_id, statistics_dict=sum_dict,
                          session=session, error_time=error_time, draw_num=draw_num, initial_times=remain_times,
                          db=ld_db, cur=ld_cur)

        # statistics_list_dict 若是list,改为dict
        if isinstance(statistics_list_dict, list):
            statistics_dict = reduce(lambda x, y: {**x, **y}, statistics_list_dict)
        else:
            statistics_dict = statistics_list_dict

        phone_user_id = phone if phone is not None else user_id
        Log.info('{} 统计的奖品结果:{}'.format(phone_user_id, statistics_dict))

        all_times = sum(statistics_dict.values())
        #  隐藏规则 不计入
        Log.info('{} 统计的抽奖次数:{}'.format(phone_user_id, all_times))

        res_dict = {k: round(v / all_times, 3) for k, v in statistics_dict.items()}
        Log.info('{} 统计的奖品概率:{}'.format(phone_user_id, res_dict))
		
        if db is None:
            ld_cur.close()
            ld_db.close()

        return statistics_dict

    def result_check(self, success_time: int, user_id: str, statistics_dict: dict, session: str, error_time: int,
                     draw_num: int, initial_times: int, db: Connection, cur: Cursor):
        """
        检查中奖结果
        :param success_time:抽奖成功次数
        :param user_id:
        :param statistics_dict:统计dict-包含全部结果
        :param session:
        :param error_time:错误次数
        :param draw_num:
        :param initial_times:初始抽奖总次数
        :param db:
        :param cur:
        :return:
        """

        sql = """
        SELECT C_ID, D_VALUE, PRODUCT_TYPE FROM table_award WHERE USER_ID = '{}' ORDER BY ID ASC ;
        """.format(user_id)

        # 获取表里、此活动的记录
        data_tuple = fetchall_data_no_close(sql, db, cur)
        Log.debug(data_tuple)

        Log.info(statistics_dict)

        remain_times2 = self.get_user_draw_times(session=session)
        Log.info('剩余抽奖次数:{}'.format(remain_times2))

        if error_time == draw_num:          # 未出现 异常返回值
            assert initial_times == remain_times2 + success_time
            assert len(data_tuple) == success_time

            award_data = data_tuple

        # 后台做了些防并发的处理,所以某次请求 响应体异常,
        # 实际在后面补发了礼包+扣次数 or 实际没有处理此次失败。
        else:

            if len(data_tuple) == success_time:             # 表里的记录数 = 成功发奖的次数
                Log.info('没有补发')
                assert initial_times == remain_times2 + success_time

                award_data = data_tuple

            else:
                assert len(data_tuple) == success_time + 1          # 表里的记录数 = 成功发奖的次数 + 1
                Log.info('补发了')
                assert initial_times == remain_times2 + success_time + 1

                Log.info('比statistics_dict多的:{}'.format(data_tuple[-1]))

                award_data = data_tuple[:-1]

        table_result = self.award_packet_change(award_data)
        eq_res = operator.eq(table_result, statistics_dict)

        if eq_res is False:
            Log.error('对比失败:{} 和 {}'.format(table_result, statistics_dict))
            raise

        Log.info('抽奖次数、发奖奖品 校验通过')

    def award_packet_change(self, award_packet_data: tuple):
        """
        表的数据 转换成 统计dict
        :param award_packet_data:
        :return:
        """
        # COMMODITY_ID、DETAIL_VALUE、PRODUCT_TYPE
        
        table_dict = {('XN1111', '11', 'XN'): '奖品名a',
						('C2222', '', 'C'): '奖品名b',
						('P3333',33, 'P'): '奖品名c',
						('W4444', '', 'W'): '奖品名d'
                      }             # 40种奖品
        XN_P = list()
        C_W = list()

        for d in award_packet_data:
            if d[-1] in ('XN', 'P'):
                XN_P.append(d)
            else:
                C_W.append(d)

        # 统计 奖品的出现次数
        apd_counter1 = Counter(XN_P)
        apd_counter2 = Counter([(apd[0], '', apd[-1]) for apd in C_W])
        # COMMODITY_ID、PRODUCT_TYPE保留;DETAIL_VALUE 改为''

        apd_list1 = list(apd_counter1.items())
        apd_list2 = list(apd_counter2.items())

        award_packet_result = dict()

        for al in apd_list1 + apd_list2:
		
            if al[0] in table_dict:

                award_packet_result.update({table_dict.get(al[0]): al[1]})
            else:
                Log.error('发的奖品没找到:{}'.format(al))
                raise

        Log.debug('表里的数据统计后:{}'.format(award_packet_result))

        return award_packet_result

    def get_user_draw_times(self, session: str):
        """
        获取抽奖次数
        :param session:
        :return:
        """
        url = 'zyooooxie/drawInfo'

        res_dict = self.send_request(self.join_url(url), session=session, params_dict={})

        if res_dict.get('obj') is not None:
            rt = res_dict.get('obj').get('Times')
            return rt

        else:
            Log.error('返回值有异常')
            raise

    @staticmethod
    def check_four_card(test_list):
        """
        drawFourCardTimes:3种类型 固定顺序循环
        :param test_list:
        :return:
        """
        assert test_list[-1] == test_list[len(test_list) - 1 - 3]

    def draw_many_users(self, draw_num: int, every_inspect: str = None):
        """
        许多用户抽奖
        :param draw_num:
        :param every_inspect:
        :return:
        """
        all_statistics_list = list()
        db, cur = connect_db(zy_db='yes')

        for d in self.get_user_phone_nickname_list():
            sd = self.draw(draw_num, user_id=d[0], phone=str(d[1]), db=db, cur=cur, every_inspect=every_inspect)

            all_statistics_list.append(sd)

        else:
            Log.debug('总体结果:{}'.format(all_statistics_list))
            cur.close()
            db.close()

        if draw_num == 1:
            # all_statistics_list 统计的是 session
            return

        # all_statistics_list是 每个用户抽奖结果dict 的 list
        all_statistics_dict = reduce(self.update_value, all_statistics_list)

        all_times = sum(all_statistics_dict.values())
        all_result = {k: round(v / all_times, 3) * 100 for k, v in all_statistics_dict.items()}

        Log.info('最后的统计结果:{}'.format(all_statistics_dict))
        Log.info('统计的次数:{}'.format(all_times))
        Log.info('计算的概率:{}'.format(all_result))

    @staticmethod
    def update_value(test_dict1: dict, test_dict2: dict):
        """
        dict1、dict2的key相同、value相加
        :param test_dict1:
        :param test_dict2:
        :return:
        """
        temp = dict()

        for k in test_dict1.keys() | test_dict2.keys():
            temp[k] = sum(d.get(k, 0) for d in (test_dict1, test_dict2))

        return temp
        


if __name__ == '__main__':
    pass

    ny = NewYear(10, random.randint(5, 88888))
	# test_kn_redis1() 设置 抽奖次数为 random.randint(95, 99)
    ny.draw_many_users(draw_num=random.randint(91, 97))

看下执行结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上图的马赛克 没办法,不想违规。

3张截图 出现的情形是:

  1. 某用户正常请求N次抽奖接口,无异常返回值;统计其的数据;统计本轮执行的所有用户的数据;
  2. 某用户请求抽奖接口遇到异常返回值,强制等待12s后,去表里查询此用户的发奖数据,对比发现-没有补发;break;统计其的数据;统计本轮执行的所有用户的数据;
  3. 某用户请求抽奖接口遇到异常返回值,强制等待12s后,去表里查询此用户的发奖数据,对比发现-补发了;break;统计其的数据【补发的不计入】;继续下一个用户;

本文链接:https://blog.csdn.net/zyooooxie/article/details/108440488

交流技术 欢迎+QQ 153132336 zy
个人博客 https://blog.csdn.net/zyooooxie

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值