基于excel实现接口自动化测试

简介

本文档介绍如何使用excel管理接口测试用例并一键执行的实现方式,其中包括 python 读写excel, request库的基本操作,接口用例的设计
源码下载传送门>>APIAtuoTest

接口用例设计

接口用例
用例字段描述

  • 被依赖表达式: 示例 (3,headers,data,response,file),第一位填写被依赖用例id,若依赖多条数据则可在后加(headers,data,response,file),字段之间使用英文"逗号"隔开,加入 headers 则表示 用例id为 3 的用例需要本条用例的 headers 数据,其它以此类推. 填写了 被依赖表达式,本条用例执行完成会自动根据 被依赖表达式 组装本条用例数据写入到被依赖用例的 需要依赖数据 中
# 组装本条用例后的数据
data = {
    'data': {'ip': '112.112.11.11', 'key': 'key'},
    'response': {
        'resultcode': '200',
        'reason': '查询成功',
        'result': {'Country': '中国', 'Province': '云南省', 'City': '昆明市', 'Isp': '电信'},
        'error_code': 0
    },
    'file': 'timg.txt'}
  • 需依赖表达式: 例如取值上方 data 中的 ip 和 key 数据,则表达示例为(var_ip=data.ip,var_key=data.key),var_ip 是变量名称, data.ip 则是数据所在的路径, 多层 dict 则通过 英文"点"把key相连,最终取到数据,若需取值多个值,则每个值之间使用英文"逗号"隔开. 取值成功后会自动写入到本条用例的 需依赖数据取值结果中
# 取值表达式
var_ip=data.ip,var_key=data.key
# 被取值数据
data = {
    'data': {'ip': '112.112.11.11', 'key': 'key'},
    'response': {
        'resultcode': '200',
        'reason': '查询成功',
        'result': {'Country': '中国', 'Province': '云南省', 'City': '昆明市', 'Isp': '电信'},
        'error_code': 0
    },
    'file': 'timg.txt'}
# 取值结果
{'var_ip': '112.112.11.11', 'var_key': 'key'}
  • 检查表达式: 目前支持 equal,notequal,in,notin 判断,若需其它判断可自行添加,此表达式分为(resultcode equal 200) 三个部分, 左侧部分为 取值表达式,中间为 判断方式 ,右侧为预期结果,左侧填写的取值表达式会自动去检查数据中把数据匹配出来, 实际就是 200 == 200,若需多个检查点则每个检查点之间使用英文"逗号"隔开.
# 检查表达式
resultcode equal 200,result.Isp equal 电信
# 检查数据
{"resultcode":"200","reason":"查询成功","result":{"Country":"中国","Province":"云南省","City":"昆明市","Isp":"电信"},"error_code":0}
# 检查结果
检查点>实际结果:200等于预期结果:200 pass 
检查点>实际结果:电信等于预期结果:电信 pass 

接口用例含有字段及描述
编号 : int(唯一) 用例id
模块 : str 用例所属模块描述,例如 用户管理之类的一级功能
标题 : str 用例简单描述,例如 新增用户之类的二级功能
测试点 : str 用例测试功能,例如 正确用户数据正常新增用户成功
级别 : str 用例重要程度,范围有(P0,P1,P2,P3),这里依照公司对用例级别的划分
执行顺序 : int(唯一) 这里可以设置用例的执行顺序, 1 为最优先执行,上不封顶
是否执行 : str 用例是否需要执行,范围有(Y,y,N,n),大Y小y代表该条用例需要执行,空或者其它都代表不执行
被依赖用例 : str 本条用例是否被其它用例依赖了测试数据
需依赖数据 : str 本条用例需要依赖的数据
依赖数据取值表达式 : str 用例执行会根据此表达式,自动去匹配 需依赖数据中的内容
依赖数据取值结果 : str 根据需依赖数据取值表达式 取值后自动存入,存入示例 {‘var_ip’: ‘112.112.11.11’, ‘var_key’: ‘key’}
接口地址 : str 接口地址
请求头 : str 请求头
请求方式 : get,post,若需要其它可自行添加
请求数据 : str 请求数据
文件名称 : str 此处添加测试框架中 test_data 目录下的 文件全名,若是批量文件上传接口,则可以录入多个文件全名以英文"逗号"隔开
返回结果 : str 用例执行后的返回
检查点 : str 根据 检查表达式 自动匹配 返回结果中的数据,并把检查结果自动写入 执行结果中
执行结果 : str 检查点匹配后的结果信息,包括每个检查点是否通过 和 若用例中的任何报错信息也会 自动录入
开始时间 : str 用例执行开始时间
结束时间 : str 用例执行结束时间
消耗时间 : str 本条用例执行总共花费时间
执行人 : 用例编写人,这个值可在 setting 文件中配置

