ai理解产品需求生成测试用例-deepseek

ai:把用例捡起来!把用例捡起来!

给大模型需求文档,让它完成设计用例,编写用例,包括功能用例、接口用例、自动化测试用例,自执行~最后发送至工作群中

直接使用deepseek即可

 

执行一下看看:

 调用ds分析需求:

 生成功能/接口用例:

 生成自动化用例:

 

 

 看一下自动生成的功能用例和接口用例:

# 为了验证用户登录功能,我们将使用 `pytest` 框架和 `requests` 库来进行 API 测试。以下是一个示例代码,展示了如何实现这个测试用例。
#
# ### 安装依赖
# 首先,确保你已经安装了 `pytest` 和 `requests` 库。如果没有安装,可以使用以下命令进行安装:
#
# ```bash
# pip install pytest requests
# ```
#
# ### 测试代码
#
# ```python
import pytest
import requests

# 定义登录API的URL
LOGIN_URL = "http://127.0.0.1:5000/login"

# 测试用例:验证用户登录功能
def test_user_login():
    # 定义测试数据
    username = "user1"
    password = "password1"
    
    # 构造请求体
    payload = {
        "username": username,
        "password": password
    }
    
    # 发送POST请求到登录API端点
    response = requests.post(LOGIN_URL, json=payload)
    
    # 验证响应状态码是否为200
    assert response.status_code == 200, f"Expected status code 200, but got {response.status_code}"
    
    # 解析API响应
    response_data = response.json()
    
    # 验证响应中是否包含预期的字段
    assert "token" in response_data, "Response does not contain 'token' field"
    assert "user_id" in response_data, "Response does not contain 'user_id' field"
    
    # 验证token和user_id是否有效
    assert isinstance(response_data["token"], str), "Token is not a string"
    assert isinstance(response_data["user_id"], int), "User ID is not an integer"

# 运行测试
if __name__ == "__main__":
    pytest.main()
# ```
#
# ### 代码说明
#
# 1. **LOGIN_URL**: 这是登录API的URL,你需要将其替换为实际的API端点。
#
# 2. **test_user_login**: 这是测试函数,使用 `pytest` 框架进行测试。
#
# 3. **payload**: 这是发送到API的请求体,包含用户名和密码。
#
# 4. **requests.post**: 发送POST请求到登录API端点。
#
# 5. **assert response.status_code == 200**: 验证响应状态码是否为200,表示请求成功。
#
# 6. **response.json()**: 解析API响应为JSON格式。
#
# 7. **assert "token" in response_data**: 验证响应中是否包含 `token` 字段。
#
# 8. **assert "user_id" in response_data**: 验证响应中是否包含 `user_id` 字段。
#
# 9. **assert isinstance(response_data["token"], str)**: 验证 `token` 是否为字符串类型。
#
# 10. **assert isinstance(response_data["user_id"], int)**: 验证 `user_id` 是否为整数类型。
#
# ### 运行测试
#
# 你可以通过以下命令运行测试:
#
# ```bash
# pytest test_login.py
# ```
#
# 如果所有断言都通过,测试将成功完成。如果有任何断言失败,`pytest` 将输出详细的错误信息,帮助你定位问题。

"""
自动化测试执行器
"""
import pytest
import os
import sys
from datetime import datetime
from typing import List, Dict
import json
from dotenv import load_dotenv

# 添加项目根目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.abspath(os.path.join(current_dir, "../../../.."))
src_dir = os.path.join(project_root, "ai-test-generator", "src")
sys.path.insert(0, src_dir)

# 加载环境变量
env_path = os.path.join(project_root, "ai-test-generator", ".env")
print(f"正在加载环境变量文件: {env_path}")
load_dotenv(env_path)

try:
    from test_reporter import TestReporter, TestResult
except ImportError as e:
    print(f"错误:无法导入test_reporter模块。请确保项目结构正确。")
    print(f"当前目录: {current_dir}")
    print(f"项目根目录: {project_root}")
    print(f"src目录: {src_dir}")
    print(f"Python路径: {sys.path}")
    raise e

