测试分享之连连看游戏的实操、校验

本文为博主原创,未经许可严禁转载。
本文链接:https://blog.csdn.net/zyooooxie/article/details/117427808

最近做了连连看的需求,这是我首次测 小游戏,测得有些心虚;就来简单做个总结吧。

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

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

【以下所有内容仅为个人项目经历,如有不同,纯属正常;实际所有接口参数值 均有加密处理】

需求:整个游戏过程

游戏开局,有个3s的倒计时;倒计时结束后,前端页面会对N个图案(每个图案重复M次)做随机排列、展示,用户要将 2个实际转弯数在2次以内的相同图案消除,在S秒内完成全部消除,奖励J金币;

游戏规则 说几句:

  1. 游戏次数 每天有C次的限制;
  2. 当局游戏开始后,游戏次数-1;
  3. 奖励的金币分档,以 实际完成时间 为依据;
  4. 游戏没有暂停功能,刷新、返回都会次数-1 且 当局游戏不发金币;

实际请求 说几句:

  1. 游戏开局后,发第一次请求,传参为开局时间戳、开局图案;
  2. 成功通关后,发第二次请求,传参为 对消步骤的list 【每个元素 包含 对消的2个图案的坐标、实际对消时间戳】;
  3. 挑战失败,前端不发 第二次请求;

看不到的 说几句:

  1. 后台在收到第一次请求后,对用户游戏次数、开局时间戳、游戏图案 做校验;收到第二次请求后,对 表里记录的游戏状态、当前时间戳和每步消除时间戳、消除list中 后一步时间戳和前一步时间戳、游戏步数、游戏图案list、整个游戏时间 做校验;
  2. 后台 未做实际对消步骤 的校验。

关于上面看不到的 解释几句:

  1. 游戏奖励的金币有大作用,所以就多做了些校验,包括但不限于 游戏次数、游戏图案、游戏时间、游戏步数;
  2. 游戏对消步骤(某一步的消除:(1,4)和(2,4)能不能消),是没做校验的;校验的是 这一步的消除图案是不是一致的;

测试点+代码

前端功能

  1. 实操游戏,通关,不同时间 发 不同奖励;
  2. 游戏中 返回、刷新(属于中断游戏的行为),会次数-1;起始3s倒计内 返回、刷新(非中断游戏),不减次数;
  3. 游戏次数的校验:次数为 非0、0;
  4. 游戏的消除动效、音效、静音功能;
  5. 前端对返回值异常的处理;

后台逻辑

  1. 通关,游戏时间不同,对应不同金币;
  2. 后台收到第一次请求,校验通过后,写入表;收到第二次请求,校验通过后,将更新此前记录;
  3. Redis存的游戏次数;

后台做的校验

  1. 第一次请求:图案、开局时间戳、游戏次数;
  2. 第二次请求:消除图案、消除时间戳、消除步数;
  3. 前后两次请求:前后图案、前后时间戳;

用例+代码

接口的传参规则

想要弄明白整个游戏过程,先得清楚 接口的传参规则【第一次、第二次请求 通用此接口】

参数名		类型				是否必填				说明
uuid		String			是					唯一标识
p1			String			否					开局棋盘数据;开局时必传
startTime	Long			否					开局时间戳;开局时必传
p2			List<String>	否					游戏成功步骤数据;游戏结束并且成功后必传

细说:

p1 是开局后的游戏图案;假设有4种图案,图案设置为1234,每种出现4次,游戏时图案数量共16个;前端的游戏界面为 4*4(4行,每行4列),假设某次游戏的图案为 1234(第一行) 4321(第二行) 2341(第三行) 3214(第四行),故p1的值为 1234432123413214;

1 2 3 4
4 3 2 1
2 3 4 1
3 2 1 4

p2 是将游戏过程 消除坐标+消除时间戳的list;

根据前面假设的p1图案【上面的表格】,我们真消除的只有2个可能:第二行 第二列的3 和 第三行 第二列的3、第二行 第四列的1 和第三行 第四列的1;

假设第一步选消除1,用的坐标为24、34;若消除的时间戳为1640966400(2022-01-01 00:00:00);故在这一步消除过程,结果为24164096640034;第二步选消除3,坐标为22、32;若消除的时间戳为1640966460(2022-01-01 00:01:00);故在第二步消除过程,结果为22164096646032;依次往后,最终将8个结果(16个图案,只能消除8次) 组合为list;

