Yapi接口文档导出测试用例至Excel中

前言

因为项目要求测试用例必须是excel,而且Yapi导出选项中没有excel,所以写个脚本方便以后一键生成。

实现

利用python实现

python 复制代码
import json

import pandas as pd
import requests


class YapiToExcel:
    def __init__(self, yapi_url, token, cookies, project_name, use_test_case):
        self.yapi_url = yapi_url.rstrip('/')
        self.token = token
        self.headers = {
            'Content-Type': 'application/json',
            'Authorization': f'{token}'
        }
        self.cookies = cookies
        self.project_name = project_name
        self.use_test_case = use_test_case

    def _generate_request_body_example(self, schema):
        """
        根据JSON Schema生成请求体示例
        """
        if not isinstance(schema, dict) or schema.get('type') != 'object':
            return json.dumps(schema, ensure_ascii=False, indent=2)

        properties = schema.get('properties', {})
        required = schema.get('required', [])

        # 构建示例请求体
        example_body = {}

        for prop_name, prop_info in properties.items():
            if not isinstance(prop_info, dict):
                continue

            # 获取属性类型
            prop_type = prop_info.get('type', 'string')

            # 优先使用mock值
            mock_info = prop_info.get('mock', {})
            if mock_info and 'mock' in mock_info:
                example_body[prop_name] = mock_info['mock']
            else:
                # 根据类型填充默认值
                if prop_type == 'string':
                    example_body[prop_name] = ""
                elif prop_type == 'integer':
                    example_body[prop_name] = 0
                elif prop_type == 'number':
                    example_body[prop_name] = 0
                elif prop_type == 'boolean':
                    example_body[prop_name] = False
                elif prop_type == 'array':
                    example_body[prop_name] = []
                elif prop_type == 'object':
                    example_body[prop_name] = {}
                else:
                    example_body[prop_name] = ""

        return json.dumps(example_body, ensure_ascii=False, indent=2)
    def get_project_list(self):
        """获取项目列表"""
        url = f"{self.yapi_url}/api/project/list"
        params = {
            'page': 1,
            'limit': 100,
            'token': self.token
        }
        response = requests.get(url, headers=self.headers, params=params)
        result = response.json()
        if result and result.get('errcode') == 0:
            return result.get('data', {})
        return {}

    def get_interface_list(self, project_id):
        """获取项目下的接口列表"""
        url = f"{self.yapi_url}/api/interface/list"
        all_interfaces = []
        page = 1
        limit = 30  # 每页获取10条数据

        while True:
            params = {
                'project_id': project_id,
                'token': self.token,
                'page': page,
                'limit': limit
            }
            response = requests.get(url, params=params)
            result = response.json()
            if result and result.get('errcode') == 0:
                data = result.get('data', {})
                interfaces = data.get('list', [])
                all_interfaces.extend(interfaces)

                # 获取总数和当前数据数量
                total_count = data.get('count', 0)
                current_count = len(all_interfaces)

                # 如果已获取的数据量大于等于总数,或者当前页没有数据,则停止循环
                if not interfaces or current_count >= total_count:
                    break

                page += 1
            else:
                break

        return all_interfaces

    def get_interface_detail(self, interface_id):
        """获取接口详情"""
        url = f"{self.yapi_url}/api/interface/get"
        params = {
            'id': interface_id,
            'token': self.token
        }
        response = requests.get(url, params=params)
        result = response.json()
        if result and result.get('errcode') == 0:
            return result.get('data', {})
        return {}

    def get_test_case_list(self, project_id):
        """获取项目下的测试用例集合列表"""
        url = f"{self.yapi_url}/api/col/list"
        params = {
            'project_id': project_id,
            # 'token': self.token
        }

        response = requests.get(url, params=params, cookies=self.cookies)
        result = response.json()
        if result and result.get('errcode') == 0:
            return result.get('data', [])
        return []

    def get_test_case_detail(self, test_case_id):
        """获取测试用例详情"""
        url = f"{self.yapi_url}/api/col/case"
        params = {
            'caseid': test_case_id,
            'token': self.token
        }
        response = requests.get(url, params=params, cookies=self.cookies)
        result = response.json()
        if result and result.get('errcode') == 0:
            return result.get('data', {})
        return {}

    def extract_interface_case_data(self, interface_detail):
        """提取测试用例数据"""
        if not interface_detail:
            return None
        parsed_body = None  # 或者根据业务逻辑设置合适的默认值
        # 请求路径
        path = interface_detail.get('path', '')

        # 请求方法
        method = interface_detail.get('method', '')

        # 请求头
        req_headers = interface_detail.get('req_headers', [])
        headers_str = json.dumps(req_headers, ensure_ascii=False) if req_headers else ''

        # 请求参数
        req_params = interface_detail.get('req_params', [])
        req_query = interface_detail.get('req_query', [])
        req_body = interface_detail.get('req_body_other', '') or interface_detail.get('req_body_form', '')
        req_body_example = ""  # 用于存储生成的请求体示例
        # if req_body:
        #     try:
        #         # 如果req_body是字符串形式的JSON,解析并格式化
        #         if isinstance(req_body, str):
        #             parsed_body = json.loads(req_body)
        #             req_body = json.dumps(parsed_body, ensure_ascii=False, indent=2)
        #         # 如果req_body已经是dict格式,直接格式化
        #         elif isinstance(req_body, dict):
        #             req_body = json.dumps(req_body, ensure_ascii=False, indent=2)
        #     except json.JSONDecodeError:
        #         # 如果解析失败,保持原始数据
        #         pass
        if req_body:
            try:
                # 如果req_body是字符串形式的JSON,解析
                if isinstance(req_body, str):
                    parsed_body = json.loads(req_body)
                # 如果req_body已经是dict格式
                elif isinstance(req_body, dict):
                    parsed_body = req_body

                # 判断是否为JSON Schema格式
                if isinstance(parsed_body, dict) and 'type' in parsed_body and 'properties' in parsed_body:
                    # 生成真实的请求体示例
                    req_body_example = self._generate_request_body_example(parsed_body)
                    # 保留原始Schema作为备用
                    req_body = json.dumps(parsed_body, ensure_ascii=False, indent=2)
                else:
                    # 普通JSON数据,格式化显示
                    req_body_example = json.dumps(parsed_body, ensure_ascii=False, indent=2)
                    req_body = req_body_example
            except json.JSONDecodeError:
                # 如果解析失败,保持原始数据
                pass

        # 如果没有生成示例请求体,则使用处理后的req_body
        if not req_body_example:
            req_body_example = req_body

        # 合并所有请求参数
        params_info = {
            'req_params': req_params,
            'req_query': req_query,
            'req_body': req_body
        }
        params_str = json.dumps(params_info, ensure_ascii=False)

        # 返回结果
        res_body = interface_detail.get('res_body', '')

        return {
            '项目名称': self.project_name,
            '测试用例ID': interface_detail.get('_id', ''),
            '测试用例名称': interface_detail.get('title', ''),
            '测试用例描述': '',
            '请求路径': path,
            '请求方法': method,
            # 'Header头': headers_str,
            '请求query': req_query,
            '请求param': req_params,
            '请求body': req_body_example,
            '预期结果': res_body,
            '创建时间': interface_detail.get('add_time', ''),
            '更新时间': interface_detail.get('up_time', '')
        }

    def extract_test_collection_data(self, test_case_detail, project_name=""):
        """提取测试集合数据"""
        if not test_case_detail:
            return None

        # 提取测试用例基本信息
        case_name = test_case_detail.get('casename', '')
        case_desc = test_case_detail.get('desc', '')

        path = test_case_detail.get('path', '')
        method = test_case_detail.get('method', '')

        # 请求头
        headers = test_case_detail.get('req_headers', [])
        headers_str = json.dumps(headers, ensure_ascii=False) if headers else ''

        # 请求头
        headers = test_case_detail.get('req_headers', [])
        headers_str = json.dumps(headers, ensure_ascii=False, indent=2) if headers else ''

        # 请求参数
        params = test_case_detail.get('req_query', [])
        params_str = json.dumps(params, ensure_ascii=False, indent=2) if params else ''
        req_query = test_case_detail.get('req_query', [])

        # 请求体数据
        req_body = test_case_detail.get('req_body_other', '')
        if req_body:
            try:
                # 如果req_body是字符串形式的JSON,解析并格式化
                if isinstance(req_body, str):
                    parsed_body = json.loads(req_body)
                    req_body = json.dumps(parsed_body, ensure_ascii=False, indent=2)
                # 如果req_body已经是dict格式,直接格式化
                elif isinstance(req_body, dict):
                    req_body = json.dumps(req_body, ensure_ascii=False, indent=2)
            except json.JSONDecodeError:
                # 如果解析失败,保持原始数据
                pass

        # 预期响应
        res_body = test_case_detail.get('res_body', '')

        return {
            '项目名称': self.project_name,
            '测试用例ID': test_case_detail.get('_id', ''),
            '测试用例名称': case_name,
            '测试用例描述': case_desc,
            '请求路径': path,
            '请求方法': method,
            # 'Header头': headers_str,
            '请求query': req_query,
            '请求param': params_str,
            '请求body': req_body,
            '预期结果': res_body,
            '创建时间': test_case_detail.get('add_time', ''),
            '更新时间': test_case_detail.get('up_time', ''),
        }

    def export_project_to_excel(self, project_id, output_file):
        """导出项目接口到Excel"""
        if self.use_test_case:
            all_test_cases = []
            test_cases_list = self.get_test_case_list(project_id)
            if test_cases_list:
                for test_cases in test_cases_list:
                    for test_case in test_cases.get('caseList'):
                        test_case_id = test_case.get('_id')
                        if test_case_id:
                            # 获取测试用例详情
                            detail = self.get_test_case_detail(test_case_id)
                            test_collection_data = self.extract_test_collection_data(detail, self.project_name)
                            if test_collection_data:
                                all_test_cases.append(test_collection_data)

            # 创建DataFrame并导出到Excel
            if all_test_cases:
                df = pd.DataFrame(all_test_cases)
                df.to_excel(output_file, index=False)
                print(f"成功导出 {len(all_test_cases)} 个接口到 {output_file}")
            else:
                print("没有找到有效的接口数据")
        else:
            # 获取接口列表
            interfaces = self.get_interface_list(project_id)
            if not interfaces:
                print("获取接口列表失败")
                return

            test_cases = []
            for interface in interfaces:
                interface_id = interface.get('_id')
                if interface_id:
                    # 获取接口详情
                    detail = self.get_interface_detail(interface_id)
                    test_case_data = self.extract_interface_case_data(detail)
                    if test_case_data:
                        test_cases.append(test_case_data)
            # 创建DataFrame并导出到Excel
            if test_cases:
                df = pd.DataFrame(test_cases)
                df.to_excel(output_file, index=False)
                print(f"成功导出 {len(test_cases)} 个接口到 {output_file}")
            else:
                print("没有找到有效的接口数据")

    def export_all_projects_to_excel(self, output_file):
        """导出所有项目接口到Excel"""
        # 获取项目列表
        project_data = self.get_project_list()
        if not project_data:
            print("获取项目列表失败")
            return

        all_test_cases = []
        projects = project_data.get('list', [])

        for project in projects:
            project_id = project.get('_id')
            project_name = project.get('name')
            print(f"正在处理项目: {project_name}")

            # 获取接口列表
            interfaces = self.get_interface_list(project_id)
            if not interfaces:
                continue

            for interface in interfaces:
                interface_id = interface.get('_id')
                if interface_id:
                    # 获取接口详情
                    detail = self.get_interface_detail(interface_id)
                    test_case_data = self.extract_interface_case_data(detail)
                    if test_case_data:
                        test_case_data['项目名称'] = project_name
                        all_test_cases.append(test_case_data)

            # 获取测试用例列表
            test_cases = self.get_test_case_list(project_id)
            if test_cases:
                for test_case in test_cases:
                    test_case_id = test_case.get('_id')
                    if test_case_id:
                        # 获取测试用例详情
                        detail = self.get_test_case_detail(test_case_id)
                        test_collection_data = self.extract_test_collection_data(detail, project_name)
                        if test_collection_data:
                            all_test_cases.append(test_collection_data)

        # 创建DataFrame并导出到Excel
        if all_test_cases:
            df = pd.DataFrame(all_test_cases)
            # 调整列顺序,将项目名称放在前面
            cols = df.columns.tolist()
            if '项目名称' in cols:
                cols.remove('项目名称')
                cols.insert(0, '项目名称')
                df = df[cols]

            df.to_excel(output_file, index=False)
            print(f"成功导出 {len(all_test_cases)} 个接口到 {output_file}")
        else:
            print("没有找到有效的接口数据")


