python 调用deepseek api生成测试用例

实现思路

先把docx文件的文本内容读取处理,然后调用deepseek api把需求内容传递deepseek ,返回的csv内容生成表格文件,再处理成思维导图

完整脚本

python 复制代码
# 文件名:deepseek生成testcase.py
# 作者:Administrator
# 日期:2026/1/3
# 描述:
# !/usr/bin/env python3
# -*- coding: utf-8 -*-


import os
import xmind
import openpyxl
import csv
import sys
import docx2txt
from openai import OpenAI
import time
from functools import wraps



# 读取docx文本内容
def read_docx(filename):
    # 提取所有文本
    text = docx2txt.process(filename)
    # 方法2:使用 filter() 处理换行
    result = '\n'.join(filter(lambda x: x.strip() != '', text.splitlines()))
    # print(result)
    return result


# 装饰器记录执行时间
def timed(func):
    """记录并显示上次执行耗时的装饰器"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        # 1. 读取上次执行耗时
        try:
            with open("last_execution_time.txt", "r", encoding="utf-8") as f:
                last_time = float(f.read().strip())
                print(f"⏱️ 上次执行耗时: {last_time:.3f}秒")
        except (FileNotFoundError, ValueError):
            print("⏱️ 这是第一次执行")

        # 2. 执行并计时
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        duration = end - start

        # 3. 保存本次执行耗时
        with open("last_execution_time.txt", "w", encoding="utf-8") as f:
            f.write(str(duration))

        # 4. 显示本次结果
        print(f"✅ 本次执行耗时: {duration:.3f}秒")

        return result

    return wrapper


@timed
def api_testcase(API_KEY,res_content):
    # 直接使用密钥(仅用于测试和调试)
    client = OpenAI(
        api_key=API_KEY,  # 直接传入
        base_url="https://api.deepseek.com"
    )

    try:
        response = client.chat.completions.create(
            model="deepseek-chat",
            messages=[
                {"role": "system", "content": """你现在扮演测试工程师专家,可以根据需求内容列出基本流场景,有效无效,边界值,可靠性,兼容,性能,安全等等方面去编写测试用例;
                """},
                {"role": "user", "content": f"""
                根据该需求描述:{res_content}.内容生成测试用例,你用CSV格式内容输出测试用例,可参考如下我提供的测试用例格式:
                以下为CSV格式内容(包含表头):
                序号,模块,用例标题,前置条件,测试步骤,预期结果
                1,登录功能,登录系统+输入正确的账号密码+登录成功,用户已注册且状态正常,1. 输入已注册的账号(用户名/邮箱/手机号);2. 输入对应的正确密码;3. 点击"登录"按钮, 1. 登录成功; 2. 跳转至系统首页或用户中心
                """},
            ],
            stream=False
        )
        print("\n", response.choices[0].message.content)

        return response.choices[0].message.content
    except Exception as e:
        print(f"错误: {e}")
        print("可能的原因:")
        print("1. API密钥无效")
        print("2. 网络问题")
        print("3. 服务不可用")


def add_prefix_if_needed(text, prefix):
    """如果 text 没有以指定前缀开头,则自动添加"""
    if not text or not prefix:
        return text
    text_str = str(text).strip()
    if not text_str.startswith(prefix.strip()):
        return f"{prefix}{text_str}"
    return text_str


def safe_str(value):
    """安全转换为字符串,处理 None、数字等"""
    if value in (None, 'None', ''):
        return ""
    return str(value).strip()


def split_lines(text):
    """将文本按换行符分割为非空行列表"""
    if not text:
        return []
    lines = safe_str(text).split('\n')
    return [line.strip() for line in lines if line.strip()]


def normalize_header(header):
    """标准化表头:去空格、转小写,便于匹配"""
    return str(header).strip().lower()


def read_excel_and_generate_xmind(input_path, xmind_path):
    # 确保输出目录存在
    output_dir = os.path.dirname(xmind_path) or '.'
    os.makedirs(output_dir, exist_ok=True)

    # 如果目标 XMind 文件已存在,先删除它(实现覆盖)
    if os.path.exists(xmind_path):
        os.remove(xmind_path)
        print(f"🗑️  已删除旧文件: {xmind_path}")

    # 创建全新的 XMind 工作簿
    xmind_wb = xmind.load(xmind_path)
    sheet_xmind = xmind_wb.getPrimarySheet()
    root_topic = sheet_xmind.getRootTopic()
    root_topic.setTitle("测试用例")

    # 判断输入文件类型
    _, ext = os.path.splitext(input_path.lower())
    rows = []
    headers_raw = []

    if ext == '.xlsx':
        workbook = openpyxl.load_workbook(input_path, data_only=True)
        sheet = workbook.active
        all_rows = list(sheet.iter_rows(values_only=True))
        if not all_rows:
            raise ValueError("Excel 文件为空")
        headers_raw = [cell for cell in all_rows[0]]
        rows = all_rows[1:]
    elif ext == '.csv':
        all_rows = None
        encodings_to_try = ['utf-8-sig', 'utf-8', 'gbk', 'gb2312', 'cp936', 'latin1']
        for enc in encodings_to_try:
            try:
                with open(input_path, 'r', encoding=enc, newline='') as f:
                    reader = csv.reader(f)
                    all_rows = list(reader)
                    print(f"✅ 使用编码 '{enc}' 成功读取 CSV 文件")
                    break
            except (UnicodeDecodeError, LookupError):
                continue

        if all_rows is None:
            raise ValueError(
                "❌ 无法读取 CSV 文件:尝试了所有常见编码(UTF-8、GBK 等)均失败。\n"
                "请检查:\n"
                "1. 文件是否为真正的 CSV 文本文件(非 Excel .xls 直接改后缀)\n"
                "2. 是否用 Excel 另存为「CSV UTF-8 (逗号分隔)(*.csv)」格式"
            )
        if not all_rows:
            raise ValueError("CSV 文件为空")
        headers_raw = all_rows[0]
        rows = all_rows[1:]
    else:
        raise ValueError("仅支持 .xlsx 或 .csv 格式的输入文件(例如:测试用例.xlsx 或 测试用例.csv)")

    # 构建标准化的 header 映射(支持大小写/空格不敏感)
    required_fields_std = ["模块", "用例标题", "前置条件", "测试步骤", "预期结果"]
    normalized_headers = [normalize_header(h) for h in headers_raw]
    header_map = {}  # {"模块": 列索引, ...}

    # 先尝试标准字段名
    for std_field in required_fields_std:
        norm_key = normalize_header(std_field)
        if norm_key in normalized_headers:
            header_map[std_field] = normalized_headers.index(norm_key)

    # 如果还有缺失,尝试别名
    if len(header_map) < len(required_fields_std):
        ALIASES = {
            "模块": ["模块", "功能模块", "所属模块", "module"],
            "用例标题": ["用例标题", "标题", "测试用例", "case title", "title"],
            "前置条件": ["前置条件", "前提条件", "precondition", "pre-condition"],
            "测试步骤": ["测试步骤", "步骤", "操作步骤", "steps", "step"],
            "预期结果": ["预期结果", "期望结果", "expected", "result", "expected result"]
        }
        for std_field in required_fields_std:
            if std_field in header_map:
                continue
            found = False
            for alias in ALIASES[std_field]:
                alias_norm = normalize_header(alias)
                if alias_norm in normalized_headers:
                    header_map[std_field] = normalized_headers.index(alias_norm)
                    found = True
                    break
            if not found:
                available = [h for h in headers_raw if h is not None]
                raise ValueError(
                    f"未找到必需字段 '{std_field}'。\n"
                    f"可用列名: {available}\n"
                    f"支持的别名包括: {ALIASES[std_field]}"
                )

    # 存储已创建的模块主题
    module_topics = {}
    case_count = 0

    # 遍历数据行
    for row_idx, row in enumerate(rows, start=2):
        # 跳过完全空的行
        if not any(cell is not None and str(cell).strip() for cell in row):
            continue

        try:
            module = row[header_map["模块"]]
            title = row[header_map["用例标题"]]
            pre_cond = row[header_map["前置条件"]]
            steps = row[header_map["测试步骤"]]
            expected = row[header_map["预期结果"]]
        except IndexError:
            print(f"⚠️  第 {row_idx} 行数据列数不足,跳过: {row}")
            continue

        if not module or not title:
            continue  # 跳过缺少关键字段的行

        # 格式化
        formatted_module = add_prefix_if_needed(module, FIELD_PREFIXES["模块"])
        formatted_title = add_prefix_if_needed(title, FIELD_PREFIXES["用例标题"])

        # 获取或创建模块主题
        if formatted_module not in module_topics:
            module_topic = root_topic.addSubTopic()
            module_topic.setTitle(formatted_module)
            module_topics[formatted_module] = module_topic

        parent_module = module_topics[formatted_module]
        case_topic = parent_module.addSubTopic()
        case_topic.setTitle(formatted_title)
        case_count += 1

        # 前置条件
        if pre_cond and safe_str(pre_cond):
            formatted_pre_cond = add_prefix_if_needed(pre_cond, FIELD_PREFIXES["前置条件"])
            pc_topic = case_topic.addSubTopic()
            pc_topic.setTitle(formatted_pre_cond)

        # 测试步骤 & 预期结果配对
        step_lines = split_lines(steps)
        expected_lines = split_lines(expected)
        min_len = min(len(step_lines), len(expected_lines))

        for i in range(min_len):
            step_text = step_lines[i]
            exp_text = expected_lines[i]

            bz_topic = case_topic.addSubTopic()
            bz_title = add_prefix_if_needed(step_text, FIELD_PREFIXES["测试步骤"])
            bz_topic.setTitle(bz_title)

            yq_subtopic = bz_topic.addSubTopic()
            yq_title = add_prefix_if_needed(exp_text, FIELD_PREFIXES["预期结果"])
            yq_subtopic.setTitle(yq_title)

        # 处理剩余未配对的步骤
        for i in range(min_len, len(step_lines)):
            bz_topic = case_topic.addSubTopic()
            bz_title = add_prefix_if_needed(step_lines[i], FIELD_PREFIXES["测试步骤"])
            bz_topic.setTitle(bz_title)

        # 处理剩余未配对的预期结果
        for i in range(min_len, len(expected_lines)):
            yq_topic = case_topic.addSubTopic()
            yq_title = add_prefix_if_needed(expected_lines[i], FIELD_PREFIXES["预期结果"])
            yq_topic.setTitle(yq_title)

    # 保存 XMind 文件
    xmind.save(xmind_wb, xmind_path)
    print(f"✅ 成功生成 XMind 文件: {xmind_path}")
    print(f"📊 共处理 {case_count} 条测试用例")


# 生成csv文件
def save_csv_from_string(csv_string, output_file="test_case.csv"):
    """
    从CSV字符串保存为文件
    """
    try:
        # 检查文件是否已存在
        if os.path.exists(output_file):
            print(f"⚠️  文件 '{output_file}' 已存在!")
            counter = 1
            while os.path.exists(f"test_case_{counter}.csv"):
                counter += 1
            output_file = f"test_case_{counter}.csv"
            print(f"📝 将保存为新文件: {output_file}")

        # 写入文件
        with open(output_file, 'w', encoding='utf-8', newline='') as f:
            f.write(csv_string.strip())

        print(f"\n✅ 成功!文件已保存为: {output_file}")
        print(f"📁 文件路径: {os.path.abspath(output_file)}")
        print(f"📊 文件大小: {os.path.getsize(output_file)} 字节")

        # 验证和显示文件内容
        print(f"\n📋 文件内容验证:")
        print("-" * 60)

        try:
            # 尝试用csv模块读取以验证格式
            with open(output_file, 'r', encoding='utf-8') as f:
                csv_reader = csv.reader(f)
                rows = list(csv_reader)

                print(f"✅ 解析成功: {len(rows)} 行数据")
                print(f"📊 表头字段数: {len(rows[0])}")
                print(f"📝 表头: {', '.join(rows[0])}")
                print(f"🔢 测试用例数: {len(rows) - 1} 个")

                # 显示前几个测试用例
                print(f"\n📋 前3个测试用例预览:")
                for i, row in enumerate(rows[1:4], 1):
                    print(f"  用例{i}: {row[1]} - {row[2][:30]}...")

        except Exception as e:
            print(f"⚠️ CSV格式验证警告: {e}")
            print("💡 建议检查CSV内容格式是否正确")

        print("-" * 60)

        # 显示文件内容前几行
        print(f"\n📄 文件内容预览 (前10行):")
        print("=" * 80)
        with open(output_file, 'r', encoding='utf-8') as f:
            for i, line in enumerate(f):
                if i < 10:
                    print(f"{i + 1:2d}: {line.rstrip()}")
                else:
                    print("   ...")
                    break
        print("=" * 80)

        return True

    except Exception as e:
        print(f"❌ 保存文件时发生错误: {e}")
        return False


def main(case_csv):
    """
    主函数
    """
    print("=" * 60)
    print("测试用例CSV文件生成工具")
    print("=" * 60)

    # 显示CSV内容统计信息
    lines = case_csv.strip().split('\n')
    print(f"📊 CSV内容统计:")
    print(f"  总行数: {len(lines)}")
    print(f"  表头字段: {len(lines[0].split(','))} 个")
    print(f"  测试用例数: {len(lines) - 1} 个")

    # 分类统计
    login_count = sum(1 for line in lines if
                      line.startswith('1,') or line.startswith('2,') or line.startswith('3,') or line.startswith(
                          '4,') or line.startswith('5,') or line.startswith('6,') or line.startswith(
                          '7,') or line.startswith('8,'))
    register_count = sum(1 for line in lines if
                         line.startswith('9,') or line.startswith('10') or line.startswith('11') or line.startswith(
                             '12') or line.startswith('13') or line.startswith('14') or line.startswith(
                             '15') or line.startswith('16') or line.startswith('17'))

    print(f"  登录功能用例: {login_count} 个")
    print(f"  注册功能用例: {register_count} 个")
    print("-" * 60)

    # 询问用户是否生成文件
    # choice = input("是否生成CSV文件?(y/n): ").strip().lower()
    choice = 'y'
    if choice == 'y':
        # 可选:让用户指定文件名
        # custom_name = input("请输入文件名(直接回车使用默认名称 test_case.csv): ").strip()
        custom_name = 'test_case.csv'

        if custom_name:
            if not custom_name.lower().endswith('.csv'):
                custom_name += '.csv'
            save_csv_from_string(case_csv, custom_name)
        else:
            save_csv_from_string(case_csv)

        print("\n🎉 CSV文件生成完成!")
    else:
        print("取消生成文件。")

    print("=" * 60)

# 删除生成文件
def delete_file(filename):
    """
    删除当前脚本目录下的指定文件

    参数:
        filename: 要删除的文件名(可以是相对路径或绝对路径)
    """
    # 获取当前脚本所在目录
    current_dir = os.path.dirname(os.path.abspath(__file__))

    # 如果传入的是绝对路径,直接使用;否则拼接为当前目录下的路径
    if os.path.isabs(filename):
        file_path = filename
    else:
        file_path = os.path.join(current_dir, filename)

    # 检查文件是否存在
    if not os.path.exists(file_path):
        print(f"文件不存在: {file_path}")
        return False

    # 确保是文件而不是目录
    if not os.path.isfile(file_path):
        print(f"这不是一个文件: {file_path}")
        return False

    try:
        os.remove(file_path)
        print(f"成功删除文件: {file_path}")
        return True
    except Exception as e:
        print(f"删除失败: {e}")
        return False


if __name__ == "__main__":
    # 每次执行删除文件
    start_list = ["output.csv",
                  "test_case.csv",
                  "测试用例.xmind"]
    run = 1
    if run == 1:
        for i in start_list:
            delete_file(i)


    # API keys
    API_KEY = "输入你的api"

    # 传入需求文档,调用大模型
    case_csv = api_testcase(API_KEY,
                            read_docx("登录功能2.docx")
                            )

    # 生成csv文件
    main(case_csv)

    # 替换;修改为\n换行
    input_file = 'test_case.csv'
    output_file = 'output.csv'

    with open(input_file, 'r', encoding='utf-8') as fin, \
            open(output_file, 'w', encoding='utf-8', newline='') as fout:

        reader = csv.reader(fin)
        writer = csv.writer(fout)

        for row in reader:
            # 对每一列(每个单元格)进行处理:把 ; 替换为 \n
            new_row = [cell.replace(';', '\n') for cell in row]
            writer.writerow(new_row)

    print(f"已将每个单元格中的 ';' 替换为换行符,结果保存到 {output_file}")


    # ====================== 配置区(根据你的需求修改) ======================
    FIELD_PREFIXES = {
        "模块": "mk:",  # 模块前缀
        "用例标题": "tc-p1:",  # 用例标题前缀
        "前置条件": "pc: ",  # 前置条件前缀
        "测试步骤": "bz: ",  # 测试步骤前缀
        "预期结果": "yq: "  # 预期结果前缀
    }
    # ======================================================================

    # 支持命令行传参:python demo5.py input.xlsx output.xmind
    if len(sys.argv) >= 2:
        input_file = sys.argv[1]
        if len(sys.argv) >= 3:
            output_file = sys.argv[2]
        else:
            base = os.path.splitext(input_file)[0]
            output_file = f"{base}.xmind"
    else:
        # 默认文件(你可以根据需要修改)
        input_file = "output.csv"  # 也可以是 "测试用例.csv"
        output_file = "测试用例.xmind"

    if not os.path.exists(input_file):
        print(f"❌ 输入文件不存在: {input_file}")
        print("请确保当前目录下有该文件,或通过命令行指定路径。")
        sys.exit(1)

    try:
        read_excel_and_generate_xmind(input_file, output_file)
    except Exception as e:
        print(f"💥 程序运行出错: {e}")
        sys.exit(1)

实现效果

相关博客

python 生成xmind文件-CSDN博客

coze 工作流生成测试用例-CSDN博客

相关推荐
火云洞红孩儿7 小时前
告别界面孤岛:PyMe如何用一站式流程重塑Python GUI开发?
开发语言·python
攻城狮7号7 小时前
不懂代码也能造?TRAE+GLM-4.6 手把手教你搭心理咨询智能客服小程序
python·小程序·uni-app·vue·trae·glm我的编程搭子·glm-4.6
叫我辉哥e17 小时前
新手进阶Python:办公看板集成ERP跨系统同步+自动备份+AI异常复盘
开发语言·人工智能·python
布局呆星8 小时前
闭包与装饰器
开发语言·python
全栈测试笔记8 小时前
异步函数与异步生成器
linux·服务器·前端·数据库·python
木头左8 小时前
基于Backtrader框架的指数期权备兑策略实现与分析
python
素心如月桠9 小时前
cmd 输入 python --version 输出为空(windows11系统安装python后执行python --version没反应)
python
飞Link9 小时前
深度解析 HyperLPR:高性能中文车牌识别框架从入门到实战
python
QQ588501989 小时前
Python_uniapp-心理健康测评服务微信小程序的设计与实现
python·微信小程序·uni-app