| """ |
| 测试模块 07 —— 工厂方法 |
| |
| 需求覆盖 |
| -------- |
| * RF2:from_config(cfg_dict) 正确映射所有参数 |
| * RF4:from_yaml(path) 读取 config.yaml 并构造实例 |
| |
| 对应用例 |
| -------- |
| TC-18, TC-19 |
| """ |
|
|
| from __future__ import annotations |
|
|
| from pathlib import Path |
|
|
| import pytest |
| import yaml |
|
|
| from maze_env import MazeEnv |
|
|
|
|
| class TestFactory: |
| """验证 from_config() 与 from_yaml() 工厂方法。""" |
|
|
| |
| |
| |
|
|
| @pytest.mark.unit |
| def test_from_config_grid_size(self, cfg_dict: dict) -> None: |
| """TC-18a:from_config 正确映射 grid_size=12。 |
| |
| 输入: cfg_dict(grid_size=12) |
| 期望: env.observation_space.shape == (4, 12, 12) |
| 实测: observation_space.shape |
| """ |
| env = MazeEnv.from_config(cfg_dict) |
| assert env.observation_space.shape == (4, 12, 12) |
|
|
| @pytest.mark.unit |
| def test_from_config_reward_goal(self, cfg_dict: dict) -> None: |
| """TC-18b:from_config 正确映射 reward_goal=200。 |
| |
| 输入: cfg_dict(goal=200) |
| 期望: env.reward_goal == 200.0 |
| 实测: env.reward_goal 属性 |
| """ |
| env = MazeEnv.from_config(cfg_dict) |
| assert env.reward_goal == 200.0 |
|
|
| @pytest.mark.unit |
| def test_from_config_reward_wall_hit(self, cfg_dict: dict) -> None: |
| """TC-18c:from_config 正确映射 reward_wall_hit=-5。 |
| |
| 输入: cfg_dict(wall_hit=-5) |
| 期望: env.reward_wall_hit == -5.0 |
| 实测: env.reward_wall_hit 属性 |
| """ |
| env = MazeEnv.from_config(cfg_dict) |
| assert env.reward_wall_hit == -5.0 |
|
|
| @pytest.mark.unit |
| def test_from_config_reward_step(self, cfg_dict: dict) -> None: |
| """TC-18d:from_config 正确映射 reward_step=-2。 |
| |
| 输入: cfg_dict(step=-2) |
| 期望: env.reward_step == -2.0 |
| 实测: env.reward_step 属性 |
| """ |
| env = MazeEnv.from_config(cfg_dict) |
| assert env.reward_step == -2.0 |
|
|
| @pytest.mark.unit |
| def test_from_config_max_steps(self, cfg_dict: dict) -> None: |
| """TC-18e:from_config 正确映射 max_steps=300。 |
| |
| 输入: cfg_dict(max_steps=300) |
| 期望: env.max_steps == 300 |
| 实测: env.max_steps 属性 |
| """ |
| env = MazeEnv.from_config(cfg_dict) |
| assert env.max_steps == 300 |
|
|
| @pytest.mark.unit |
| def test_from_config_reset_works(self, cfg_dict: dict) -> None: |
| """TC-18f:from_config 构造的环境可以正常 reset。 |
| |
| 输入: from_config(cfg_dict).reset() |
| 期望: obs.shape == (4, 12, 12),不抛出异常 |
| 实测: reset() 返回值 |
| """ |
| env = MazeEnv.from_config(cfg_dict) |
| obs, info = env.reset() |
| assert obs.shape == (4, 12, 12) |
|
|
| |
| |
| |
|
|
| @pytest.mark.integration |
| def test_from_yaml_loads(self, config_yaml_path: Path) -> None: |
| """TC-19a:from_yaml 读取 config.yaml 不抛出异常。 |
| |
| 输入: config.yaml 文件路径 |
| 期望: 成功返回 MazeEnv 实例 |
| 实测: isinstance 检查 |
| """ |
| env = MazeEnv.from_yaml(config_yaml_path) |
| assert isinstance(env, MazeEnv) |
|
|
| @pytest.mark.integration |
| def test_from_yaml_matches_config(self, config_yaml_path: Path) -> None: |
| """TC-19b:from_yaml 参数与 config.yaml 内容一致。 |
| |
| 输入: config.yaml |
| 期望: env.observation_space.shape[1] == cfg["maze"]["grid_size"] |
| env.max_steps == cfg["maze"]["max_steps"] |
| env.reward_goal == cfg["rewards"]["goal"] |
| 实测: 属性值(动态读取,不硬编码) |
| """ |
| with open(config_yaml_path, "r", encoding="utf-8") as fh: |
| cfg = yaml.safe_load(fh) |
|
|
| expected_grid_size = cfg["maze"]["grid_size"] |
| expected_max_steps = cfg["maze"]["max_steps"] |
| expected_goal = float(cfg["rewards"]["goal"]) |
|
|
| env = MazeEnv.from_yaml(config_yaml_path) |
| assert env.observation_space.shape[1] == expected_grid_size, \ |
| f"grid_size 应为 {expected_grid_size}" |
| assert env.max_steps == expected_max_steps, \ |
| f"max_steps 应为 {expected_max_steps}(来自 config.yaml),实际 {env.max_steps}" |
| assert env.reward_goal == expected_goal, \ |
| f"reward_goal 应为 {expected_goal}" |
|
|
| @pytest.mark.integration |
| def test_from_yaml_reset_works(self, config_yaml_path: Path) -> None: |
| """TC-19c:from_yaml 构造的环境可以正常 reset 并产生正确形状的 obs。 |
| |
| 输入: from_yaml(config_yaml_path).reset() |
| 期望: obs.shape == (4, 10, 10) |
| 实测: reset() 返回值 |
| """ |
| env = MazeEnv.from_yaml(config_yaml_path) |
| obs, _ = env.reset() |
| assert obs.shape == (4, 10, 10) |
|
|