Excel的操作

封装关于 python 对excel 的基本操作,包括 返回指定行/每行打印/每列打印/读指定位置/写指定位置/按列读/按列写/按行读/按行写等等,下面放了一些实现操作的类方法

  • 替换数据
    # 替换指定位置的值
    def replace_data(self, row, cell, data):
        if self.mode == 1:
            # print(row,cell,data)
            self.handle_sheet.write(int(row), int(cell), data)
            self.handle.save(self.excel_filename)
            return "修改行 %s 列 %s ,修改数据为 %s" % (str(row), str(cell), str(data))
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'

    # 替换一行
    def replace_nrows(self, data, nrows_number):
        if self.mode == 1:
            for i, item in enumerate(data):
                self.handle_sheet.write(nrows_number - 1, i, item)
            self.handle.save(self.excel_filename)
            return nrows_number, letter(i + 1)
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'

    # 替换一列
    def replace_ncols(self, data, ncols_number):
        if self.mode == 1:
            for i, item in enumerate(data):
                self.handle_sheet.write(i, ncols_number - 1, item)
            self.handle.save(self.excel_filename)
            return i + 1, letter(ncols_number)
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'
  • 删除数据
    # 删除全部
    def del_alldata(self):
        if self.mode == 1:
            replace_number = 0
            for i in range(self.sheet.ncols):
                for n in range(self.sheet.nrows):
                    self.handle_sheet.write(n, i, '')
                    replace_number += 1
            self.handle.save(self.excel_filename)
            return replace_number
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'

    # 删除行
    def del_nrows(self, nrows_number):
        if self.mode == 1:
            replace_number = 0
            for i in range(self.sheet.ncols):
                self.handle_sheet.write(nrows_number - 1, i, '')
                replace_number += 1
            self.handle.save(self.excel_filename)
            return replace_number
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'

    # 删除列
    def del_ncols(self, ncols_number):
        if self.mode == 1:
            replace_number = 0
            for n in range(self.sheet.nrows):
                self.handle_sheet.write(n, ncols_number - 1, '')
                replace_number += 1
            self.handle.save(self.excel_filename)
            return replace_number
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'

    # 删除某个位置
    def del_data(self, nrows_number, ncols_number):
        if self.mode == 1:
            self.handle_sheet.write(nrows_number + 1, ncols_number + 1, '')
            self.handle.save(self.excel_filename)
            return self.sheet.cell(ncols_number + 1, nrows_number + 1).value
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'

    # 删除指定数据
    def del_value(self, data):
        if self.mode == 1:
            replace_number = 0
            for i in range(self.sheet.ncols):
                for n in range(self.sheet.nrows):
                    if data == self.sheet.cell(n, i).value:
                        self.handle_sheet.write(n, i, '')
                        replace_number += 1
            self.handle.save(self.excel_filename)
            return replace_number
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'
  • 读数据
    # 读取指定位置的数据 return data
    def xlsred(self, nrows_number, ncols_number):
        if self.mode == 1:
            str = self.sheet.cell(nrows_number, ncols_number).value
            print("read data : ", str)

            print("Data-ncols: ", nrows_number)

            print("Data-number: ", ncols_number)

            return str
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'
     # 每列打印 return data
    def xlsread_allncols(self):
        if self.mode == 1:
            data_list = []
            for i in range(self.sheet.ncols):
                data = []
                for n in range(self.sheet.nrows):
                    if self.sheet.cell(n, i).value != '':
                        data.append(self.sheet.cell(n, i).value)
                data_list.append(data)
            return data_list
        else:
            print(self.excel_filename + u" 不存在,无法进行操作!!!")

            return 'error: not file.'