def run_tests(test_files: List[str] = None) -> bool:
    """
    运行测试用例并生成报告
    
    Args:
        test_files: 要运行的测试文件列表,如果为None则运行所有测试
        
    Returns:
        bool: 所有测试是否通过
    """
    print("\n开始执行测试...")
    
    # 初始化测试报告器
    reporter = TestReporter()
    
    # 如果没有指定测试文件,则运行所有测试
    if test_files is None:
        test_files = [f for f in os.listdir(os.path.dirname(__file__)) 
                     if f.endswith('_test.py') and f != '_automation.py']
        print(f"将运行所有测试文件: {test_files}")
    
    all_passed = True
    
    for test_file in test_files:
        test_path = os.path.join(os.path.dirname(__file__), test_file)
        test_id = os.path.splitext(test_file)[0]
        
        print(f"\n执行测试文件: {test_file}")
        
        # 记录测试执行
        start_time = datetime.now()
        try:
            # 运行测试
            result = pytest.main([test_path, '-v'])
            status = 'passed' if result == 0 else 'failed'
            error_msg = None if result == 0 else 'Test execution failed'
            if result != 0:
                all_passed = False
            print(f"测试执行结果: {status}")
        except Exception as e:
            status = 'error'
            error_msg = str(e)
            all_passed = False
            print(f"测试执行出错: {e}")
        
        duration = (datetime.now() - start_time).total_seconds()
        
        # 记录测试结果
        reporter.add_test_result(TestResult(
            test_id=test_id,
            test_name=test_file,
            test_type='api',
            status=status,
            duration=duration,
            error_message=error_msg,
            start_time=start_time.isoformat(),
            end_time=datetime.now().isoformat()
        ))
    
    # 生成并保存报告
    reports_dir = os.path.join(project_root, "ai-test-generator", "test_cases", "reports")
    reporter.save_report(reports_dir)
    
    # 发送钉钉通知
    print("\n准备发送测试报告...")
    reporter.send_dingtalk_notification()
    
    print(f"\n测试执行完成,{'全部通过' if all_passed else '存在失败'}")
    return all_passed

if __name__ == "__main__":
    # 获取命令行参数中的测试文件
    test_files = sys.argv[1:] if len(sys.argv) > 1 else None
    
    # 运行测试
    success = run_tests(test_files)
    
    # 设置退出码
    sys.exit(0 if success else 1)
metadata:
  id: FUNC_1__20250313_155452
  type: functional
  feature: 1._**用户登录功能**:
  generated_time: '2025-03-13T15:54:52.411287'
test_case:
  title: 用户登录功能测试
  description: 验证用户登录功能是否正常工作,确保用户能够通过正确的用户名和密码成功登录系统。
  test_type: functional
  prerequisites:
  - 系统已安装并运行
  - 用户已注册并拥有有效的用户名和密码
  steps:
  - 打开登录页面
  - 输入有效的用户名
  - 输入有效的密码
  - 点击登录按钮
  expected_results:
  - 登录页面成功加载
  - 用户名和密码输入框接受输入
  - 登录按钮可点击
  - 用户成功登录并跳转到主页
  test_data:
    username: testuser
    password: testpassword

本地简单写了个登录接口,用生成的用例跑一下,看看收集结果以及发送结果的功能

接口:

 改改用例里面的url和参数:

 

 

 

 report中的内容:

发送的wehook通知:

 代码:

