目录[-]

一、前言:如果公司要求你用AI生成测试用例,你会怎么做?

 

这是我们系列教程的第五节了, 自从2025年开始,AI大模型的热度越来越高,各种工具层出不穷,AI非常智能,可以生成我们平时忽略掉的问题,质量好、效率高,那么,真的适合我们的业务吗?

我尝试过,在本地搭建一个Ollama环境,使用qwen2.5:7b模型,将公司(内网环境与外网隔离)的产品文档,直接扔给AI,AI给出的内容,漏洞百出,那么是AI错了吗?

当然不是,是“”的方式不对。

我们换个思路想一想,产品提供的文档,大多数是 docx 格式的,当我们直接将 docx 格式的产品文档扔给AI,AI会怎么做?

AI的步骤

  1. 识别文件类型:确保确实是 docx 格式的内容,不是图片或是其他格式

  2. 安全性检查:确保不含违规、恶意内容
  3. 把文档“读成纯文本”
    • 将文档中的“正文”、“标题”、“段落”、“列表”等信息,全部提取出来,转成文字格式。
    • 如果有文字的“图片”,先做 ORC 识别,提取图里的文字再理解内容;
    • 如果是没有文字的“图片”,也能理解和分析。
  4. 理解你让我“分析什么,生成什么”
    • 是总结?
    • 是提取要点?
    • 是找问题?
    • 是润色?
    • 是做表格?
    • 等等,明确用户的“任务目标”。
  5. 对文本做理解与处理
    • 分段理解语义
    • 识别逻辑关系
    • 抓重点、去冗余(相当于看懂整篇文章说了什么)
  6. 按要求生成结果
    • 要总结——》生成:精简总结
    • 要提取信息——》生成:要点
    • 要改写代码——》重新修改
  7. 最后将结果以“流式输出”的方式返回给用户

结论就是

  1. 直接将产品文档扔给AI的做法不可取,AI可能理解一部分,但绝不可能全部理解

  2. 产品设计文档的能力与产品息息相关,如果产品设计文档本身就不完善、不准确,怎么保证AI设计准确?
  3. 业务关联性与复杂度,如果一个功能,与其他功能关联,或需要结合其他页面判断,AI能明白么?
  4. 综上所述,我们在设计之前,需要弄明白,什么是AI可以做的,什么是AI做不到的。

我们公司采用的策略是:

    测试左移 —— 在收到产品设计文档后,测试对其再次加工,加工为AI模型最能读懂的方式 ,YAML 格式的PRD 文档。它是最适合AI大模型理解的类型。

二、核心思路说明

 

2.1 第一阶段:直接将 PRD(产品设计文档)给 AI大模型

把产品经理给的 docx 文档直接扔给 AI 大模型,prompt 写明“帮我生成测试用例”。

结果:

  • AI 理解不准确,漏测、胡编乱造

  • 生成结果不稳定
  • 边界场景覆盖率低

问题分析:

  • docx图文混排,AI看到的是文本流,不知道哪个是标题、哪个是字段、哪个是约束条件。

2.2 第二阶段:人工整理后再喂

测试人员看一遍 PRD ,完全理解后,将关键信息整理成文本再给 AI。

结果:

  • 质量提升

  • 整理工作耗时,每个人整理标准不一致
  • 需求变更时,需要重新整理

问题分析:

  • 输入的结构化不足,一个团队里,每个人整理的水平不一,生成的用例也就不一。

2.3 第三阶段:测试左移 + YAML 结构化

请注意上面步骤的第3点,AI最擅长识别的是“纯文本”,本质上是“输入质量决定了输出质量”,如果输入不稳定,AI返回的值肯定也会不稳定。所以,我们公司将“独立、边界清晰、逻辑确定”的业务模块和功能,主动拆分 YAML 格式的文档,并将其结构化。

三、YAML 字段设计:让AI理解你的真实意图

 

YAML 是什么?它是一种“人机都容易理解”的数据格式,可以理解为一个大 JSON。一个更好写、更易读、长的像散文的 JSON 格式,如下:

3.1 基础版本:定义规则

 

# yaml
- name: 手机号输入框
  type: input
  required: true
  placeholder: "请输入手机号码"
  max_length: 11
  allowed: numeric