第一次请求p1 的用例

图案、开局时间戳、游戏次数

  1. 游戏次数

1,当天游戏次数 默认是C次;
2,p1成功请求后,次数会-1;
3,次数为0,继续去请求,报错;

  1. 开局时间戳

1,此时间戳 < 服务器当前时间戳;【网络传输需要时间】(规则:此时间戳 最多小于 服务器时间戳 B秒)

2,此时间戳是 用户手机时间转化的;【有重大隐患:用户手机时间可随意设置,很多人会调快手机时间】(规则:此时间戳 最多大于 服务器时间戳 A 秒)

  1. 图案

1,图案是否只有4种类型;
2,每种只出现4次;

  1. 其他

1,将某次成功拿到响应的请求p1 重复发送N次,查看实际响应、表里记录;
2,并发请求p1;

第二次请求p2 的用例

消除图案、消除时间戳、消除步数

  1. 消除图案

1,不重复消除,但消除的图案是不一样的;(举例-某次游戏的消除步骤:1行1列和1行2列消除、1行3列和1行4列消除、2行1列和2行2列消除)
2,重复消除 某几个图案;(举例:1行1列和1行2列消除、1行1列和1行2列消除、1行1列和1行2列消除)
3,不重复消除,消除的图案是一致的;

  1. 消除时间戳

1,前、后时间戳:混合加减、递减、相等、递加;
2,first时间戳 和 服务器当前时间戳;
3,end时间戳 和 服务器当前时间戳;

  1. 消除步数

1,8步、非8步;

  1. 其他

1,将某次成功拿到响应的请求p2 重复发送N次,查看实际响应、表里记录;
2,并发请求p2;

前后两次请求p1、p2的 用例

前后图案、前后时间戳

  1. 前后图案

1,前后图案不对应;
2,前后图案对应,但p2消除的图案出现 重复;
3,前后图案对应,但p2消除的图案出现 不一致;

  1. 前后时间戳

1,前后时间戳 不匹配;(p1是2018年,p2是当前)
2,前后时间长匹配,但不现实;(p1、p2都是2018年,或者2025年)

  1. 其他

1,p2的步数;
2,重复请求 某正常响应的p1、p2;

代码

代码仅供参考,代码有删减、部分代码有特意修改

@File: p_c_web.py

把前端参数反推,校验实际消除图案是否相同。


def p1(data_dict: dict):
    uuid_1, start_time_1, parameters_str = data_dict['uuid'], data_dict['start'], data_dict['p1']
    Log.info(uuid_1)
    Log.info('p1 开始时间的时间戳:{}'.format(start_time_1))

    parameters_list = list(parameters_str)

    ele_list_1 = list()
    for i in range(4):
        ele = parameters_list[i * 4:(i + 1) * 4]
        Log.info(ele)
        
        for e_index, e in enumerate(ele):
            new = (e, (i + 1) * 10 + (e_index+1))
            ele_list_1.append(new)

    Log.info(ele_list_1)

    return ele_list_1, uuid_1, start_time_1


def p2(data_dict_list: dict or list):
    if isinstance(data_dict_list, dict):
        uuid_2, parameters = data_dict_list['uuid'], data_dict_list['p2']
    else:
        uuid_2 = 'zy'
        parameters = data_dict_list

    time_stamp_list_2 = list()
    ele_list_2 = list()

    for p in parameters:
        Log.debug('当前元素:{}'.format(p))

        res_str = str(p)
        now_time_stamp = res_str[2:-2]
        time_stamp_list_2.append(now_time_stamp)

        if parameters[0] != p:
            assert time_stamp_list_2[-1] > time_stamp_list_2[-2]

		new1, new2 = res_str[:2], res_str[-2:]
        Log.info('第一步的结果:{}-{}'.format(new1, new2))

        ele_list_2.append((new1, new2))

    Log.info(ele_list_2)
    return ele_list_2, uuid_2, time_stamp_list_2