接口框架设计

目前框架功能 : 读写excel用例并自动执行后把本身测试结果写入, 后续自行可以 分析测试结果生成 各种版本的测试报告 和 自行推送测试结果至各个平台,如 Email,企业微信,钉钉,微信,短信,平台机器人查看相关产品的接口文档

框架目录

  • APIAutoTest
    • config
      • settings.py
    • doc
    • lib
      • opt_excel.py
      • opt_expression.py
      • opt_test_data.py
      • request_client.py
    • result
    • test_case
      • TestCase.xls
    • test_data
    • tool
      • my_time.py
    • main.py

使用说明

1.环境 :python 3.x ,需依赖 xlrd xlwt xlutils
2.依照 TestCase.xls 用例格式完成用例接口用例编写
3.python main.py 执行测试

代码片段

# opt_expression.py
def write_rely_case_data(now_case_id, data_str):
    """
    该条用例会被谁依赖并把需要依赖的内容写入被依赖用例的 依赖用例数据 中
    :param data_str: 依赖表达式  2,headers,data,response,file
    :param now_case_id: 当前测试用例id
    :return: 把 被依赖的测试数据 写入 需依赖的用例  依赖用例数据 中
    """
    temp_dict = {}
    # 如果传入的 依赖用例 为空,则不需要组装 需依赖数据 并 写入需依赖的用例中
    if data_str == "":
        return False
    # 组装 需依赖 的测试数据
    data_list = data_str.split(",")
    temp_dict["rely_case_id"] = data_list[0]
    temp_dict["all_data"] = {}
    for item in range(1, len(data_list)):
        if "headers" == data_list[item]:
            case_headers = opt_test_data.read_headers(now_case_id)
            if case_headers:
                temp_dict["all_data"]["headers"] = eval(case_headers)
        elif "data" == data_list[item]:
            case_data = opt_test_data.read_data(now_case_id)
            if case_data:
                temp_dict["all_data"]["data"] = eval(case_data)
        elif "response" == data_list[item]:
            case_response = opt_test_data.read_response(now_case_id)
            if case_response:
                temp_dict["all_data"]["response"] = eval(case_response)
        elif "file" == data_list[item]:
            case_filename = opt_test_data.read_files_name(now_case_id)
            temp_dict["all_data"]["file"] = case_filename
    # 把组装好的 需依赖数据 写入 需依赖用例的 依赖用例数据 中
    opt_test_data.write_rely_on_case_data(int(temp_dict["rely_case_id"]), str(temp_dict["all_data"]))

    return temp_dict


def dict_str(test_data_str):
    """
    将从测试用例中读取出来的字符串转换成字典
    :param test_data_str: 读取出来的字符串
    :return: 字典
    """
    if test_data_str:
        return eval(test_data_str)
    return None


def get_expression_data(pattern, data_dict):
    """
    根据传入表达式pattern匹配数据data中的数据
    :param pattern: 表达式
    :param data_dict: 匹配数据
    :return: dict
    {'var1': 'headers', 'var2': 'response'}
    """
    var_dict = {}
    pattern_list = pattern.split(",")
    # print(pattern_list)
    for item in pattern_list:
        temp_list = item.split("=")
        # 获取变量名称
        temp_var = temp_list[0]
        # 分析表达式
        var_dict[temp_var] = _dict_path_value(temp_list[1], data_dict)
    return var_dict


def replace_data(var_data, data_str):
    """
    根据传入的 var_data, 修改 data 中的字符串
    :param var_data: 表达式从依赖用例数据中取出的变量字典
    :param data_str: 需要替换的对象字符串(从用例中取出的请求头等)
    :return: 修改过的 请求数据 dict
    """
    for item in var_data:
        data_str = data_str.replace(item, var_data[item])
    return data_str


