# -*- coding: utf-8 -*- import argparse import os import shutil import subprocess import sys current_file_path = os.path.abspath(__file__) project_root = os.path.abspath(os.path.join(os.path.dirname(current_file_path), '../../')) if project_root not in sys.path: sys.path.insert(0, project_root) TEST_CASE_DIR = 'joyhub_backend/test_case/TestCase' case_dir = os.path.join(project_root, TEST_CASE_DIR) ALLURE_RESULTS_DIR = os.path.join(project_root, 'joyhub_backend', 'test_case', 'reports', 'allure-results') ALLURE_REPORT_DIR = os.path.join(project_root, 'joyhub_backend', 'test_case', 'reports', 'allure-report') def ensure_dirs(): os.makedirs(ALLURE_RESULTS_DIR, exist_ok=True) os.makedirs(ALLURE_REPORT_DIR, exist_ok=True) def clean_allure_results(): if os.path.exists(ALLURE_RESULTS_DIR): shutil.rmtree(ALLURE_RESULTS_DIR) os.makedirs(ALLURE_RESULTS_DIR, exist_ok=True) def find_test_files(directory): test_files = [] for root, dirs, files in os.walk(directory): for file in files: if file.endswith('.py') and not file.startswith('__') and file != 'conftest.py': test_files.append(os.path.join(root, file)) return test_files def run_pytest(args_list): env = os.environ.copy() env['PYTHONPATH'] = project_root + (os.pathsep + env['PYTHONPATH'] if 'PYTHONPATH' in env else '') cmd = ['python', '-m', 'pytest'] + args_list print("开始执行pytest...") print("执行命令: {}".format(' '.join('"{}"'.format(item) if ' ' in item else item for item in cmd)), flush=True) result = subprocess.run(cmd, cwd=project_root, env=env) print("pytest执行结束,退出码: {}".format(result.returncode), flush=True) return result.returncode def run_tests(target=None, test_type='all'): base_args = ['-v', '--tb=short', '--alluredir={}'.format(ALLURE_RESULTS_DIR)] if test_type == 'all': print("运行所有测试用例...") test_files = find_test_files(case_dir) if not test_files: print("错误: 未找到测试文件") return 1 args = test_files + base_args elif test_type == 'dir': full_path = os.path.join(case_dir, target.replace('/', os.sep).replace('\\', os.sep)) if not os.path.exists(full_path): print("错误: 目录不存在: {}".format(full_path)) return 1 print("按目录运行: {}".format(target)) test_files = find_test_files(full_path) if not test_files: print("错误: 未找到测试文件") return 1 args = test_files + base_args elif test_type == 'file': full_path = os.path.join(case_dir, target.replace('/', os.sep).replace('\\', os.sep)) if not os.path.exists(full_path): print("错误: 文件不存在: {}".format(full_path)) return 1 print("按文件运行: {}".format(target)) args = [full_path] + base_args elif test_type == 'keyword': print("按关键字运行: {}".format(target)) test_files = find_test_files(case_dir) args = test_files + ['-k={}'.format(target)] + base_args elif test_type == 'marker': print("按pytest标记运行: {}".format(target)) test_files = find_test_files(case_dir) args = test_files + ['-m={}'.format(target)] + base_args elif test_type == 'feature': print("按Allure feature运行: {}".format(target)) test_files = find_test_files(case_dir) args = test_files + ['--allure-features={}'.format(target)] + base_args elif test_type == 'story': print("按Allure story运行: {}".format(target)) test_files = find_test_files(case_dir) args = test_files + ['--allure-stories={}'.format(target)] + base_args else: print("错误: 未知的测试类型: {}".format(test_type)) return 1 return run_pytest(args) def generate_allure_report(): print("开始生成Allure报告...", flush=True) if os.path.exists(ALLURE_REPORT_DIR): shutil.rmtree(ALLURE_REPORT_DIR) cmd = 'allure generate "{}" --output "{}"'.format(ALLURE_RESULTS_DIR, ALLURE_REPORT_DIR) print("执行命令: {}".format(cmd), flush=True) try: subprocess.run(cmd, check=True, shell=True) print("Allure报告生成成功: {}".format(ALLURE_REPORT_DIR)) print("打开报告命令: allure open \"{}\"".format(ALLURE_REPORT_DIR)) return 0 except (subprocess.CalledProcessError, FileNotFoundError, OSError) as error: print("生成Allure报告失败: {}".format(error)) print("手动执行: {}".format(cmd)) return 1 def open_allure_report(): cmd = 'allure open "{}"'.format(ALLURE_REPORT_DIR) try: subprocess.Popen(cmd, shell=True) print("Allure报告已打开: {}".format(ALLURE_REPORT_DIR)) return 0 except (FileNotFoundError, OSError) as error: print("打开Allure报告失败: {}".format(error)) return 1 def main(): parser = argparse.ArgumentParser(description='JoyHub Backend 接口自动化测试执行工具') run_group = parser.add_mutually_exclusive_group(required=False) run_group.add_argument('--feature', type=str, help='按Allure feature运行') run_group.add_argument('--story', type=str, help='按Allure story运行') run_group.add_argument('--dir', type=str, help='按目录运行(相对于TestCase目录)') run_group.add_argument('--file', type=str, help='按文件运行(相对于TestCase目录)') run_group.add_argument('--keyword', type=str, help='按关键字运行') run_group.add_argument('--marker', type=str, help='按pytest标记运行') parser.add_argument('--report', action='store_true', help='生成Allure报告') parser.add_argument('--open', action='store_true', help='打开Allure报告') parser.add_argument('--no-report', action='store_true', help='不生成Allure报告') args = parser.parse_args() ensure_dirs() clean_allure_results() if args.feature: exit_code = run_tests(args.feature, 'feature') elif args.story: exit_code = run_tests(args.story, 'story') elif args.dir: exit_code = run_tests(args.dir, 'dir') elif args.file: exit_code = run_tests(args.file, 'file') elif args.keyword: exit_code = run_tests(args.keyword, 'keyword') elif args.marker: exit_code = run_tests(args.marker, 'marker') else: exit_code = run_tests() if args.report or not args.no_report: generate_allure_report() if args.open: open_allure_report() print("=" * 80) print("测试执行完成" if exit_code == 0 else "测试执行失败,退出码: {}".format(exit_code)) print("=" * 80) sys.exit(exit_code) if __name__ == '__main__': main()