首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >AI 实践探索:辅助生成测试用例

AI 实践探索:辅助生成测试用例

作者头像
巫山老妖
发布2025-06-16 21:45:47
发布2025-06-16 21:45:47
34900
代码可运行
举报
文章被收录于专栏:小巫技术博客小巫技术博客
运行总次数:0
代码可运行

背景

目前我们的测试用例主要依赖人工生成和维护,AI时代的来临,我们也在思考“AI如何赋能业务”,提出了如下命题:

“探索通过AI辅助生成测试用例,完成从需求到测试用例生成的穿刺”。

目标

  • • 找全测试路径
  • • 辅助生成测试用例

实践案例:登录注册流程

自然语言描述需求

需求名称:注册登录流程 需求描述: 1、注册和登录在同一个页面,有2个按钮,一个注册,一个登录,用户输入用户名、密码进行登录或者注册 2、首页:加载一张图,有个退出按钮,点击则退出首页

注:这里只是为了验证思路,需求描述会比较简单,实际需求考虑会更完善。

如何找全测试路径

使用LLM生成mermaid格式的状态机描述

使用Dify[1] 搭建的工作流:

将前面的需求描述作为输入参数,提供Prompt模板告诉LLM,如下所示: [2]

LLM 生成的mermaid 状态机描述:

代码语言:javascript
代码运行次数:0
运行
复制
stateDiagram-v2
    [*] --> Unregistered
    Unregistered --> Registering: start_register
    Registering --> Unregistered: register_failed
    Registering --> LoggingIn: register_success
    Unregistered --> LoggingIn: start_login
    LoggingIn --> Unregistered: login_failed
    LoggingIn --> LoggedIn: login_success
    LoggedIn --> Unregistered: logout
    LoggedIn --> [*]: exit

Markdown对mermaid支持友好,可以直接渲染成状态机图:

这里选择Mermaid来描述状态机的理由,主要是Mermaid天然适合文档化,代码轻量且无额外依赖,无需处理图片格式的一些问题。

参考:AI大模型生成的图表为什么倾向使用Mermaid格式?[3]

使用AI帮我们开发工具

前面通过LLM能够帮我们理解需求生成状态机图,如果想基于状态机找全测试路径,我们尝试使用AI编程工具来辅助生成规则工具,来确保每次遍历的路径是一致的。

比如Cursor

通过多轮的对话和人工修正,Cursor能够很高效的帮助我生成符合预期的代码,但仍需要人工去验证和调试。

核心路径生成算法:

代码语言:javascript
代码运行次数:0
运行
复制
from typing importList, Dict, Set
from abc import ABC, abstractmethod

classPathGeneratorBase(ABC):
    def__init__(self):
        self.graph = {}
        self.paths = []
        self.events = {}
        
    @abstractmethod
    defparse_input(self):
        """解析输入源(Mermaid或SCXML)"""
        pass
        
    defgenerate_paths(self, max_depth: int = ) -> List[List[str]]:
        """通用的路径生成算法"""
        paths = []
        start = self._find_start_state()
        visited_states = set()
        
        defdfs(current: str, path: List[str]):
            iflen(path) > max_depth:
                return
                
            current_transitions = self._get_transitions(current)
            
            ifself._should_terminate(current, path, current_transitions):
                paths.append(path[:])
                return
                
            visited_states.add(current)
            
            for next_state in current_transitions:
                dfs(next_state, path + [next_state])
                
            visited_states.remove(current)
            
        dfs(start, [start])
        returnself._deduplicate_paths(paths)
        
    def_find_start_state(self) -> str:
        """查找起始状态"""
        if'START'inself.graph:
            return'START'
            
        in_degrees = self._calculate_in_degrees()
        for node, degree in in_degrees.items():
            if degree == :
                return node
        returnNone
        
    def_get_transitions(self, state: str) -> List[str]:
        """获取状态的所有可能转换"""
        if state notinself.graph:
            return []
        return [target for target inself.graph[state]]
        
    def_should_terminate(self, current: str, path: List[str], transitions: List[str]) -> bool:
        """判断是否应该终止当前路径"""
        returnlen(path) > and (not transitions or current in path[:-])
        
    def_deduplicate_paths(self, paths: List[List[str]]) -> List[List[str]]:
        """去除重复路径"""
        unique_paths = []
        path_strings = set()
        
        for path insorted(paths, key=len):
            path_str = "->".join(path)
            if path_str notin path_strings:
                path_strings.add(path_str)
                unique_paths.append(path)
                
        return unique_paths
        
    defcalculate_coverage(self) -> Dict:
        """计算测试覆盖率"""
        all_states = set(self.graph.keys())
        all_transitions = set()
        covered_states = set()
        covered_transitions = set()
        
        for path inself.paths:
            covered_states.update(path)
            for i inrange(len(path) - ):
                transition = (path[i], path[i + ])
                covered_transitions.add(transition)
                all_transitions.add(transition)
                
        return {
            "state_coverage": len(covered_states) / len(all_states) * ,
            "transition_coverage": len(covered_transitions) / len(all_transitions) * 
        }

根据路径生成算法遍历生成的路径索引:

如何生成测试用例

用例关键要素

  • • 前置条件:描述复现测试场景所需的条件;
  • • 操作步骤:描述测试场景下用户的操作行为;
  • • 预期结果:描述测试场景下,用户执行完操作预期得到的结果。

