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()
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()
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 """
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}×tamp={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}")
.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

 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号