def assert_data(case_id, assert_str):
    """
    根据assert_str的断言,判断 实际结果 和 预期结果 之间的关系
    :param assert_str: 断言 "headers.key1 equal key1_123"
    :param case_id: 测试用例id
    :return: pass fail
    """
    result_str = ""
    actual = eval(opt_test_data.read_response(case_id))
    assert_list = assert_str.split(",")
    for item in assert_list:
        my_assert = re.findall(r"\s\S+\s", item)[0].strip()
        pattern_left = re.findall(r"\S+\s", item)[0].strip()
        pattern_right = re.findall(r"\s\S+", item)[1].strip()

        if "equal" == my_assert:
            data_left = _dict_path_value(pattern_left, actual)
            data_right = pattern_right
            if data_left == data_right:
                result_str += "检查点>实际结果:%s等于预期结果:%s pass \n" % (data_left, data_right)
            else:
                result_str += "检查点>实际结果:%s不等于预期结果:%s fail \n" % (data_left, data_right)
        elif "notequal" == my_assert:
            data_left = _dict_path_value(pattern_left, actual)
            data_right = pattern_right
            if data_left != data_right:
                result_str += "检查点>实际结果:%s等于预期结果:%s pass \n" % (data_left, data_right)
            else:
                result_str += "检查点>实际结果:%s不等于预期结果:%s fail \n" % (data_left, data_right)
        elif "in" == my_assert:
            data_left = _dict_path_value(pattern_left, actual)
            data_right = pattern_right
            if data_left in data_right:
                result_str += "检查点>实际结果:%s等于预期结果:%s pass \n" % (data_left, data_right)
            else:
                result_str += "检查点>实际结果:%s不等于预期结果:%s fail \n" % (data_left, data_right)
        elif "notin" == my_assert:
            data_left = _dict_path_value(pattern_left, actual)
            data_right = pattern_right
            if data_left not in data_right:
                result_str += "检查点>实际结果:%s等于预期结果:%s pass \n" % (data_left, data_right)
            else:
                result_str += "检查点>实际结果:%s不等于预期结果:%s fail \n" % (data_left, data_right)
    return result_str


def _dict_path_value(path, data):
    """
    根据path获取dict_data里面的值
    """
    num = 1
    pahts = path.split(".")
    mydict = {}
    for p in pahts:
        if num == 1:
            mydict = data[p + ""]
            num = num + 1
        else:
            mydict = mydict[p + ""]
            num = num + 1
    return mydict
# opt_test_data.py
def read_is_run(case_id):
    """读取该条用例的 是否执行 信息"""
    is_run = remove_str_all_break(get_case_row_data(case_id)[6])
    if len(is_run) <= 0:
        return None
    return is_run


def write_is_run(case_id, content):
    """修改该条用例的 是否执行 信息"""
    case_row_number = get_case_row_number(case_id)
    test_case_data().replace_data(case_row_number, 6, content)


def read_rely_on_case(case_id):
    """读取该条用例的 被依赖用例/依赖内容 信息"""
    rely_on_case = remove_str_all_break(get_case_row_data(case_id)[7])
    if len(rely_on_case) <= 0:
        return None
    return rely_on_case


def write_rely_on_case(case_id, content):
    """修改该条用例的 依赖用例 信息"""
    case_row_number = get_case_row_number(case_id)
    test_case_data().replace_data(case_row_number, 7, content)


def read_rely_on_case_data(case_id):
    """读取该条用例的 需依赖用例数据 信息"""
    case_data = remove_str_all_break(get_case_row_data(case_id)[8])
    if len(case_data) <= 0:
        return None
    return case_data


def write_rely_on_case_data(case_id, content):
    """修改该条用例的 需依赖用例数据 信息"""
    case_row_number = get_case_row_number(case_id)
    test_case_data().replace_data(case_row_number, 8, content)