def check_func(p1_ele_list: list, p1_uuid: str, p1_start_time: int, p2_ele_list: list, p2_uuid: str, p2_time_stamp_list: list):
    assert p2_uuid == p1_uuid

    assert p1_start_time < p2_time_stamp_list[0]

    p2_ele_dict = dict.fromkeys(p2_ele_list)
    Log.info('p2 dict:{}'.format(p2_ele_dict))

    p1_ele_dict = {
    
    e[1]: e[0] for e in p1_ele_list}
    Log.info('p1 dict:{}'.format(p1_ele_dict))
    
    for p2e in p2_ele_dict:
        # Log.info(p2e)
        # Log.info(p1_ele_dict[p2e[0]])
        # Log.info(p1_ele_dict[p2e[1]])
        assert p1_ele_dict[p2e[0]] == p1_ele_dict[p2e[1]]
    else:
        Log.info('消除的图案 一一对应')


def main(p1_dict: dict, p2_dict: dict):
    ele_list1, uuid1, start_time1 = p1(p1_dict)
    ele_list2, uuid2, time_stamp_list2 = p2(p2_dict)
    check_func(ele_list1, uuid1, start_time1, ele_list2, uuid2, time_stamp_list2)

@File: p_c.py

1,模拟真实游戏消除过程,发请求
2,校验 收到请求后,表记录的更新 【2次校验都是 人眼识别、人脑校验,是因为就几个字段,日志都打印出来了,我就没写 细致的方法】


def send_requests(session: str, uuid_num: str, **kwargs):
    url = "https://zyooooxie/csdn~startGame"

    data_dict = kwargs
    data_dict.update(uuid=uuid_num)
    header = {
    
    'Cookie': 'sessionId={}'.format(session)}
    res = requests.post(url, json=data_dict, verify=False, headers=header)

    # Log.info(dump.dump_all(res).decode('utf-8'))
    Log.info('实际返回值:{}'.format(res.json()))
    return res.json()['success']

def new_compose_func(p1_dict: dict, p2_dict: dict, session_id: str, new_end_time_stamp: int or tuple,
                     new_start_time_stamp: int = None, new_p2start: int = None):

    new_parameters_str = p1_dict['p1']

    # new_parameters = p1_dict['p1']
    # new_parameters_str = new_parameters[::-1]

    new_ele_list, *args = p2(p2_dict)
    Log.info('实际坐标 list:{}'.format(new_ele_list))

    # 第一次请求
    new_give_time_stamp, new_start_time_stamp = return_time_stamp(give_time_stamp=new_start_time_stamp)
    Log.info(new_give_time_stamp)
    # return_time_stamp这个方法是返回 really开始时间戳、p1的start_time_stamp 【方法 无法提供,见谅】
    
    new_uuid = return_uuid()

    res1 = send_requests(session=session_id, uuid_num=new_uuid, p1=new_parameters_str, startTime=int(new_start_time_stamp))

    if res1 is True:
        Log.info('uuid的字段值是 {}'.format(new_uuid))

        exe_sql = """
        SELECT uuid FROM t_zyooooxie WHERE user_id = 'zyooooxie' ORDER BY create_time DESC LIMIT 1;
        """
        sql_data = get_data_info(exe_sql)
        Log.info('实际表的数据:{}'.format(sql_data))


    # 第二次请求
    Log.info('really结束时间戳:{}'.format(new_end_time_stamp))

    new_time_stamp_list = list()
    start = new_give_time_stamp
    for i in range(8):
        if new_p2start is None:     # p2请求第一个时间戳和p1的不同
            abc = i + 1

        else:
            abc = i
            # Log.debug('p2请求中 第一个时间戳 == 开始时间戳')

        if type(new_end_time_stamp) == int:  # 整数 即 真实时间戳
            time_difference = (new_end_time_stamp - int(start)) // 8
            Log.debug('时间差:{}'.format(time_difference))

        else:                               # 元组内部为 时间差
            if len(new_end_time_stamp) == 1:
                time_difference = new_end_time_stamp[0]
                Log.debug('时间差:{}'.format(time_difference))

            else:
                time_difference = (random.choice(new_end_time_stamp), True)
                start += time_difference[0]

                Log.debug('时间差:{}'.format(time_difference[0]))

        if isinstance(time_difference, tuple):
            cuo = start
        else:
            cuo = abc * time_difference + int(new_give_time_stamp)
        Log.debug('当前时间戳:{}'.format(cuo))

        new_time_stamp_list.append(cuo)

    Log.info('p2的 时间戳list:{}'.format(new_time_stamp_list))

    # new_time_stamp_list[0] -= 100000
    # Log.info('p2的 时间戳list:{}'.format(new_time_stamp_list))

    p2_list = list()

    for location, time_stamp in zip(new_ele_list, new_time_stamp_list):
        Log.debug('消除的坐标为{}'.format(location))
        Log.debug("实际消除的time_stamp:{}".format(time_stamp))

        data = str(time_stamp) + str(location)
        Log.debug('第二步的结果:{}'.format(data))

        p2_list.append(data)

    # Log.info(p2_list)
    # # p2_list.append(p2_list[-1])
    # p2_list.pop()
    # p2_list.pop()
    # p2_list.pop()
    # Log.info(p2_list)

    Log.info('实际p2 last_time_stamp - p1开始时间戳 = :{}'.format(fact))

    if fact < 10:
        gc = False
    elif fact < 20:
        gc = 100
    elif fact < 30:
        gc = 200
    elif fact < 40:
        gc = 300
    else:
        gc = False

    res2 = send_requests(session=session_id, uuid_num=new_uuid, p2=p2_list)

    # redis_use()

    if res2 is True:

        Log.info('uuid的字段值是 {}'.format(new_uuid))
        Log.info('gold_coin的字段值是 {}'.format(gc if isinstance(gc, int) else '0'))
        exe_sql = """
        SELECT uuid, gold_coin FROM t_zyooooxie WHERE user_id = 'zyooooxie' ORDER BY create_time DESC LIMIT 1;
        """
        sql_data = get_data_info(exe_sql)
        Log.info('实际表的数据:{}'.format(sql_data))


