| """ |
| test_required.py โโ ๅไธชๆๅฎ้ชๆถๆต่ฏ็จไพ |
| |
| TC-R1 test_dimension_and_channels |
| TC-R2 test_map_connectivity |
| TC-R3 test_termination_and_truncation |
| TC-R4 test_seeding_reproducibility |
| """ |
|
|
| from __future__ import annotations |
|
|
| from collections import deque |
|
|
| import numpy as np |
| import pytest |
|
|
| from maze_env import MazeEnv |
|
|
|
|
| |
| |
| |
|
|
| def _bfs_connected(wall_map: np.ndarray, start: tuple[int, int], |
| goal: tuple[int, int]) -> bool: |
| """ๅฏน็ปๅฎ wall_map ๆง่ก BFS๏ผ่ฟๅ start ๅฐ goal ๆฏๅฆๅฏ่พพใ |
| |
| Args: |
| wall_map: shape (N, N) float32 ๆฐ็ป๏ผ1.0 ไปฃ่กจๅข๏ผ0.0 ไปฃ่กจๅฏ้่กใ |
| start: ่ตทๅงๆ ผๅๆ (row, col)ใ |
| goal: ็ฎๆ ๆ ผๅๆ (row, col)ใ |
| |
| Returns: |
| True ่กจ็คบๅฏ่พพ๏ผFalse ่กจ็คบไธๅฏ่พพใ |
| """ |
| N = wall_map.shape[0] |
| visited: set[tuple[int, int]] = {start} |
| queue: deque[tuple[int, int]] = deque([start]) |
| deltas = [(-1, 0), (1, 0), (0, -1), (0, 1)] |
| while queue: |
| r, c = queue.popleft() |
| if (r, c) == goal: |
| return True |
| for dr, dc in deltas: |
| nr, nc = r + dr, c + dc |
| if (0 <= nr < N and 0 <= nc < N |
| and wall_map[nr, nc] == 0.0 |
| and (nr, nc) not in visited): |
| visited.add((nr, nc)) |
| queue.append((nr, nc)) |
| return (start == goal) |
|
|
|
|
| |
| |
| |
|
|
| class TestDimensionAndChannels: |
| """TC-R1๏ผ10ร10 ็ฏๅข็่งๆตๅฝข็ถไธ้้่ฏญไน้ช่ฏใ""" |
|
|
| @pytest.mark.unit |
| def test_dimension_and_channels(self) -> None: |
| """ๅฎไพๅ 10ร10 ็ฏๅข๏ผๆญ่จ obs.shape==(3,10,10)๏ผ |
| Agent ้้ๅ็ป็น้้ๅ่ช sum==1ใ |
| |
| ่พๅ
ฅ: MazeEnv(grid_size=10, obstacle_density=0.3, seed=0).reset() |
| ๆๆ: |
| obs.shape == (4, 10, 10) |
| obs[1].sum() == 1.0 ๏ผAgent ้้๏ผๅฏไธๆฟๆดปๆ ผ๏ผ |
| obs[2].sum() == 1.0 ๏ผ็ป็น้้๏ผๅฏไธๆฟๆดปๆ ผ๏ผ |
| ๅฎๆต: obs ๅ็ปดๅบฆๅ้้ sum |
| """ |
| env = MazeEnv(grid_size=10, obstacle_density=0.3, seed=0) |
| obs, _ = env.reset() |
|
|
| assert obs.shape == (4, 10, 10), \ |
| f"obs.shape ๆๆ (3,10,10)๏ผๅฎ้
{obs.shape}" |
| assert float(obs[1].sum()) == 1.0, \ |
| f"Agent ้้ (obs[1]) ๅบๆฐๅฅฝๆ 1 ไธชๆฟๆดปๆ ผ๏ผๅฎ้
sum={obs[1].sum()}" |
| assert float(obs[2].sum()) == 1.0, \ |
| f"็ป็น้้ (obs[2]) ๅบๆฐๅฅฝๆ 1 ไธชๆฟๆดปๆ ผ๏ผๅฎ้
sum={obs[2].sum()}" |
|
|
|
|
| |
| |
| |
|
|
| class TestMapConnectivity: |
| """TC-R2๏ผ่ฟ็ปญ 100 ๆฌก reset๏ผๆฏๆฌก็จ็ฌ็ซ BFS ้ช่ฏ่ตท็ป็นๅฏ่พพใ""" |
|
|
| @pytest.mark.slow |
| def test_map_connectivity(self) -> None: |
| """ๅพช็ฏ reset() 100 ๆฌก๏ผ็จๅค้จ BFS ็ฌ็ซ้ช่ฏๆฏๅผ ๅฐๅพ็่ตท็ป็น่ฟ้ๆงใ |
| |
| ่พๅ
ฅ: MazeEnv(grid_size=10, obstacle_density=0.45)๏ผreset() ร 100 |
| ๆๆ: 100% ่ฟ้๏ผไปปๆไธๆฌกไธ่ฟ้ๅณๅคฑ่ดฅ๏ผ |
| ๅฎๆต: ๅค้จ BFS ้ช่ฏ wall_map๏ผobs[0]๏ผ๏ผstart=(1,1)๏ผgoal=(N-2,N-2) |
| """ |
| N = 10 |
| start = (1, 1) |
| goal = (N - 2, N - 2) |
| env = MazeEnv(grid_size=N, obstacle_density=0.45) |
|
|
| for i in range(100): |
| obs, info = env.reset() |
| wall_map = obs[0] |
|
|
| connected = _bfs_connected(wall_map, start, goal) |
| assert connected, ( |
| f"็ฌฌ {i+1} ๆฌก reset๏ผBFS ้ช่ฏ่ตท็น {start} โ ็ป็น {goal} ไธ่ฟ้๏ผ" |
| f"่ฏดๆ MazeEnv ็่ฟ้ๆง่ฟๆปคๅจๆช็ๆใ" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestTerminationAndTruncation: |
| """TC-R3๏ผ้ช่ฏ truncated๏ผๆญฅๆฐ่ๅฐฝ๏ผไธ terminated๏ผๅฐ่พพ็ป็น๏ผ็ไบๆฅ่ฏญไนใ""" |
|
|
| @pytest.mark.integration |
| def test_termination_and_truncation(self) -> None: |
| """Part A๏ผ็ฏ็ๆๅข็ด่ณ max_steps=50๏ผๆญ่จ truncated=True & terminated=Falseใ |
| Part B๏ผๆๅจๅฐ agent ็ฝฎไบ็ป็น้่ฟ๏ผๆง่กๆๅไธๆญฅ๏ผๆญ่จ terminated=True & truncated=Falseใ |
| |
| ่พๅ
ฅ: |
| Part A: MazeEnv(grid_size=6, obstacle_density=0.0, seed=0, max_steps=50) |
| ๅๅค step(0)๏ผๆ็ปญๆไธ่พน็ๅข๏ผร 50 |
| Part B: ๅไธ็ฏๅข reset()๏ผ้่ฟๅๆณ็งปๅจๅผๅฏผ agent ๅฐ็ป็น (4,4) |
| |
| ๆๆ: |
| Part A: truncated is True, terminated is False |
| Part B: terminated is True, truncated is False |
| """ |
| |
| env = MazeEnv(grid_size=6, obstacle_density=0.0, seed=0, max_steps=50) |
| env.reset() |
|
|
| terminated = truncated = False |
| for _ in range(50): |
| _, _, terminated, truncated, _ = env.step(0) |
|
|
| assert truncated is True, "ๆญฅๆฐ่ๅฐฝ๏ผmax_steps=50๏ผๆถ๏ผtruncated ๅบไธบ True" |
| assert terminated is False, "ๆญฅๆฐ่ๅฐฝไฝๆชๅฐ็ป็นๆถ๏ผterminated ๅบไธบ False" |
|
|
| |
| env2 = MazeEnv(grid_size=6, obstacle_density=0.0, seed=0, max_steps=200) |
| env2.reset() |
|
|
| |
| for _ in range(3): |
| env2.step(3) |
| for _ in range(2): |
| env2.step(1) |
| _, _, terminated, truncated, info = env2.step(1) |
|
|
| assert terminated is True, "ๅฐ่พพ็ป็นๆถ๏ผterminated ๅบไธบ True" |
| assert truncated is False, "ๅฐ่พพ็ป็นๆถ๏ผtruncated ๅฟ
้กปไธบ False๏ผไธฅๆ ผไบๆฅ๏ผ" |
| assert info["success"] is True, "ๅฐ่พพ็ป็นๅ success ๅบไธบ True" |
|
|
|
|
| |
| |
| |
|
|
| class TestSeedingReproducibility: |
| """TC-R4๏ผ็ธๅ seed=42 ็ไธคไธช็ฌ็ซๅฎไพไบง็ๅฎๅ
จ็ธๅ็ๅฐๅพใ่ตท็นใ็ป็นใ""" |
|
|
| @pytest.mark.unit |
| def test_seeding_reproducibility(self) -> None: |
| """็จ็ธๅ seed=42 ๅๅงๅไธคไธช็ฌ็ซ MazeEnv ๅฎไพ๏ผๅๅซ reset()๏ผ |
| ๆญ่จๅฐๅพ็ฉ้ตใagent ไฝ็ฝฎใgoal ไฝ็ฝฎๅฎๅ
จไธ่ดใ |
| |
| ่พๅ
ฅ: |
| env_a = MazeEnv(grid_size=10, obstacle_density=0.3, seed=42) |
| env_b = MazeEnv(grid_size=10, obstacle_density=0.3, seed=42) |
| ๅ่ฐ็จ reset() |
| |
| ๆๆ: |
| np.array_equal(obs_a[0], obs_b[0]) โ ๅฐๅพๅฎๅ
จไธ่ด |
| info_a["agent_pos"] == info_b["agent_pos"] |
| info_a["goal_pos"] == info_b["goal_pos"] |
| np.array_equal(obs_a, obs_b) โ ไธ้้ๅ
จ้จไธ่ด |
| """ |
| env_a = MazeEnv(grid_size=10, obstacle_density=0.3, seed=42) |
| env_b = MazeEnv(grid_size=10, obstacle_density=0.3, seed=42) |
|
|
| obs_a, info_a = env_a.reset() |
| obs_b, info_b = env_b.reset() |
|
|
| assert np.array_equal(obs_a[0], obs_b[0]), \ |
| "็ธๅ seed ็ไธคไธชๅฎไพๅบไบง็ๅฎๅ
จ็ธๅ็ๅขๅฃๅฐๅพ๏ผobs[0]๏ผ" |
| assert info_a["agent_pos"] == info_b["agent_pos"], \ |
| "็ธๅ seed ็ไธคไธชๅฎไพๅบไบง็็ธๅ็่ตท็น" |
| assert info_a["goal_pos"] == info_b["goal_pos"], \ |
| "็ธๅ seed ็ไธคไธชๅฎไพๅบไบง็็ธๅ็็ป็น" |
| assert np.array_equal(obs_a, obs_b), \ |
| "็ธๅ seed ็ไธคไธชๅฎไพๆดไฝ่งๆต๏ผไธ้้๏ผๅบๅฎๅ
จไธ่ด" |
|
|