compliance/tests/test_test_case_registry.py
gongwenxin 331e397367 step1
2025-05-19 15:57:35 +08:00

325 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import unittest
import os
import shutil
import logging
from typing import List
# 调整导入路径以适应测试文件在 tests/ 目录下的情况
# 我们假设 tests/ 和 ddms_compliance_suite/ 在同一级别 (项目根目录下)
import sys
# 获取当前文件 (test_test_case_registry.py) 的目录 (tests/)
current_file_dir = os.path.dirname(os.path.abspath(__file__))
# 获取项目根目录 (tests/ 的上一级)
project_root = os.path.dirname(current_file_dir)
# 将项目根目录添加到 sys.path 中,以便可以找到 ddms_compliance_suite 包
if project_root not in sys.path:
sys.path.insert(0, project_root)
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
from ddms_compliance_suite.test_case_registry import TestCaseRegistry
# 为了测试,我们需要一个临时的测试用例目录
TEMP_TEST_CASES_DIR = os.path.join(current_file_dir, "temp_custom_testcases_for_registry_test")
# 禁用 TestCaseRegistry 和 BaseAPITestCase 在测试期间的 INFO 和 DEBUG 日志,除非特意捕获
# logging.getLogger("ddms_compliance_suite.test_case_registry").setLevel(logging.WARNING)
# logging.getLogger("testcase").setLevel(logging.WARNING) # BaseAPITestCase uses testcase.<id>
class TestTestCaseRegistry(unittest.TestCase):
def setUp(self):
"""在每个测试方法运行前创建临时测试用例目录。"""
if os.path.exists(TEMP_TEST_CASES_DIR):
shutil.rmtree(TEMP_TEST_CASES_DIR)
os.makedirs(TEMP_TEST_CASES_DIR)
self.registry = None # 确保每个测试都重新初始化registry
def tearDown(self):
"""在每个测试方法运行后清理临时测试用例目录。"""
if os.path.exists(TEMP_TEST_CASES_DIR):
shutil.rmtree(TEMP_TEST_CASES_DIR)
def _create_test_case_file(self, filename: str, content: str):
"""辅助方法,在临时目录中创建测试用例文件。"""
with open(os.path.join(TEMP_TEST_CASES_DIR, filename), "w", encoding="utf-8") as f:
f.write(content)
def test_init_with_non_existent_dir(self):
"""测试使用不存在的目录初始化 TestCaseRegistry。"""
non_existent_dir = os.path.join(TEMP_TEST_CASES_DIR, "_i_do_not_exist_")
with self.assertLogs(level='WARNING') as log_watcher:
registry = TestCaseRegistry(test_cases_dir=non_existent_dir)
self.assertTrue(any(f"测试用例目录不存在或不是一个目录: {non_existent_dir}" in msg for msg in log_watcher.output))
self.assertEqual(len(registry.get_all_test_case_classes()), 0)
def test_init_with_empty_dir(self):
"""测试使用空的目录初始化 TestCaseRegistry。"""
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
self.assertEqual(len(registry.get_all_test_case_classes()), 0)
# 应该有一条INFO日志表明发现完成且数量为0
def test_discover_single_valid_test_case(self):
"""测试发现单个有效的测试用例。"""
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class MyTest(BaseAPITestCase):
id = "TC-001"
name = "Test Case 1"
description = "Desc 1"
severity = TestSeverity.HIGH
tags = ["tag1"]
"""
self._create_test_case_file("test_001.py", content)
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
all_cases = registry.get_all_test_case_classes()
self.assertEqual(len(all_cases), 1)
self.assertEqual(all_cases[0].id, "TC-001")
self.assertIsNotNone(registry.get_test_case_by_id("TC-001"))
def test_discover_multiple_test_cases_in_one_file(self):
"""测试在单个文件中发现多个测试用例。"""
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class TestA(BaseAPITestCase):
id = "TC-A"
name = "A"
class TestB(BaseAPITestCase):
id = "TC-B"
name = "B"
"""
self._create_test_case_file("test_ab.py", content)
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
all_cases = registry.get_all_test_case_classes()
self.assertEqual(len(all_cases), 2)
self.assertIsNotNone(registry.get_test_case_by_id("TC-A"))
self.assertIsNotNone(registry.get_test_case_by_id("TC-B"))
# 确保顺序与文件中定义的顺序或至少是可预测的一致inspect.getmembers 通常按字母顺序
ids = sorted([case.id for case in all_cases])
self.assertEqual(ids, ["TC-A", "TC-B"])
def test_discover_test_cases_in_multiple_files(self):
"""测试在多个文件中发现测试用例。"""
content1 = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class Test1(BaseAPITestCase):
id = "TC-1"
"""
content2 = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class Test2(BaseAPITestCase):
id = "TC-2"
"""
self._create_test_case_file("file1.py", content1)
self._create_test_case_file("file2.py", content2)
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
all_cases = registry.get_all_test_case_classes()
self.assertEqual(len(all_cases), 2)
self.assertIsNotNone(registry.get_test_case_by_id("TC-1"))
self.assertIsNotNone(registry.get_test_case_by_id("TC-2"))
def test_ignore_non_py_files_and_dunder_files(self):
"""测试忽略非.py文件和以__开头的文件。"""
self._create_test_case_file("not_a_test.txt", "text content")
self._create_test_case_file("__init__.py", "# I am an init file")
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class RealTest(BaseAPITestCase):
id = "TC-REAL"
"""
self._create_test_case_file("real_test.py", content)
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
all_cases = registry.get_all_test_case_classes()
self.assertEqual(len(all_cases), 1)
self.assertEqual(all_cases[0].id, "TC-REAL")
def test_handle_import_error_in_test_file(self):
"""测试处理测试用例文件中的导入错误。"""
content = "import non_existent_module\n" # This will cause ImportError
self._create_test_case_file("importerror_test.py", content)
with self.assertLogs(level='ERROR') as log_watcher:
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
self.assertTrue(any("导入模块 'importerror_test'" in msg and "失败" in msg for msg in log_watcher.output))
self.assertEqual(len(registry.get_all_test_case_classes()), 0)
def test_handle_attribute_error_missing_id(self):
"""测试处理测试用例类缺少 'id' 属性的情况。"""
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class MissingIdTest(BaseAPITestCase):
name = "Missing ID"
# id is missing
"""
self._create_test_case_file("missing_id.py", content)
# AttributeError is caught by the generic Exception in discover_test_cases if not directly handled
# It might also depend on when/how inspect.getmembers tries to access obj.id
# Forcing access here to ensure the test scenario is valid if discovery itself doesn't raise it immediately
with self.assertLogs(level='ERROR') as log_watcher:
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
# The error log in discover_test_cases for AttributeError on obj.id might be tricky to assert precisely
# We'll check that no test cases were loaded from this problematic file, but other files might load.
self.assertTrue(any("在模块 'missing_id'" in msg and "查找测试用例时出错" in msg for msg in log_watcher.output),
msg=f"Did not find expected error log. Logs: {log_watcher.output}")
# Ensure no test cases are registered if the only file has this error
self.assertEqual(len(registry.get_all_test_case_classes()), 0)
def test_duplicate_test_case_id(self):
"""测试发现重复的测试用例 ID。"""
content1 = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class TestOne(BaseAPITestCase):
id = "DUPLICATE-ID-001"
name = "First with ID"
"""
content2 = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class TestTwo(BaseAPITestCase):
id = "DUPLICATE-ID-001" # Same ID
name = "Second with ID"
"""
self._create_test_case_file("file_one.py", content1)
self._create_test_case_file("file_two.py", content2)
with self.assertLogs(level='WARNING') as log_watcher:
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
self.assertTrue(any("发现重复的测试用例 ID: 'DUPLICATE-ID-001'" in msg for msg in log_watcher.output))
all_cases = registry.get_all_test_case_classes()
# inspect.getmembers order is not guaranteed across files, so we can't be sure which one is kept.
# However, the _registry (by ID) should have only one entry.
self.assertEqual(len(registry._registry), 1)
# The _test_case_classes list might have two if they are distinct class objects, but one overrides in _registry
# Depending on load order, one will overwrite the other in _registry. Let's check the final one.
registered_case = registry.get_test_case_by_id("DUPLICATE-ID-001")
self.assertIsNotNone(registered_case)
# We cannot reliably assert which name ('First with ID' or 'Second with ID') is kept due to file load order.
# Check that _test_case_classes might have more if classes are distinct but _registry has one.
# If the class objects are truly distinct, len(all_cases) could be 2. The important part is that by ID, only one is retrievable.
# A more robust check for `_test_case_classes` would be to ensure it contains the class that `_registry` points to.
self.assertIn(registered_case, all_cases)
# If the goal is that _test_case_classes should also be unique by some criteria after discovery, that logic would need adjustment.
# For now, get_all_test_case_classes returns all *discovered* classes that are BaseAPITestCase subclasses.
# And get_test_case_by_id returns the one that won the ID race.
# A typical use case iterates get_all_test_case_classes() for filtering, so this list should ideally be clean or documented.
# For now, we accept it might contain classes whose IDs were superseded if they are distinct objects.
def test_get_applicable_test_cases_no_restrictions(self):
"""测试 get_applicable_test_cases当测试用例没有适用性限制时。"""
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class NoRestrictionTest(BaseAPITestCase):
id = "TC-NR-001"
name = "No Restrictions"
"""
self._create_test_case_file("no_restriction.py", content)
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
applicable = registry.get_applicable_test_cases("GET", "/api/items")
self.assertEqual(len(applicable), 1)
self.assertEqual(applicable[0].id, "TC-NR-001")
def test_get_applicable_by_method(self):
"""测试根据 applicable_methods 进行筛选。"""
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class GetOnlyTest(BaseAPITestCase):
id = "TC-GET-ONLY"
name = "GET Only"
applicable_methods = ["GET", "HEAD"]
class PostOnlyTest(BaseAPITestCase):
id = "TC-POST-ONLY"
name = "POST Only"
applicable_methods = ["POST"]
"""
self._create_test_case_file("method_tests.py", content)
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
applicable_get = registry.get_applicable_test_cases("GET", "/api/data")
self.assertEqual(len(applicable_get), 1)
self.assertEqual(applicable_get[0].id, "TC-GET-ONLY")
applicable_post = registry.get_applicable_test_cases("POST", "/api/data")
self.assertEqual(len(applicable_post), 1)
self.assertEqual(applicable_post[0].id, "TC-POST-ONLY")
applicable_put = registry.get_applicable_test_cases("PUT", "/api/data")
self.assertEqual(len(applicable_put), 0)
applicable_head = registry.get_applicable_test_cases("HEAD", "/api/data") # Case sensitive check for methods in list
self.assertEqual(len(applicable_head), 1)
self.assertEqual(applicable_head[0].id, "TC-GET-ONLY")
def test_get_applicable_by_path_regex(self):
"""测试根据 applicable_paths_regex 进行筛选。"""
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class UserPathTest(BaseAPITestCase):
id = "TC-USER-PATH"
name = "User Path"
applicable_paths_regex = r"^/api/users/\\d+$" # Matches /api/users/<number>
class OrderPathTest(BaseAPITestCase):
id = "TC-ORDER-PATH"
name = "Order Path"
applicable_paths_regex = r"^/api/orders"
"""
self._create_test_case_file("path_tests.py", content)
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
applicable_user = registry.get_applicable_test_cases("GET", "/api/users/123")
self.assertEqual(len(applicable_user), 1)
self.assertEqual(applicable_user[0].id, "TC-USER-PATH")
applicable_order = registry.get_applicable_test_cases("POST", "/api/orders/new")
self.assertEqual(len(applicable_order), 1)
self.assertEqual(applicable_order[0].id, "TC-ORDER-PATH")
applicable_none1 = registry.get_applicable_test_cases("GET", "/api/products/789")
self.assertEqual(len(applicable_none1), 0)
applicable_none2 = registry.get_applicable_test_cases("GET", "/api/users/profile") # Does not match \d+
self.assertEqual(len(applicable_none2), 0)
def test_get_applicable_by_method_and_path(self):
"""测试同时根据方法和路径进行筛选。"""
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class SpecificGetTest(BaseAPITestCase):
id = "TC-SPECIFIC-GET"
name = "Specific GET"
applicable_methods = ["GET"]
applicable_paths_regex = r"^/data/\\w+$"
"""
self._create_test_case_file("specific_get.py", content)
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
applicable = registry.get_applicable_test_cases("GET", "/data/item1")
self.assertEqual(len(applicable), 1)
self.assertEqual(applicable[0].id, "TC-SPECIFIC-GET")
not_applicable_method = registry.get_applicable_test_cases("POST", "/data/item1")
self.assertEqual(len(not_applicable_method), 0)
not_applicable_path = registry.get_applicable_test_cases("GET", "/data/item1/details")
self.assertEqual(len(not_applicable_path), 0)
def test_invalid_path_regex_handling(self):
"""测试处理无效的路径正则表达式。"""
content = """
from ddms_compliance_suite.test_framework_core import BaseAPITestCase, TestSeverity
class InvalidRegexTest(BaseAPITestCase):
id = "TC-INVALID-REGEX"
name = "Invalid Regex"
applicable_paths_regex = r"^/api/path_(" # Unbalanced parenthesis
"""
self._create_test_case_file("invalid_regex.py", content)
with self.assertLogs(level='ERROR') as log_watcher:
registry = TestCaseRegistry(test_cases_dir=TEMP_TEST_CASES_DIR)
self.assertTrue(any("中的路径正则表达式 'invalid_regex.InvalidRegexTest' 无效" in msg for msg in log_watcher.output))
# The test case with invalid regex should not match any path
applicable = registry.get_applicable_test_cases("GET", "/api/path_something")
self.assertEqual(len(applicable), 0)
if __name__ == '__main__':
# Configure logging for detailed output when running directly
# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG,
# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
unittest.main()