这个 YAML 告诉AI:

  • 这是一个输入框,必填
  • 最多11位,只能输入纯数字
  • 提示文字是“请输入手机号码”

AI 看到后,就知道要生成:

  • 手机输入框为“空”校验用例
  • 12位超过长度的校验用例
  • 输入非数字的异常用例

3.2 进阶版本:内置测试数据

 

      # yaml
       - name: 手机号输入框
        type: input
        required: true
        placeholder: "请输入手机号码"
        min_length: 11
        max_length: 11
        allowed: numeric  # 只允许输入纯数字
        rules:
          - 必须是11位纯数字
          - 不允许小于11位 或 大于11位
        valid_examples:
          - 13800001111
        invalid_examples:
          - 123456(位数不足)
          - 13800000000000(位数超长)
          - 133-5555_3134(空格 或 横杠等特殊字符)

这样一来,AI就能精准生成:

  • 正向用例:输入 13800000111——》登录成功
  • 反向用例:小于11位、大于11位、非数字、空——》提示错误

3.3 再次进化版本:添加更多规则

 

      # yaml
      - name: 手机号输入框
        type: input
        required: true
        placeholder: "请输入手机号码"
        min_length: 11
        max_length: 11
        allowed: numeric  # 只允许输入纯数字
        rules:
          - 必须是11位纯数字
          - 不允许小于11位 或 大于11位
        valid_examples:
          - 13800001111
        invalid_examples:
          - 123456(位数不足)
          - 13800000000000(位数超长)
          - 133-5555_3134(空格 或 横杠等特殊字符)
        error_message:
          length_error:"请输入11位手机号码"
          requied_error:"手机号不能为空"
          # 测试覆盖类型(AI 自动生成用例)
        test_coverage:
          - 空值校验
          - 长度校验
          - 格式校验
          - 号段合法性校验
          - 黑名单校验
          - 特殊字符校验
          - 连续/重复数字校验

结论:设计一套完整、精确的结构化 PRD,提升是巨大的。在设计好 YAML 之后,我们就可以开始编写代码了。

四、核心代码实现

 

4.1 为什么不用 ChromaDB?

一开始我打算使用 ChromaDB,但在 Windows环境下,其稳定性堪忧,频繁崩溃,报错代码:0xC000005,分析后发现是 sqlite3 线程问题,尝试修复但没有成功。

最后我选择“字典模式”+“FAISS”的方案:

  • 保留原始 YAML 字典,用于精确字段访问
  • 减轻 prompt 修改难度(字典格式字段比文本块更容易读取)
  • 将字典转换为文本块,存入 FAISS 做语义检索

4.2 核心代码

 

import yaml
import json
import os
import pandas as pd
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import FAISS

print("🚀 字典模式 + FAISS版")

# ========== 1. 加载YAML为字典 ==========
with open('product.yaml', 'r', encoding='utf-8') as f:
    spec_dict = yaml.safe_load(f)

print(f"📄 第1步:加载完成,模块数: {len(spec_dict.get('modules', []))}")

# ========== 2. 字典转文本块(用于FAISS检索)==========
def dict_to_texts(obj, path="", texts=None):
    """保留字典结构信息,转换为文本块"""
    if texts is None:
        texts = []

    if isinstance(obj, dict):
        for k, v in obj.items():
            new_path = f"{path}.{k}" if path else k
            dict_to_texts(v, new_path, texts)
    elif isinstance(obj, list):
        for i, item in enumerate(obj):
            dict_to_texts(item, f"{path}[{i}]", texts)
    else:
        texts.append(f"{path}: {obj}")

    return texts


texts = dict_to_texts(spec_dict)
print(f"📚 第2步:生成 {len(texts)} 个文本块")
# print(texts)

# ========== 3. 创建或加载FAISS ==========
embeddings = OllamaEmbeddings(model="nomic-embed-text")

# 检查是否已有向量库
if os.path.exists("./faiss_db") and os.path.isdir("./faiss_db"):
    print("📂 检测到已有FAISS库,直接加载...")
    vectorstore = FAISS.load_local("./faiss_db", embeddings, allow_dangerous_deserialization=True)
    print("✅ FAISS加载完成")