main.py

  1 import os
  2 import sys
  3 from test_generator import AITestGenerator
  4 from test_case_manager import TestCaseManager
  5 from dotenv import load_dotenv
  6 
  7 def read_requirements():
  8     """从用户输入读取需求文档"""
  9     print("\n请输入需求文档(输入完成后请按Ctrl+D或Ctrl+Z结束):")
 10     lines = []
 11     try:
 12         while True:
 13             line = input()
 14             lines.append(line)
 15     except (EOFError, KeyboardInterrupt):
 16         pass
 17     return "\n".join(lines)
 18 
 19 def read_requirements_from_file(file_path):
 20     """从文件读取需求文档"""
 21     with open(file_path, 'r', encoding='utf-8') as f:
 22         return f.read()
 23 
 24 def main():
 25     # 加载环境变量
 26     load_dotenv()
 27     
 28     # 初始化生成器和管理器
 29     generator = AITestGenerator()
 30     manager = TestCaseManager()
 31     
 32     # 解析命令行参数
 33     if len(sys.argv) < 2:
 34         print("用法: python main.py <command> [options]")
 35         print("命令:")
 36         print("  generate <requirements_file>  - 从需求文件生成测试用例")
 37         print("  generate                      - 从用户输入生成测试用例")
 38         print("  run [type] [feature]         - 执行测试用例")
 39         print("选项:")
 40         print("  type: functional 或 api")
 41         print("  feature: 特定功能名称")
 42         return
 43     
 44     command = sys.argv[1]
 45     
 46     if command == "generate":
 47         # 获取需求文档
 48         if len(sys.argv) > 2:
 49             # 从文件读取需求
 50             requirements = read_requirements_from_file(sys.argv[2])
 51         else:
 52             # 从用户输入读取需求
 53             requirements = read_requirements()
 54         
 55         if not requirements.strip():
 56             print("错误:需求文档不能为空")
 57             return
 58         
 59         # 分析需求
 60         print("\n正在分析需求文档...")
 61         features = generator.analyze_requirements(requirements)
 62         
 63         print("\n提取的功能点:")
 64         for i, feature in enumerate(features, 1):
 65             print(f"{i}. {feature}")
 66         
 67         # 为每个功能点生成测试用例
 68         test_types = ['functional', 'api', 'automation']
 69         for feature in features:
 70             feature_name = feature.strip().lower().replace(' ', '_')
 71             
 72             for test_type in test_types:
 73                 print(f"\n正在生成 {test_type} 测试用例,功能点:{feature}")
 74                 test_case = generator.generate_test_cases(feature, test_type)
 75                 
 76                 # 保存测试用例
 77                 if test_type == 'functional':
 78                     # 保存为YAML文件
 79                     file_path = manager.save_functional_test(test_case.dict(), feature_name)
 80                     print(f"功能测试用例已保存到: {file_path}")
 81                     
 82                 elif test_type == 'api':
 83                     # 保存为Python测试文件和JSON数据文件
 84                     case_path, data_path = manager.save_api_test(test_case.dict(), feature_name)
 85                     print(f"API测试用例已保存到: {case_path}")
 86                     print(f"API测试数据已保存到: {data_path}")
 87                     
 88                 elif test_type == 'automation':
 89                     # 生成自动化测试代码
 90                     automation_code = generator.generate_automation_code(test_case)
 91                     # 保存自动化测试代码
 92                     file_path = os.path.join(manager.api_cases_dir, f"{feature_name}_automation.py")
 93                     with open(file_path, 'w', encoding='utf-8') as f:
 94                         f.write(automation_code)
 95                     print(f"自动化测试代码已保存到: {file_path}")
 96         
 97         print("\n所有测试用例生成完成!")
 98         print("\n您可以在以下目录找到生成的测试用例:")
 99         print(f"功能测试用例: {manager.functional_dir}")
100         print(f"API测试用例: {manager.api_cases_dir}")
101         print(f"API测试数据: {manager.api_data_dir}")
102         
103     elif command == "run":
104         # 执行测试用例
105         test_type = sys.argv[2] if len(sys.argv) > 2 else None
106         feature = sys.argv[3] if len(sys.argv) > 3 else None
107         
108         print(f"\n开始执行测试{'(' + test_type + ')' if test_type else ''}")
109         if feature:
110             print(f"功能点: {feature}")
111         
112         success = manager.execute_tests(test_type, feature)
113         
114         print("\n测试执行完成!")
115         print(f"测试{'全部通过' if success else '存在失败'}")
116         print(f"详细报告已保存至: {manager.reports_dir}")
117     
118     else:
119         print(f"未知命令: {command}")
120         return
121 
122 if __name__ == "__main__":
123     main() 
View Code