def main2(dict_p1: dict, dict_p2: dict, session_id: str):

    # start_time_stamp_2 = None           # 使用当前时间戳
    start_time_stamp_2 = 1747670401000          # 2025-05-20 00:00:01
    # start_time_stamp_2 = 1526745601000          # 2018-05-20 00:00:01
    # start_time_stamp_2 = int(time.time()) * 1000 + 120000        # 当前时间+120s
    # start_time_stamp_2 = int(time.time()) * 1000 + 5000        # 当前时间 + Ns

    # end_time_stamp_2 = start_time_stamp_2 + 18000
    # end_time_stamp_2 = int(time.time()) * 1000 + 2000       	# 当前时间 + Ns
    # end_time_stamp_2 = int(time.time()) * 1000 + 120000        # 当前时间+120s
    # end_time_stamp_2 = 1526745601000          # 2018-05-20 00:00:01
    # end_time_stamp_2 = 1526918401000          # 2018-05-22 00:00:01
    # end_time_stamp_2 = 1747670401000          # 2025-05-20 00:00:01
    # end_time_stamp_2 = 1747843201000          # 2025-05-22 00:00:01
    # end_time_stamp_2 = (0,)           # 固定时间差
    end_time_stamp_2 = (900,)           # 固定时间差
    # end_time_stamp_2 = (-1000,)           # 固定时间差
    # end_time_stamp_2 = (1300, 1500, 700, 800)           # 随机时间+
    # end_time_stamp_2 = (-1200, -1300, -900, -800)         # 随机时间-
    # end_time_stamp_2 = (-1200, 1300, -900, 800)         # 随机混合
    # end_time_stamp_2 = int(time.time()) * 1000 - 12000        # 当前时间-12s
    # end_time_stamp_2 = int(time.time()) * 1000 - 120000        # 当前时间-120s

    p2start = None      # 第二个请求中 第一个元素时间戳 不等于 第一个请求的时间戳
    # p2start = 1         # 等于

    new_compose_func(dict_p1, dict_p2, session_id, end_time_stamp_2, new_start_time_stamp=start_time_stamp_2, new_p2start=p2start)

测试总结

  1. 前端的测试有疏忽:兼容性测试、异常返回值测试;
  2. 游戏暂停的功能,会使得游戏过程无限拉长,前、后端要校验的数据、字段都变得很复杂;
  3. 加密、后台校验 做得弱,有可能被人撸金币;
  4. 时间戳存表时 int转datetime,会有误差;
  5. 使用脚本 发请求要考虑实际情景(要强制等待:服务器接受请求时间和 实际参数值中的时间 是否吻合);
  6. 很多人的手机时间 会调快;

这篇博客内容就这些,实际很多东西不敢、不能说得特别细致;本文链接:https://blog.csdn.net/zyooooxie/article/details/117427808

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

猜你喜欢

转载自blog.csdn.net/zyooooxie/article/details/117427808