else:
    print(f"🔄 未找到FAISS库,开始生成向量... (共{len(texts)}个文本块)")
    print(" ⏳ 每个向量约1-3秒,请耐心等待...")

    from tqdm import tqdm

    batch_size = 10
    all_embeddings = []

    for i in tqdm(range(0, len(texts), batch_size), desc="生成向量"):
        batch = texts[i:i + batch_size]
        batch_embeddings = embeddings.embed_documents(batch)
        all_embeddings.extend(batch_embeddings)

    print("🔄 创建FAISS索引...")
    vectorstore = FAISS.from_embeddings(
        text_embeddings=list(zip(texts, all_embeddings)),
        embedding=embeddings
    )

    print("💾 保存FAISS索引...")
    vectorstore.save_local("./faiss_db")
    print("✅ FAISS保存完成")

# ========== 4. 获取字段字典(不需要检索)==========
try:
    list_all = spec_dict.get('modules', [])
    if not list_all:
        raise ValueError("未找到任何模块")

    # 获取第一个模块的所有字段
    fields_list = list_all[0].get('fields', [])

    if not fields_list:
        raise ValueError("模块中没有字段定义")

    print(f"📋 第4步:成功获取{len(fields_list)}个字段")
    for field in fields_list:
        print(f"  - {field.get('name')} ({field.get('type')})")

except Exception as e:
    print(f'❌ 第4步出错: {e}')
    exit()

4.3 流失输出:告别等待焦虑

 

answer = ""
for chunk in llm.stream(prompt):
    print(chunk, end="", flush=True)
    answer += chunk

llm.stream():这个方法就会以“流式输出”的格式,就像是在和AI对话一样,生成结果存在xlsx文件后,如下

4.4 prompt 规则优化

上面的代码可以明显看到,【生成规则】写了几个,如:1. required: true → 生成“为空校验”用例。

但是我总不可能每次都修改源代码,就为了适应“新规则”吧,所以,我们需要将想要AI读取的规则,提前写到 YAML 里,如下:

         # yaml
         ai_test_case_rules:
          - 规则1:required: true → 必须生成【为空校验】用例
          - 规则2:min_length、max_length 存在 → 必须生成【边界值用例】:等于长度成功、小于长度失败、大于长度失败
          - 规则3:allowed: numeric → 必须生成【非数字校验用例】:包含字母、汉字、特殊符号、空格、分隔符均不通过
          - 规则4:每个字段必须覆盖三类用例:正常值、边界值、异常值
          - 规则5:invalid_examples 中每一项 → 必须生成一条对应失败用例
          - 规则6:valid_examples 中每一项 → 必须生成一条对应成功用例
          - 规则7:rules 中每一条校验规则 → 必须生成对应的正向/反向用例
          - 规则8:所有 error_message 必须匹配对应用例的预期结果
          - 规则9:test_coverage 列表中每一项 → 必须生成对应测试场景用例

在 Python 里,添加规则如下(第6条)

最后生成的结果就变成下面这样,从4条用例,变成8条用例,那么还可不可以继续优化?当然可以,只是本文不再套娃了。

 

五、总结与展望

5.1 核心收获

  1. AI 的输入质量决定输出质量:不要让 AI 猜,而是主动结构化。
  2. 测试左移不只是早点接入测试:而是用结构化的方式把需求变成可执行的测试重点。
  3. FAISS 比 ChromaDB稳定:在Windows环境更推荐。

5.2 适用场景

这套方案适合:

  1. 独立、边界清晰、逻辑确定的业务模块(如配置项管理、登录、基础信息维护等)
  2. 表单类、输入校验类功能
  3. 需要频繁回归的场景
  4. 简单的测试功能

复杂场景(如交易主流程、支付回调、订单状态机)目前还不推荐 AI 化,因为“喂”给AI的成本太高了。

5.3 下一步计划

现在我们的设计,还是比较简单的,要知道,AI 优化是永无止境的,设计和推广 YAML 是时代进步的一个缩影,我们设想,我使用了AI在本地环境搭建了一套完善的“AI 生成测试用例”方法,新手可以专注于基础的业务功能测试,而老手就可以专注于“支付、订单”等更关键的业务,这,就是AI的价值所在。

AI 不会取代测试工程师,就像屠龙刀,三岁小孩子使用和谢逊使用,自然是天壤之别,外功固然重要,但测试工程师的内功也不能放下。

 

to be continued 。。。