File size: 6,674 Bytes
fe0625d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
"""
测试模块 08 —— render() 输出

需求覆盖
--------
* R11:render(mode='ansi') 返回含 A(agent)和 G(goal)标记的字符串
* R12:render 头部信息含 HitWalls 计数

对应用例
--------
TC-20, TC-21
"""

from __future__ import annotations

import pytest

from maze_env import MazeEnv


def _ansi_env() -> MazeEnv:
    """返回 render_mode='ansi' 的确定性零障碍环境(grid=6,seed=0)。"""
    return MazeEnv(grid_size=6, obstacle_density=0.0, seed=0, render_mode="ansi")


class TestRender:
    """验证 render('ansi') 输出格式与关键标记。"""

    # ------------------------------------------------------------------ #
    # TC-20  A / G 标记                                                    #
    # ------------------------------------------------------------------ #

    @pytest.mark.unit
    def test_render_contains_agent_marker(self) -> None:
        """TC-20a:render 输出包含 'A'(agent 标记)。

        输入:  render_mode='ansi',reset(),render()
        期望:  'A' in rendered_str
        实测:  返回字符串内容
        """
        env = _ansi_env()
        env.reset()
        rendered = env.render()
        assert "A" in rendered, "render 输出应包含 agent 标记 'A'"

    @pytest.mark.unit
    def test_render_contains_goal_marker(self) -> None:
        """TC-20b:render 输出包含 'G'(goal 标记)。

        输入:  render_mode='ansi',reset(),render()
        期望:  'G' in rendered_str
        实测:  返回字符串内容
        """
        env = _ansi_env()
        env.reset()
        rendered = env.render()
        assert "G" in rendered, "render 输出应包含 goal 标记 'G'"

    @pytest.mark.unit
    def test_render_returns_string(self) -> None:
        """TC-20c:render 返回值类型为 str(ansi 模式)。

        输入:  render_mode='ansi',reset(),render()
        期望:  isinstance(rendered, str)
        实测:  type 检查
        """
        env = _ansi_env()
        env.reset()
        rendered = env.render()
        assert isinstance(rendered, str), "render 应返回字符串"

    # ------------------------------------------------------------------ #
    # TC-21  头部 HitWalls 信息                                            #
    # ------------------------------------------------------------------ #

    @pytest.mark.unit
    def test_render_header_contains_hitwalls(self) -> None:
        """TC-21a:render 头部含 'WallHits' 相关信息。

        输入:  render_mode='ansi',reset(),撞墙 1 次,render()
        期望:  render 输出含 'wall'(大小写不敏感)
        实测:  rendered.lower()
        """
        env = _ansi_env()
        env.reset()
        env.step(0)   # 撞上边界墙
        rendered = env.render()
        assert "wall" in rendered.lower(), \
            "render 头部应包含 WallHits 相关信息"

    @pytest.mark.unit
    def test_render_header_hitwalls_count(self) -> None:
        """TC-21b:render 头部 WallHits 数值与实际撞墙次数一致。

        输入:  render_mode='ansi',reset(),撞墙 2 次,render()
        期望:  render 输出首行含 '2'
        实测:  rendered 首行内容
        """
        env = _ansi_env()
        env.reset()
        env.step(0)
        env.step(0)
        rendered = env.render()
        first_line = rendered.split("\n")[0]
        assert "2" in first_line, \
            f"render 头部应显示 WallHits=2,实际首行:{first_line!r}"

    @pytest.mark.unit
    def test_render_after_move_a_moved(self) -> None:
        """TC-21c:移动后,'A' 标记仍存在于 render 输出中。

        输入:  render_mode='ansi',reset(),右移一步到 (1,2),render()
        期望:  'A' in rendered
        实测:  字符串包含检查
        """
        env = _ansi_env()
        env.reset()
        env.step(3)   # 右移
        rendered = env.render()
        assert "A" in rendered

    # ------------------------------------------------------------------ #
    # TC-22  human 模式与 None 模式                                        #
    # ------------------------------------------------------------------ #

    @pytest.mark.unit
    def test_render_human_returns_none(self, capsys) -> None:
        """TC-22a:render_mode='human' 时 render() 打印到 stdout 并返回 None。

        输入:  render_mode='human',reset(),render()
        期望:  返回值为 None,stdout 非空
        实测:  返回值类型 + capsys 捕获
        """
        env = MazeEnv(grid_size=5, obstacle_density=0.0, seed=0, render_mode="human")
        env.reset()
        result = env.render()
        captured = capsys.readouterr()
        assert result is None, "human 模式 render() 应返回 None"
        assert len(captured.out) > 0, "human 模式应向 stdout 打印内容"

    @pytest.mark.unit
    def test_render_none_mode_returns_none(self) -> None:
        """TC-22b:render_mode=None 时 render() 直接返回 None,无输出。

        输入:  MazeEnv()(默认 render_mode=None),reset(),render()
        期望:  None
        实测:  返回值
        """
        env = MazeEnv(grid_size=5, obstacle_density=0.0, seed=0)
        env.reset()
        assert env.render() is None

    @pytest.mark.unit
    def test_step_triggers_human_render(self, capsys) -> None:
        """TC-22c:render_mode='human' 时,step() 自动调用 render() 打印输出。

        输入:  render_mode='human',reset(),step(右移)
        期望:  stdout 非空(step 内部调用 render)
        实测:  capsys.readouterr().out
        """
        env = MazeEnv(grid_size=5, obstacle_density=0.0, seed=0, render_mode="human")
        env.reset()
        capsys.readouterr()   # 清空 reset 阶段的可能输出
        env.step(3)           # 右移;render_mode='human' 时 step 末尾自动 render
        captured = capsys.readouterr()
        assert len(captured.out) > 0, "human 模式 step() 应自动触发 render 输出"

    # ------------------------------------------------------------------ #
    # TC-23  close() 方法                                                  #
    # ------------------------------------------------------------------ #

    @pytest.mark.unit
    def test_close_does_not_raise(self) -> None:
        """TC-23:close() 调用不抛出任何异常。

        输入:  reset(),close()
        期望:  无异常
        实测:  直接调用
        """
        env = MazeEnv(grid_size=5, obstacle_density=0.0, seed=0)
        env.reset()
        env.close()   # 无外部资源,不应抛出