ForcePilot/backend/test/unit/channels/infrastructure/test_dependency_injection.py

155 lines
5.2 KiB
Python
Raw Normal View History

"""yuxi.channels.infrastructure.dependency_injection 单元测试。
覆盖 ``DependencyInjectionContainer`` 行为
- ``registerSingleton`` 注册后 ``resolve`` 返回原实例单例语义
- 重复注册覆盖旧绑定last-write-wins
- 未注册接口 ``resolve`` ``InternalError``装配期漏注册视为编程错误
- 线程安全并发注册与解析不破坏注册表一致性
不依赖运行中的 Docker 服务纯单元测试
"""
from __future__ import annotations
import threading
from concurrent.futures import ThreadPoolExecutor
from unittest.mock import MagicMock
import pytest
from yuxi.channels.contract.errors import InternalError
from yuxi.channels.infrastructure.dependency_injection import (
DependencyInjectionContainer,
)
pytestmark = pytest.mark.unit
class _FakeInterface:
"""测试用接口类型占位。"""
class _FakeImpl:
"""测试用实现类型占位。"""
@pytest.mark.unit
class TestRegisterAndResolve:
"""registerSingleton + resolve 基本行为测试。"""
def test_register_and_resolve_returns_same_instance(self):
# Arrange
container = DependencyInjectionContainer()
instance = MagicMock()
# Act
container.registerSingleton(_FakeInterface, instance)
resolved = container.resolve(_FakeInterface)
# Assert
assert resolved is instance
def test_resolve_unregistered_interface_raises_internal_error(self):
# Arrange
container = DependencyInjectionContainer()
# Act / Assert: 未注册接口解析失败,错误码 INTERNAL装配期漏注册
with pytest.raises(InternalError) as exc_info:
container.resolve(_FakeInterface)
assert exc_info.value.error_code == "INTERNAL"
assert "_FakeInterface" in exc_info.value.message
def test_reregister_overrides_previous_binding(self):
"""重复注册覆盖旧绑定last-write-wins幂等覆盖语义"""
# Arrange
container = DependencyInjectionContainer()
first = MagicMock(name="first")
second = MagicMock(name="second")
# Act
container.registerSingleton(_FakeInterface, first)
container.registerSingleton(_FakeInterface, second)
resolved = container.resolve(_FakeInterface)
# Assert
assert resolved is second
assert resolved is not first
def test_multiple_interfaces_independent(self):
"""不同接口的注册互不影响。"""
# Arrange
container = DependencyInjectionContainer()
iface_a = MagicMock()
impl_b = MagicMock()
# Act
container.registerSingleton(_FakeInterface, iface_a)
container.registerSingleton(_FakeImpl, impl_b)
# Assert
assert container.resolve(_FakeInterface) is iface_a
assert container.resolve(_FakeImpl) is impl_b
@pytest.mark.unit
class TestThreadSafety:
"""容器线程安全测试。"""
def test_concurrent_register_and_resolve_consistent(self):
"""多线程并发注册与解析不破坏注册表一致性。
使用同一接口并发 register + resolve所有 resolve 必须返回注册过的
某个实例不能是 None 或未注册异常证明锁保护下读写串行化
"""
# Arrange
container = DependencyInjectionContainer()
instances = [MagicMock(name=f"inst-{i}") for i in range(20)]
resolved: list[MagicMock] = []
errors: list[Exception] = []
barrier = threading.Barrier(20)
def worker(idx: int) -> None:
barrier.wait()
instance = instances[idx]
container.registerSingleton(_FakeInterface, instance)
try:
resolved.append(container.resolve(_FakeInterface))
except Exception as e: # noqa: BLE001 - 测试需捕获全部异常
errors.append(e)
# Act
with ThreadPoolExecutor(max_workers=20) as executor:
list(executor.map(worker, range(20)))
# Assert
assert not errors, f"并发 resolve 抛出异常: {errors}"
# 所有解析结果必须是注册过的某个实例last-write-wins 下最终为最后一个,
# 中间并发解析可能拿到任意已注册实例,但必须在 instances 集合中)
for r in resolved:
assert r in instances
def test_concurrent_resolve_after_register_no_error(self):
"""单次注册后并发 resolve 不抛异常且返回同一实例。"""
# Arrange
container = DependencyInjectionContainer()
instance = MagicMock()
container.registerSingleton(_FakeInterface, instance)
results: list[object] = []
errors: list[Exception] = []
barrier = threading.Barrier(50)
def worker(_) -> None:
barrier.wait()
try:
results.append(container.resolve(_FakeInterface))
except Exception as e: # noqa: BLE001
errors.append(e)
# Act
with ThreadPoolExecutor(max_workers=50) as executor:
list(executor.map(worker, range(50)))
# Assert
assert not errors
assert all(r is instance for r in results)