示例:

场景

前置条件

操作步骤

预期结果

银行卡支付场景

用户已绑定有效银行卡

在收银台选择银行卡支付

显示支付成功页面,账户余额减少相应金额

LLM 生成测试用例

工作流设计:

  1. 1. 输入需求描述和测试路径
  2. 2. 设计prompt提示词,要求LLM按固定格式输出测试用例

生成的测试用例结果示例:

代码语言:javascript
代码运行次数:0
运行
复制
[
  {
    "precondition":"用户未注册,系统处于未登录状态",
    "operation_procedure":[
      "1. 访问系统首页",
      "2. 输入用户名:a1_(3字符)",
      "3. 输入密码:Passw0rd!",
      "4. 点击'注册'按钮"
    ],
    "expected_result":[
      "注册失败,提示'用户名长度需在4-20字符之间'",
      "页面停留在注册/登录页面"
    ]
},
{
    "precondition":"用户未注册,系统处于未登录状态",
    "operation_procedure":[
      "1. 访问系统首页",
      "2. 输入用户名:existing_user",
      "3. 输入密码:Passw0rd!",
      "4. 点击'注册'按钮",
      "5. 再次输入相同用户名和密码点击'注册'按钮"
    ],
    "expected_result":[
      "第二次注册时提示'用户名已存在'",
      "页面停留在注册/登录页面"
    ]
},
{
    "precondition":"用户未注册,系统处于未登录状态",
    "operation_procedure":[
      "1. 访问系统首页",
      "2. 输入用户名:user😊name",
      "3. 输入密码:Passw0rd!",
      "4. 点击'注册'按钮"
    ],
    "expected_result":[
      "注册失败,提示'用户名包含非法字符'",
      "页面停留在注册/登录页面"
    ]
},
{
    "precondition":"用户未注册,系统处于未登录状态",
    "operation_procedure":[
      "1. 访问系统首页",
      "2. 输入不存在的用户名:new_user",
      "3. 输入错误密码:wrongPass",
      "4. 点击'登录'按钮"
    ],
    "expected_result":[
      "登录失败,提示'用户名或密码错误'",
      "页面停留在注册/登录页面"
    ]
},
{
    "precondition":"用户已注册,系统处于未登录状态",
    "operation_procedure":[
      "1. 访问系统首页",
      "2. 输入已注册用户名:valid_user",
      "3. 输入正确密码:Passw0rd!",
      "4. 点击'登录'按钮"
    ],
    "expected_result":[
      "登录成功,跳转至带图片的首页",
      "页面显示退出按钮"
    ]
},
{
    "precondition":"新用户完成注册,系统处于未登录状态",
    "operation_procedure":[
      "1. 访问系统首页",
      "2. 输入符合要求的用户名:new_user_123",
      "3. 输入符合要求的密码:Passw0rd!",
      "4. 点击'注册'按钮",
      "5. 使用相同凭证点击'登录'按钮"
    ],
    "expected_result":[
      "注册成功后自动清除表单",
      "登录成功跳转至首页"
    ]
},
{
    "precondition":"用户已登录,处于首页",
    "operation_procedure":[
      "1. 点击'退出'按钮"
    ],
    "expected_result":[
      "返回注册/登录页面",
      "用户会话终止"
    ]
},
{
    "precondition":"用户未注册,系统处于未登录状态",
    "operation_procedure":[
      "1. 访问系统首页",
      "2. 输入有效用户名:valid_user",
      "3. 输入无效密码:password",
      "4. 点击'注册'按钮"
    ],
    "expected_result":[
      "注册失败,提示'密码需包含大写字母、小写字母、数字和特殊字符'",
      "页面停留在注册/登录页面"
    ]
}
]

这里可以进一步将json数据转成xmind或excel格式的文件。

收益

价值思考? 几个方向:

  • • AI辅助建模(状态机、类图),提升研发需求建模效率和规范
  • • 释放助理生成测试用例的生产力,转变为review/修改用例的角色
  • • AI辅助生成符合业务规则的可测试数据,提升构造数据的效率

经验总结

  • • 使用AI帮助我们进行自然语言推理和图解析
  • • 要让AI生成的测试用例更准确,需要精确描述需求,对需求进行建模,描述清楚业务规则
  • • 使用AI编程工具帮助我们开发工具,比如有明确规则的工具开发
  • • 通过搭建AI工作流完成需求穿刺

附录

  • • Dify文档: https://docs.dify.ai/zh-hans
引用链接

[1] Dify: https://docs.dify.ai/zh-hans [2] : https://cdn.nlark.com/yuque/0/2025/png/1214279/1746627272185-d8f5b7c5-666f-4686-8985-77eeaf0a6b43.png [3] AI大模型生成的图表为什么倾向使用Mermaid格式?: https://juejin.cn/post/7475617896543338511

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-05-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 巫山老妖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 目标
  • 实践案例:登录注册流程
    • 自然语言描述需求
  • 如何找全测试路径
    • 使用LLM生成mermaid格式的状态机描述
    • 使用AI帮我们开发工具
  • 如何生成测试用例
    • 用例关键要素
    • LLM 生成测试用例
  • 收益
  • 经验总结
  • 附录
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档