""" PregoPal - 测试运行器 ===================== 无需 pytest,直接运行所有测试并生成 HTML 测试报告。 """ import sys import os import time import traceback import json from datetime import datetime from pathlib import Path # 确保项目根目录在路径中 sys.path.insert(0, str(Path(__file__).parent.parent)) # ============================================================ # 测试框架(极简版) # ============================================================ class TestResult: def __init__(self, class_name, method_name, doc, passed, message=""): self.class_name = class_name self.method_name = method_name self.doc = doc self.passed = passed self.message = message @property def full_name(self): return f"{self.class_name}.{self.method_name}" class TestRunner: def __init__(self): self.results = [] self.start_time = None self.end_time = None def run(self, test_module): """运行一个测试模块中的所有测试类""" self.start_time = time.time() for name in dir(test_module): obj = getattr(test_module, name) if isinstance(obj, type) and name.startswith("Test"): self._run_test_class(obj) self.end_time = time.time() def _run_test_class(self, cls): """运行一个测试类中的所有测试方法""" for name in dir(cls): if not name.startswith("test_"): continue method = getattr(cls, name) doc = method.__doc__ or name # 每个测试方法创建独立实例,确保 setup/teardown 隔离 instance = cls() # setup if hasattr(instance, "setup_method"): try: instance.setup_method() except Exception as e: print(f" [WARN] setup_method failed for {cls.__name__}.{name}: {e}") try: method(instance) self.results.append(TestResult(cls.__name__, name, doc, True)) except Exception as e: tb = traceback.format_exc() # 安全获取错误消息(避免 GBK UnicodeEncodeError) try: msg = str(e) if str(e) else tb[:500] except UnicodeEncodeError: msg = tb[:500] self.results.append(TestResult(cls.__name__, name, doc, False, msg)) # teardown if hasattr(instance, "teardown_method"): try: instance.teardown_method() except Exception as e: print(f" [WARN] teardown_method failed for {cls.__name__}.{name}: {e}") @property def total(self): return len(self.results) @property def passed(self): return sum(1 for r in self.results if r.passed) @property def failed(self): return sum(1 for r in self.results if not r.passed) @property def duration(self): if self.start_time and self.end_time: return round(self.end_time - self.start_time, 2) return 0 def generate_html_report(self, output_path="test_report.html"): """生成 HTML 测试报告""" now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") rows = "" for r in self.results: status = "PASS" if r.passed else "FAIL" color = "#4CAF50" if r.passed else "#f44336" message = f"
{r.message}" if not r.passed else ""
rows += f"""
生成时间: {now} | 测试框架: 内置 TestRunner
| 状态 | 测试类 | 测试方法 | 描述 | 结果 | 错误信息 |
|---|