test_case_manager.py

  1 import os
  2 import sys
  3 from test_generator import AITestGenerator
  4 from test_case_manager import TestCaseManager
  5 from dotenv import load_dotenv
  6 
  7 def read_requirements():
  8     """从用户输入读取需求文档"""
  9     print("\n请输入需求文档(输入完成后请按Ctrl+D或Ctrl+Z结束):")
 10     lines = []
 11     try:
 12         while True:
 13             line = input()
 14             lines.append(line)
 15     except (EOFError, KeyboardInterrupt):
 16         pass
 17     return "\n".join(lines)
 18 
 19 def read_requirements_from_file(file_path):
 20     """从文件读取需求文档"""
 21     with open(file_path, 'r', encoding='utf-8') as f:
 22         return f.read()
 23 
 24 def main():
 25     # 加载环境变量
 26     load_dotenv()
 27     
 28     # 初始化生成器和管理器
 29     generator = AITestGenerator()
 30     manager = TestCaseManager()
 31     
 32     # 解析命令行参数
 33     if len(sys.argv) < 2:
 34         print("用法: python main.py <command> [options]")
 35         print("命令:")
 36         print("  generate <requirements_file>  - 从需求文件生成测试用例")
 37         print("  generate                      - 从用户输入生成测试用例")
 38         print("  run [type] [feature]         - 执行测试用例")
 39         print("选项:")
 40         print("  type: functional 或 api")
 41         print("  feature: 特定功能名称")
 42         return
 43     
 44     command = sys.argv[1]
 45     
 46     if command == "generate":
 47         # 获取需求文档
 48         if len(sys.argv) > 2:
 49             # 从文件读取需求
 50             requirements = read_requirements_from_file(sys.argv[2])
 51         else:
 52             # 从用户输入读取需求
 53             requirements = read_requirements()
 54         
 55         if not requirements.strip():
 56             print("错误:需求文档不能为空")
 57             return
 58         
 59         # 分析需求
 60         print("\n正在分析需求文档...")
 61         features = generator.analyze_requirements(requirements)
 62         
 63         print("\n提取的功能点:")
 64         for i, feature in enumerate(features, 1):
 65             print(f"{i}. {feature}")
 66         
 67         # 为每个功能点生成测试用例
 68         test_types = ['functional', 'api', 'automation']
 69         for feature in features:
 70             feature_name = feature.strip().lower().replace(' ', '_')
 71             
 72             for test_type in test_types:
 73                 print(f"\n正在生成 {test_type} 测试用例,功能点:{feature}")
 74                 test_case = generator.generate_test_cases(feature, test_type)
 75                 
 76                 # 保存测试用例
 77                 if test_type == 'functional':
 78                     # 保存为YAML文件
 79                     file_path = manager.save_functional_test(test_case.dict(), feature_name)
 80                     print(f"功能测试用例已保存到: {file_path}")
 81                     
 82                 elif test_type == 'api':
 83                     # 保存为Python测试文件和JSON数据文件
 84                     case_path, data_path = manager.save_api_test(test_case.dict(), feature_name)
 85                     print(f"API测试用例已保存到: {case_path}")
 86                     print(f"API测试数据已保存到: {data_path}")
 87                     
 88                 elif test_type == 'automation':
 89                     # 生成自动化测试代码
 90                     automation_code = generator.generate_automation_code(test_case)
 91                     # 保存自动化测试代码
 92                     file_path = os.path.join(manager.api_cases_dir, f"{feature_name}_automation.py")
 93                     with open(file_path, 'w', encoding='utf-8') as f:
 94                         f.write(automation_code)
 95                     print(f"自动化测试代码已保存到: {file_path}")
 96         
 97         print("\n所有测试用例生成完成!")
 98         print("\n您可以在以下目录找到生成的测试用例:")
 99         print(f"功能测试用例: {manager.functional_dir}")
100         print(f"API测试用例: {manager.api_cases_dir}")
101         print(f"API测试数据: {manager.api_data_dir}")
102         
103     elif command == "run":
104         # 执行测试用例
105         test_type = sys.argv[2] if len(sys.argv) > 2 else None
106         feature = sys.argv[3] if len(sys.argv) > 3 else None
107         
108         print(f"\n开始执行测试{'(' + test_type + ')' if test_type else ''}")
109         if feature:
110             print(f"功能点: {feature}")
111         
112         success = manager.execute_tests(test_type, feature)
113         
114         print("\n测试执行完成!")
115         print(f"测试{'全部通过' if success else '存在失败'}")
116         print(f"详细报告已保存至: {manager.reports_dir}")
117     
118     else:
119         print(f"未知命令: {command}")
120         return
121 
122 if __name__ == "__main__":
123     main() 
View Code