def _generate_request_body_example(self, schema):
    """
    根据JSON Schema生成请求体示例
    """
    if not isinstance(schema, dict) or schema.get('type') != 'object':
        return json.dumps(schema, ensure_ascii=False, indent=2)

    properties = schema.get('properties', {})
    required = schema.get('required', [])

    # 构建示例请求体
    example_body = {}

    for prop_name, prop_info in properties.items():
        if not isinstance(prop_info, dict):
            continue

        # 获取属性类型
        prop_type = prop_info.get('type', 'string')

        # 优先使用mock值
        mock_info = prop_info.get('mock', {})
        if mock_info and 'mock' in mock_info:
            example_body[prop_name] = mock_info['mock']
        else:
            # 根据类型填充默认值
            if prop_type == 'string':
                example_body[prop_name] = ""
            elif prop_type == 'integer':
                example_body[prop_name] = 0
            elif prop_type == 'number':
                example_body[prop_name] = 0
            elif prop_type == 'boolean':
                example_body[prop_name] = False
            elif prop_type == 'array':
                example_body[prop_name] = []
            elif prop_type == 'object':
                example_body[prop_name] = {}
            else:
                example_body[prop_name] = ""

    return json.dumps(example_body, ensure_ascii=False, indent=2)

def main():
    # 直接定义常量
    YAPI_URL = "http://11.11.11.11:3000/"  # 替换为你的Yapi服务器地址
    TOKEN = "1234"  # 替换为你的Yapi访问token
    PROJECT_ID = 2680  # 项目ID,如果为None则导出所有项目
    OUTPUT_FILE = "yapi_test_cases.xlsx"  # 输出Excel文件名
    # 如果要导出Yapi测试用例集合那么需要去F12 查看cookies
    cookies = {
        '_yapi_token': "1234",
        '_yapi_uid': "1243"
    }
    project_name = "国内食材管理"
    use_test_case = True  # 如果想导出Yapi中测试用例集合那么设置为True

    yapi_client = YapiToExcel(YAPI_URL, TOKEN, cookies, project_name=project_name, use_test_case=use_test_case)

    if PROJECT_ID:
        yapi_client.export_project_to_excel(PROJECT_ID, OUTPUT_FILE)
    else:
        yapi_client.export_all_projects_to_excel(OUTPUT_FILE)


if __name__ == "__main__":
    main()
相关推荐
CodeCraft Studio19 小时前
国产化Excel开发组件Spire.XLS教程:Python 将 CSV 转换为 Excel(.XLSX)
windows·python·excel·csv转excel·spire·excel开发
测试19981 天前
单元测试到底是什么?该怎么做?
自动化测试·软件测试·python·测试工具·职场和发展·单元测试·测试用例
石国旺1 天前
前端javascript在线生成excel,word模板-通用场景(免费)
前端·javascript·excel
界面开发小八哥2 天前
DevExpress WinForms中文教程:Data Grid - Excel样式的自定义过滤器对话框
ui·.net·excel·界面控件·winform·devexpress·ui开发
玩泥巴的2 天前
使用二次封装的Excel COM 组件操作Excel\WPS ET中的区域、行和列
excel·二次开发
阿波罗尼亚2 天前
Excel Word Pdf 格式转换
pdf·word·excel
揭老师高效办公2 天前
在Excel和WPS表格中隔一行插入一个空白行
excel·wps表格
凯子坚持 c2 天前
Claude Code 完整手册:从入门、配置到高级自动化
运维·自动化·excel·claude code