"""src/wov_app/logging.py 的模块级测试(数据 → 测试过程 → 验证结果)。 被测模块:`src/wov_app/logging.py`(控制台日志配置),可独立调用。 用例使用真实 logging 模块与真实日志记录,验证命名、处理器去重与传播设置。 """ from __future__ import annotations import logging from wov_app.logging import _APP_LOGGER_NAME, _ensure_console_handler, get_logger def _strip_handlers(logger: logging.Logger) -> None: """清空日志器处理器,保证用例从干净状态开始(模块自建隔离)。""" for handler in list(logger.handlers): logger.removeHandler(handler) def test_get_logger_uses_app_prefix() -> None: """日志器名称带应用前缀,便于与其他库日志区分。""" # 数据:模块名 "scheduler"。 # 测试过程 logger = get_logger("scheduler") # 验证结果 assert logger.name == f"{_APP_LOGGER_NAME}.scheduler" _strip_handlers(logger) def test_get_logger_attaches_console_handler_once() -> None: """重复获取同一日志器不会重复附加处理器(避免日志重复打印)。""" # 数据:先清空,再连续获取两次。 logger = get_logger("dup") _strip_handlers(logger) # 测试过程 first = get_logger("dup") second = get_logger("dup") # 验证结果:处理器只有 1 个。 assert first is second assert len([h for h in second.handlers if isinstance(h, logging.StreamHandler)]) == 1 _strip_handlers(second) def test_get_logger_sets_info_level_and_no_propagation() -> None: """日志级别为 INFO 且不向根日志传播(防止 uvicorn 重复输出)。""" # 数据:新日志器。 logger = get_logger("levels") _strip_handlers(logger) # 测试过程 logger = get_logger("levels") # 验证结果 assert logger.level == logging.INFO assert logger.propagate is False _strip_handlers(logger) def test_ensure_console_handler_is_idempotent() -> None: """_ensure_console_handler 对已配置的日志器不再追加处理器。""" # 数据:手工配置过的日志器。 logger = logging.getLogger("vrsub.idempotent") _strip_handlers(logger) # 测试过程 _ensure_console_handler(logger) _ensure_console_handler(logger) # 验证结果 assert len(logger.handlers) == 1 _strip_handlers(logger) def test_log_record_is_actually_emitted(capsys) -> None: """日志记录真实写出到控制台(handler 生效,而非仅配置)。""" # 数据:日志器 + 一条 INFO 记录。 logger = get_logger("emit") # 测试过程 logger.info("测试日志 %s", "生效") # 验证结果:输出内容包含格式化后的消息。 captured = capsys.readouterr() assert "测试日志 生效" in (captured.err + captured.out) _strip_handlers(logger)