test_generator.py

  1 import os
  2 import sys
  3 import time
  4 import json
  5 import re
  6 from typing import List, Dict
  7 import requests
  8 import httpx
  9 from dotenv import load_dotenv
 10 from pydantic import BaseModel
 11 
 12 class TestCase(BaseModel):
 13     """测试用例模型"""
 14     title: str
 15     description: str
 16     test_type: str  # 'functional', 'api', 'automation'
 17     prerequisites: List[str]
 18     steps: List[str]
 19     expected_results: List[str]
 20     test_data: Dict[str, str]
 21 
 22 class AITestGenerator:
 23     def __init__(self):
 24         load_dotenv()
 25         self.api_key = os.getenv("DEEPSEEK_API_KEY")
 26         if not self.api_key:
 27             print("错误: 未找到DEEPSEEK_API_KEY环境变量。请确保.env文件中包含有效的API密钥。")
 28             sys.exit(1)
 29             
 30         self.url = "https://api.deepseek.com/chat/completions"
 31         self.headers = {
 32             "Content-Type": "application/json",
 33             "Authorization": f"Bearer {self.api_key}"
 34         }
 35         print("成功初始化DeepSeek API配置")
 36         
 37     def _format_json_prompt(self, content: str) -> str:
 38         """格式化提示词,确保AI生成标准JSON格式响应"""
 39         return f"""请严格按照以下JSON格式生成响应:
 40 {{
 41     "title": "测试用例标题",
 42     "description": "测试用例描述",
 43     "test_type": "api",
 44     "prerequisites": ["前置条件1", "前置条件2"],
 45     "steps": ["步骤1", "步骤2"],
 46     "expected_results": ["预期结果1", "预期结果2"],
 47     "test_data": {{
 48         "key1": "value1",
 49         "key2": "value2"
 50     }}
 51 }}
 52 
 53 请基于以下内容生成测试用例(请确保生成的是合法的JSON格式):
 54 {content}"""
 55 
 56     def _extract_json_from_text(self, text: str) -> str:
 57         """从文本中提取JSON部分"""
 58         # 查找第一个 { 和最后一个 } 之间的内容
 59         json_match = re.search(r'({[\s\S]*})', text)
 60         if json_match:
 61             return json_match.group(1)
 62         return text
 63 
 64     def _clean_json_string(self, json_str: str) -> str:
 65         """清理和修复常见的JSON格式问题"""
 66         # 移除可能导致解析错误的Unicode字符
 67         json_str = json_str.encode('utf-8', 'ignore').decode('utf-8')
 68         
 69         # 修复常见的格式问题
 70         json_str = re.sub(r'(?<!\\)"(\w+)"(?=:)', r'"\1"', json_str)  # 修复键的引号
 71         json_str = re.sub(r'(?<=: )"([^"]*?)(?<!\\)"(?=[,}\]])', r'"\1"', json_str)  # 修复值的引号
 72         json_str = re.sub(r',(\s*[}\]])', r'\1', json_str)  # 移除尾随逗号
 73         
 74         return json_str
 75 
 76     def _parse_ai_response(self, response: str) -> dict:
 77         """解析AI响应并转换为字典格式"""
 78         try:
 79             # 首先尝试直接解析
 80             return json.loads(response)
 81         except json.JSONDecodeError:
 82             print("直接解析JSON失败,尝试修复格式...")
 83             
 84             try:
 85                 # 提取JSON部分
 86                 json_str = self._extract_json_from_text(response)
 87                 # 清理和修复JSON字符串
 88                 cleaned_json = self._clean_json_string(json_str)
 89                 # 再次尝试解析
 90                 return json.loads(cleaned_json)
 91             except json.JSONDecodeError as e:
 92                 print(f"JSON解析失败: {e}")
 93                 print("原始响应:", response)
 94                 
 95                 # 构建一个基本的测试用例结构
 96                 return self._create_fallback_test_case(response)
 97 
 98     def _create_fallback_test_case(self, raw_response: str) -> dict:
 99         """当JSON解析失败时创建基本的测试用例结构"""