def read_rely_on_case_expression(case_id):
    """读取该条用例的 需依赖用例取值表达式 信息"""
    case_expression = remove_str_all_break(get_case_row_data(case_id)[9])
    if len(case_expression) <= 0:
        return None
    return case_expression


def write_rely_on_case_expression(case_id, content):
    """修改该条用例的 需依赖用例取值表达式 信息"""
    case_row_number = get_case_row_number(case_id)
    test_case_data().replace_data(case_row_number, 9, content)


def read_rely_on_case_expression_result(case_id):
    """读取该条用例的 需依赖用例取值表达式取值结果 信息"""
    expression_result = remove_str_all_break(get_case_row_data(case_id)[10])
    if len(expression_result) <= 0:
        return None
    return expression_result


def write_rely_on_case_expression_result(case_id, content):
    """修改该条用例的 需依赖用例取值表达式取值结果 信息"""
    case_row_number = get_case_row_number(case_id)
    test_case_data().replace_data(case_row_number, 10, content)


def read_api_path(case_id):
    """读取该条用例的 api地址 信息"""
    api_path = remove_str_all_break(get_case_row_data(case_id)[11])
    if len(api_path) <= 0:
        return None
    return api_path


def write_api_path(case_id, content):
    """修改该条用例的 api地址 信息"""
    case_row_number = get_case_row_number(case_id)
    test_case_data().replace_data(case_row_number, 11, content)


def read_headers(case_id):
    """读取该条用例的 请求头 信息"""
    headers = remove_str_all_break(get_case_row_data(case_id)[12])
    if len(headers) <= 0:
        return None
    return headers


def write_headers(case_id, content):
    """修改该条用例的 请求头 信息"""
    case_row_number = get_case_row_number(case_id)
    test_case_data().replace_data(case_row_number, 12, content)


def read_method(case_id):
    """读取该条用例的 请求方法 信息"""
    method = remove_str_all_break(get_case_row_data(case_id)[13])
    if len(method) <= 0:
        return None
    return method


def write_method(case_id, content):
    """修改该条用例的 请求方法 信息"""
    case_row_number = get_case_row_number(case_id)
    test_case_data().replace_data(case_row_number, 13, content)
