背景
目前我們的測試用例主要依賴人工生成和維護,AI時代的來臨,我們也在思考“AI如何賦能業務”,提出了如下命題:
“探索通過AI輔助生成測試用例,完成從需求到測試用例生成的穿刺”。
目標
- 找全測試路徑
- 輔助生成測試用例
實踐案例:登錄注冊流程
自然語言描述需求
需求名稱:注冊登錄流程
需求描述:
1、注冊和登錄在同一個頁面,有2個按鈕,一個注冊,一個登錄,用戶輸入用戶名、密碼進行登錄或者注冊
2、首頁:加載一張圖,有個退出按鈕,點擊則退出首頁
注:這里只是為了驗證思路,需求描述會比較簡單,實際需求考慮會更完善。
如何找全測試路徑
使用LLM生成mermaid格式的狀態機描述
使用Dify 搭建的工作流:
將前面的需求描述作為輸入參數,提供Prompt模板告訴LLM,如下所示:
LLM 生成的mermaid 狀態機描述:
stateDiagram-v2[*] --> UnregisteredUnregistered --> Registering: start_registerRegistering --> Unregistered: register_failedRegistering --> LoggingIn: register_successUnregistered --> LoggingIn: start_loginLoggingIn --> Unregistered: login_failedLoggingIn --> LoggedIn: login_successLoggedIn --> Unregistered: logoutLoggedIn --> [*]: exit
Markdown對mermaid支持友好,可以直接渲染成狀態機圖:
這里選擇Mermaid來描述狀態機的理由,主要是Mermaid天然適合文檔化,代碼輕量且無額外依賴,無需處理圖片格式的一些問題。
參考:AI大模型生成的圖表為什么傾向使用Mermaid格式?
使用AI幫我們開發工具
前面通過LLM能夠幫我們理解需求生成狀態機圖,如果想基于狀態機找全測試路徑,我們嘗試使用AI編程工具來輔助生成規則工具,來確保每次遍歷的路徑是一致的。
比如Cursor:
通過多輪的對話和人工修正,Cursor能夠很高效的幫助我生成符合預期的代碼,但仍需要人工去驗證和調試。
核心路徑生成算法:
from typing import List, Dict, Set
from abc import ABC, abstractmethodclass PathGeneratorBase(ABC):def __init__(self):self.graph = {}self.paths = []self.events = {}@abstractmethoddef parse_input(self):"""解析輸入源(Mermaid或SCXML)"""passdef generate_paths(self, max_depth: int = 15) -> List[List[str]]:"""通用的路徑生成算法"""paths = []start = self._find_start_state()visited_states = set()def dfs(current: str, path: List[str]):if len(path) > max_depth:returncurrent_transitions = self._get_transitions(current)if self._should_terminate(current, path, current_transitions):paths.append(path[:])returnvisited_states.add(current)for next_state in current_transitions:dfs(next_state, path + [next_state])visited_states.remove(current)dfs(start, [start])return self._deduplicate_paths(paths)def _find_start_state(self) -> str:"""查找起始狀態"""if 'START' in self.graph:return 'START'in_degrees = self._calculate_in_degrees()for node, degree in in_degrees.items():if degree == 0:return nodereturn Nonedef _get_transitions(self, state: str) -> List[str]:"""獲取狀態的所有可能轉換"""if state not in self.graph:return []return [target for target in self.graph[state]]def _should_terminate(self, current: str, path: List[str], transitions: List[str]) -> bool:"""判斷是否應該終止當前路徑"""return len(path) > 1 and (not transitions or current in path[:-1])def _deduplicate_paths(self, paths: List[List[str]]) -> List[List[str]]:"""去除重復路徑"""unique_paths = []path_strings = set()for path in sorted(paths, key=len):path_str = "->".join(path)if path_str not in path_strings:path_strings.add(path_str)unique_paths.append(path)return unique_pathsdef calculate_coverage(self) -> Dict:"""計算測試覆蓋率"""all_states = set(self.graph.keys())all_transitions = set()covered_states = set()covered_transitions = set()for path in self.paths:covered_states.update(path)for i in range(len(path) - 1):transition = (path[i], path[i + 1])covered_transitions.add(transition)all_transitions.add(transition)return {"state_coverage": len(covered_states) / len(all_states) * 100,"transition_coverage": len(covered_transitions) / len(all_transitions) * 100}
根據路徑生成算法遍歷生成的路徑索引:
如何生成測試用例
用例關鍵要素
- 前置條件:描述復現測試場景所需的條件;
- 操作步驟:描述測試場景下用戶的操作行為;
- 預期結果:描述測試場景下,用戶執行完操作預期得到的結果。
示例:
場景 | 前置條件 | 操作步驟 | 預期結果 |
---|---|---|---|
銀行卡支付場景 | 用戶已綁定有效銀行卡 | 在收銀臺選擇銀行卡支付 | 顯示支付成功頁面,賬戶余額減少相應金額 |
LLM 生成測試用例
工作流設計:
- 輸入需求描述和測試路徑
- 設計prompt提示詞,要求LLM按固定格式輸出測試用例
生成的測試用例結果示例:
[{"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