100         # 使用正则表达式提取可能的测试步骤
101         steps = re.findall(r'\d+\.(.*?)(?=\d+\.|$)', raw_response, re.DOTALL)
102         steps = [step.strip() for step in steps if step.strip()]
103         
104         # 尝试提取标题
105         title_match = re.search(r'标题[::](.*?)(?=\n|$)', raw_response)
106         title = title_match.group(1).strip() if title_match else "未命名测试用例"
107         
108         # 尝试提取描述
109         desc_match = re.search(r'描述[::](.*?)(?=\n|$)', raw_response)
110         description = desc_match.group(1).strip() if desc_match else raw_response[:100] + "..."
111         
112         return {
113             "title": title,
114             "description": description,
115             "test_type": "api",
116             "prerequisites": ["环境已正确配置", "服务正常运行"],
117             "steps": steps if steps else ["请手动提取测试步骤"],
118             "expected_results": ["请根据业务需求验证结果"],
119             "test_data": {
120                 "note": "请根据实际情况补充测试数据"
121             }
122         }
123 
124     def analyze_requirements(self, requirements_text: str) -> List[str]:
125         """分析需求文档,提取关键功能点"""
126         try:
127             print("正在分析需求文档...")
128             data = {
129                 "model": "deepseek-chat",
130                 "messages": [
131                     {"role": "system", "content": "你是一个专业的测试分析师,请帮助分析需求文档并提取关键功能点。"},
132                     {"role": "user", "content": f"请分析以下需求文档并列出关键功能点:\n{requirements_text}"}
133                 ],
134                 "stream": False
135             }
136             
137             response = requests.post(self.url, headers=self.headers, json=data)
138             
139             if response.status_code == 200:
140                 result = response.json()
141                 print("需求分析完成")
142                 return result['choices'][0]['message']['content'].split("\n")
143             else:
144                 print(f"API请求失败,状态码: {response.status_code}")
145                 print(f"错误信息: {response.text}")
146                 return ["API请求失败"]
147                 
148         except Exception as e:
149             print(f"分析需求时出错: {e}")
150             return ["分析需求时出错"]
151 
152     def generate_test_cases(self, feature: str, test_type: str) -> TestCase:
153         """根据功能点生成测试用例"""
154         prompt = self._create_test_case_prompt(feature, test_type)
155         try:
156             print(f"正在生成{test_type}测试用例...")
157             data = {
158                 "model": "deepseek-chat",
159                 "messages": [
160                     {"role": "system", "content": "你是一个专业的测试工程师,请根据功能点生成详细的测试用例。"},
161                     {"role": "user", "content": self._format_json_prompt(prompt)}
162                 ],
163                 "stream": False
164             }
165             
166             response = requests.post(self.url, headers=self.headers, json=data)
167             
168             if response.status_code == 200:
169                 result = response.json()
170                 print("测试用例生成完成")
171                 test_case_dict = self._parse_ai_response(result['choices'][0]['message']['content'])
172                 return TestCase(**test_case_dict)
173             else:
174                 print(f"API请求失败,状态码: {response.status_code}")
175                 print(f"错误信息: {response.text}")
176                 raise Exception("API请求失败")
177                 
178         except Exception as e:
179             print(f"生成测试用例时出错: {e}")
180             # 返回一个默认的测试用例
181             return TestCase(
182                 title=f"生成失败 - {feature}",
183                 description="由于API连接问题,无法生成测试用例",
184                 test_type=test_type,
185                 prerequisites=["无"],
186                 steps=["无"],
187                 expected_results=["无"],
188                 test_data={}
189             )
190 
191     def generate_automation_code(self, test_case: TestCase) -> str:
192         """生成自动化测试代码"""
193         prompt = f"""请为以下测试用例生成Python自动化测试代码:
194         测试标题:{test_case.title}
195         测试步骤:
196         {chr(10).join(test_case.steps)}
197         
198         请使用pytest框架,并根据测试类型选择合适的工具(Selenium用于UI测试,requests用于API测试)。
199         """
200         
201         try:
202             print("正在生成自动化测试代码...")
203             data = {
204                 "model": "deepseek-chat",
205                 "messages": [
206                     {"role": "system", "content": "你是一个专业的自动化测试工程师,请生成Python自动化测试代码。"},
207                     {"role": "user", "content": prompt}
208                 ],
209                 "stream": False
210             }
211             
212             response = requests.post(self.url, headers=self.headers, json=data)
213             
214             if response.status_code == 200:
215                 result = response.json()
216                 print("自动化测试代码生成完成")
217                 return result['choices'][0]['message']['content']
218             else:
219                 print(f"API请求失败,状态码: {response.status_code}")
220                 print(f"错误信息: {response.text}")
221                 raise Exception("API请求失败")
222                 
223         except Exception as e:
224             print(f"生成自动化测试代码时出错: {e}")
225             return "由于API连接问题,无法生成自动化测试代码"
226 
227     def _create_test_case_prompt(self, feature: str, test_type: str) -> str:
228         """创建生成测试用例的提示词"""
229         if test_type == 'api':
230             return f"""请为以下API功能点生成测试用例:
231             功能:{feature}
232             
233             请包含以下信息:
234             1. API接口名称和路径
235             2. 请求方法(GET/POST等)
236             3. 请求参数和格式
237             4. 预期响应和状态码
238             5. 异常情况测试
239             6. 测试数据示例
240             
241             请确保生成的响应是合法的JSON格式。
242             """
243         else:
244             return f"""请为以下功能点生成{test_type}测试用例:
245             功能:{feature}
246             
247             请包含以下信息:
248             1. 测试用例标题
249             2. 测试描述
250             3. 前置条件
251             4. 测试步骤
252             5. 预期结果
253             6. 测试数据
254             
255             请确保生成的响应是合法的JSON格式。
256             """ 
View Code