# request_client.py
    def main(self):
        # 获取所有需要执行的用例id列表
        all_test_count_list = opt_test_data.get_test_run_order()
        for item in all_test_count_list:
            # 判断本条用例是否需要被执行
            is_run = opt_test_data.read_is_run(item)
            try:
                if is_run == "Y" or is_run == "y":
                    # 写入开始执行时间
                    opt_test_data.write_start_time(item, my_time.now_time())
                    # 判断本条用例是否需要依赖数据
                    # 获取 需依赖用例的数据 和 需依赖用例的表达式
                    rely_on_case_data = opt_test_data.read_rely_on_case_data(item)
                    rely_on_case_expression = opt_test_data.read_rely_on_case_expression(item)
                    if rely_on_case_data and rely_on_case_expression:
                        # 准备本条需要依赖的数据
                        self.write_rely_data(item, rely_on_case_expression, rely_on_case_data)
                        # 如果 接口地址\请求头\请求方法\请求数据\文件名称 中需要依赖数据,则使用依赖数据替换
                        self.replace_request_data(item)
                        # 接下来流程一样
                        self._send_end(item)
                    # 不需要依赖数据
                    else:
                        # 接下来流程一样
                        self._send_end(item)

                    # 打印一下本条用例基本情况
                    self.print_now_status(item)
                else:
                    # 本条用例不执行,执行下一条
                    continue
            except Exception as e:
                self.print_now_status(item)
                print(e)
                print(traceback.print_exc())
                opt_test_data.write_status(item, str(traceback.format_exc()))
            finally:
                # 写入本条用例执行完成时间
                self._end_time(item)
                # 写入本条用例的执行人
                self._write_executor(item)
                # 计算本条用例总共花费时间并写入消耗时间中
                self._write_total_time(item)
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Pytest是一个用于接口自动化测试的框架,它可以帮助您编写和组织测试,并提供一些有用的特性,如支持多种断言库、支持并行测试、提供丰富的报告等。 在设计Pytest接口自动化框架时,需要考虑以下几点: 1. 易用性:Pytest的语法简单易懂,易于编写和维护测试用例。 2. 可扩展性:Pytest支持插件机制,可以根据需要扩展其功能。 3. 可读性:Pytest报告中提供了详细的错误信息,方便定位问题。 4. 并行测试:Pytest支持并行测试,可以提高测试效率。 5. 支持多种断言库:Pytest支持多种断言库,如assert语句、基于内置库unittest的断言方法等。 通过考虑以上几点,可以设计出一个功能强大、易用性高的Pytest接口自动化框架。 ### 回答2: pytest接口自动化框架是一种用于编写、执行和管理接口自动化测试的开源测试框架。它基于Python语言,提供了丰富的功能和灵活的设计,使得接口测试变得简单、可维护和可扩展。 pytest的设计思想是"简单即美",它采用了直观的语法和简洁的规范,使得测试用例的编写变得简单易懂。通过使用pytest,我们可以使用简单的装饰器、断言和参数化等功能来编写清晰和可读性强的测试代码。 pytest框架还提供了丰富的插件生态系统,使得我们能够针对实际的测试需求灵活地扩展功能。例如,可以使用插件来生成测试报告、集成持续集成工具、通过参数化实现数据驱动等。这样,我们可以根据项目的需求选择并集成适合的插件,从而使得测试框架更加强大和易于扩展。 此外,pytest框架还支持并行执行测试用例、分布式测试、失败重试等特性,这些功能能够提高测试执行效率和稳定性。同时,其良好的Pytest的集成能力让我们能够简单地与其他工具(如Selenium、Appium等)进行集成,从而实现更全面的测试覆盖。 总而言之,pytest接口自动化框架设计简单易用,具有灵活扩展的特点,并且提供了丰富的特性和插件生态系统。这使得我们能够高效地编写、执行和管理接口自动化测试,从而提高软件质量和加速交付。 ### 回答3: pytest是一种简洁、可扩展且易于使用的Python测试框架,适用于各种类型的测试,包括接口自动化测试。以下是关于如何设计一个pytest接口自动化测试框架的一些建议。 1.项目结构:对于一个pytest接口自动化测试框架,建议按照功能模块或测试类别对测试用例进行组织并进行结构化管理。项目结构应该清晰,易于维护和扩展。 2.配置管理:使用pytest的配置文件pytest.ini或conftest.py来管理框架的配置信息,例如API地址、登录信息、数据库连接等。这样可以方便地配置不同环境下的接口测试。 3.用例设计和管理:用pytest的装饰器标记测试类和方法,例如@pytest.mark.parametrize、@pytest.mark.parametrize、@pytest.mark.skip等。此外,可以使用pytest的fixture机制来共享测试数据、测试环境等。 4.测试报告:pytest提供丰富的插件来生成美观的测试报告,如pytest-html、pytest-allure等。测试报告可以包含测试用例的执行结果、错误信息、日志、失败截图等,便于结果分析和问题定位。 5.异常处理:在接口自动化测试中,经常会遇到异常情况,如接口超时、响应错误等。可以使用pytest的try..except..finally来处理这些异常,并对异常进行记录和处理,以保证测试脚本的健壮性和稳定性。 6.数据管理:在接口自动化测试中,往往需要准备测试数据、验证响应数据等。可以使用pytest的参数化机制来管理测试数据,例如使用CSV、Excel或JSON文件来存储和读取测试数据。 7.断言机制:pytest提供了丰富的断言函数来判断测试结果是否符合预期,如assert、assertEqual、assertTrue等。可以根据接口的返回值进行断言,以验证接口功能是否正确。 总之,设计一个pytest接口自动化测试框架需要考虑项目结构、配置管理、用例设计、测试报告、异常处理、数据管理和断言机制等。合理使用pytest的功能和插件,能够提高测试效率、降低测试成本,并且易于维护和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2436534

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值