test_report.py

  1 import os
  2 import json
  3 import requests
  4 import time
  5 import hmac
  6 import hashlib
  7 import base64
  8 import urllib.parse
  9 from datetime import datetime
 10 from typing import Dict, List, Optional
 11 from dataclasses import dataclass
 12 from collections import defaultdict
 13 from dotenv import load_dotenv
 14 
 15 @dataclass
 16 class TestResult:
 17     """Test execution result data structure"""
 18     test_id: str
 19     test_name: str
 20     test_type: str
 21     status: str  # 'passed', 'failed', 'skipped', 'error'
 22     duration: float
 23     error_message: Optional[str] = None
 24     start_time: Optional[str] = None
 25     end_time: Optional[str] = None
 26 
 27 class TestReporter:
 28     """Test execution reporter that collects test results and sends notifications.
 29     
 30     This class provides functionality to:
 31     - Collect test execution results
 32     - Generate execution statistics
 33     - Send notifications via DingTalk webhook
 34     """
 35     
 36     def __init__(self, dingtalk_webhook: Optional[str] = None):
 37         """Initialize the test reporter.
 38         
 39         Args:
 40             dingtalk_webhook: DingTalk webhook URL for sending notifications
 41         """
 42         print("\n初始化测试报告器...")
 43         
 44         # 加载环境变量
 45         print("正在加载环境变量...")
 46         load_dotenv()
 47         
 48         # 获取钉钉webhook配置
 49         self.webhook_url = dingtalk_webhook or os.getenv("DINGTALK_WEBHOOK")
 50         self.secret = os.getenv("DINGTALK_SECRET")
 51         self.keyword = os.getenv("DINGTALK_KEYWORD")
 52         
 53         if not self.webhook_url:
 54             print("警告: 未配置钉钉Webhook URL,测试报告将不会发送到钉钉群")
 55             print("请在.env文件中设置DINGTALK_WEBHOOK环境变量")
 56         else:
 57             print(f"已配置钉钉Webhook URL: {self.webhook_url[:30]}...")
 58             if self.secret:
 59                 print("已配置加签密钥")
 60             if self.keyword:
 61                 print(f"已配置关键字: {self.keyword}")
 62         
 63         self.test_results: List[TestResult] = []
 64         self.start_time = datetime.now()
 65         print("测试报告器初始化完成\n")
 66     
 67     def _generate_sign(self) -> str:
 68         """Generate DingTalk signature.
 69         
 70         Returns:
 71             str: The signed URL with timestamp and sign parameters
 72         """
 73         timestamp = str(round(time.time() * 1000))
 74         secret_enc = self.secret.encode('utf-8')
 75         string_to_sign = '{}\n{}'.format(timestamp, self.secret)
 76         string_to_sign_enc = string_to_sign.encode('utf-8')
 77         hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
 78         sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
 79         
 80         # 构建完整的URL
 81         if '?' in self.webhook_url:
 82             url = f"{self.webhook_url}&timestamp={timestamp}&sign={sign}"
 83         else:
 84             url = f"{self.webhook_url}?timestamp={timestamp}&sign={sign}"
 85         return url
 86     
 87     def add_test_result(self, result: TestResult):
 88         """Add a test execution result.
 89         
 90         Args:
 91             result: TestResult object containing execution details
 92         """
 93         self.test_results.append(result)
 94         print(f"添加测试结果: {result.test_name} - {result.status}")
 95     
 96     def generate_statistics(self) -> Dict:
 97         """Generate test execution statistics.
 98         
 99         Returns:
100             Dictionary containing execution statistics
101         """
102         stats = {
103             "total": len(self.test_results),
104             "passed": 0,
105             "failed": 0,
106             "skipped": 0,
107             "error": 0,
108             "duration": 0.0,
109             "by_type": defaultdict(lambda: {"total": 0, "passed": 0, "failed": 0, "skipped": 0, "error": 0})
110         }
111         
112         for result in self.test_results:
113             stats[result.status] += 1
114             stats["duration"] += result.duration
115             
116             # Update type-specific statistics
117             type_stats = stats["by_type"][result.test_type]
118             type_stats["total"] += 1
119             type_stats[result.status] += 1
120         
121         return dict(stats)
122     
123     def _generate_markdown_report(self) -> str:
124         """Generate markdown formatted test execution report.
125         
126         Returns:
127             Markdown formatted string containing the test report
128         """
129         stats = self.generate_statistics()
130         end_time = datetime.now()
131         duration = (end_time - self.start_time).total_seconds()
132         
133         # 添加关键字前缀
134         keyword_prefix = f"【{self.keyword}】" if self.keyword else ""
135         
136         # Create markdown report
137         report = [
138             f"{keyword_prefix}## 测试执行报告",
139             f"### 执行时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}",
140             f"### 执行时长: {duration:.2f} 秒",
141             "",
142             "### 总体统计",
143             f"- 总用例数: {stats['total']}",
144             f"- 通过: {stats['passed']} ✅",
145             f"- 失败: {stats['failed']} ❌",
146             f"- 跳过: {stats['skipped']} ⚠️",
147             f"- 错误: {stats['error']} 🚫",
148             "",
149             "### 分类统计"
150         ]
151         
152         # Add type-specific statistics
153         for test_type, type_stats in stats["by_type"].items():
154             report.extend([
155                 f"#### {test_type}测试",
156                 f"- 总数: {type_stats['total']}",
157                 f"- 通过: {type_stats['passed']} ✅",
158                 f"- 失败: {type_stats['failed']} ❌",
159                 f"- 跳过: {type_stats['skipped']} ⚠️",
160                 f"- 错误: {type_stats['error']} 🚫"
161             ])
162         
163         # Add failed test details
164         failed_tests = [r for r in self.test_results if r.status in ('failed', 'error')]
165         if failed_tests:
166             report.extend([
167                 "",
168                 "### 失败用例详情"
169             ])
170             for test in failed_tests:
171                 report.extend([
172                     f"#### {test.test_name}",
173                     f"- ID: {test.test_id}",
174                     f"- 类型: {test.test_type}",
175                     f"- 状态: {test.status}",
176                     f"- 错误信息: {test.error_message or '无'}"
177                 ])
178         
179         return "\n".join(report)
180     
181     def send_dingtalk_notification(self) -> bool:
182         """Send test execution report to DingTalk group.
183         
184         Returns:
185             bool: True if notification was sent successfully, False otherwise
186         """
187         print("\n准备发送钉钉通知...")
188         
189         if not self.webhook_url:
190             print("错误: 未配置钉钉Webhook URL")
191             return False
192         
193         try:
194             markdown_content = self._generate_markdown_report()
195             stats = self.generate_statistics()
196             
197             # 准备请求URL(处理加签)
198             request_url = self._generate_sign() if self.secret else self.webhook_url
199             
200             # Prepare message
201             message = {
202                 "msgtype": "markdown",
203                 "markdown": {
204                     "title": "测试执行报告",
205                     "text": markdown_content
206                 },
207                 "at": {
208                     "isAtAll": stats["failed"] > 0 or stats["error"] > 0  # @所有人如果有失败用例
209                 }
210             }
211             
212             print("正在发送钉钉通知...")
213             print(f"消息内容预览:\n{markdown_content[:200]}...")
214             
215             # Send notification
216             response = requests.post(
217                 request_url,
218                 json=message,
219                 headers={"Content-Type": "application/json"}
220             )
221             
222             if response.status_code == 200:
223                 response_data = response.json()
224                 if response_data.get('errcode') == 0:
225                     print("钉钉通知发送成功")
226                     return True
227                 else:
228                     print(f"钉钉通知发送失败: {response_data.get('errmsg')}")
229                     return False
230             else:
231                 print(f"钉钉通知发送失败: HTTP {response.status_code} - {response.text}")
232                 return False
233                 
234         except Exception as e:
235             print(f"发送钉钉通知时出错: {e}")
236             return False
237     
238     def save_report(self, output_dir: str):
239         """Save test execution report to files.
240         
241         Args:
242             output_dir: Directory to save the report files
243         """
244         print(f"\n正在保存测试报告到目录: {output_dir}")
245         os.makedirs(output_dir, exist_ok=True)
246         timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
247         
248         # Save markdown report
249         markdown_path = os.path.join(output_dir, f"test_report_{timestamp}.md")
250         with open(markdown_path, 'w', encoding='utf-8') as f:
251             f.write(self._generate_markdown_report())
252         
253         # Save JSON statistics
254         json_path = os.path.join(output_dir, f"test_stats_{timestamp}.json")
255         with open(json_path, 'w', encoding='utf-8') as f:
256             json.dump(self.generate_statistics(), f, ensure_ascii=False, indent=2)
257         
258         print(f"报告已保存至: {output_dir}") 
View Code

.env

# DeepSeek API配置
DEEPSEEK_API_KEY=xxxxxx

# 钉钉机器人配置
DINGTALK_WEBHOOK=xxxxxx

requirements.txt

deepseek-ai==0.0.1
requests>=2.32.3
httpx==0.24.1
pytest==7.4.3
selenium==4.15.2
python-dotenv==1.0.0
langchain==0.0.350
pydantic==2.5.2
black==23.11.0
pytest-html==4.1.1
PyYAML==6.0.1
jinja2==3.1.2 
posted @ 2025-03-13 17:15  RichardShi  阅读(1575)  评论(3)    收藏  举报