You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单元测试中模拟按键操作?生命游戏控制台应用测试场景

解决生命游戏控制台应用单元测试的无限循环问题

看起来你遇到的核心问题是测试Run()方法时,因为没正确模拟控制台按键输入,导致测试陷入无限循环,根本没法走到验证步骤对吧?别担心,我们一步步来解决这个问题。

问题根源分析

你的测试代码有两个关键问题:

  1. Mock设置时机错误:你现在是先调用_application.Run(),再配置_fakeConsole的行为。这时候Run()已经进入循环了,Mock的默认行为(KeyAvailable返回false)会让LoopUntilButtonPressed里的while循环一直执行,永远不会响应后续的Setup。
  2. 嵌套循环的退出逻辑未模拟:Run()里有两层嵌套的LoopUntilButtonPressed,内层是渲染棋盘直到按下Esc,外层是重新开一局直到按下Esc。你需要模拟两次按键退出的场景,才能让整个Run()方法执行完毕。

修正后的测试代码

[TestFixture]
public class ApplicationTests {
    private Mock<IGame> _fakeGame;
    private Mock<IBoard> _fakeBoard;
    private Mock<IBoardGenerator> _fakeBoardGenerator;
    private Mock<IConsole> _fakeConsole;
    private Application _application;

    [SetUp]
    public void SetUp() {
        _fakeGame = new Mock<IGame>();
        _fakeBoard = new Mock<IBoard>();
        _fakeBoardGenerator = new Mock<IBoardGenerator>();
        _fakeConsole = new Mock<IConsole>();
        _application = new Application(_fakeGame.Object, _fakeBoard.Object, _fakeBoardGenerator.Object, _fakeConsole.Object);
    }

    [Test]
    public void Run_MethodCalled_GameCorrectlySet() {
        // 1. 先配置Mock行为,再调用Run()
        // 模拟内层循环:先执行一次渲染,然后检测到按键
        // 模拟外层循环:内层退出后,直接检测到按键退出
        _fakeConsole.SetupSequence(c => c.KeyAvailable)
            .Returns(false)   // 内层循环第一次检查:无按键,执行RenderBoard
            .Returns(true)    // 内层循环第二次检查:有按键,退出while
            .Returns(true);   // 外层循环检查:有按键,退出while

        _fakeConsole.SetupSequence(c => c.ReadKey(It.IsAny<bool>()))
            .Returns(ConsoleKey.Escape)  // 内层循环读取到Esc,退出内层Loop
            .Returns(ConsoleKey.Escape); // 外层循环读取到Esc,退出外层Loop

        // 2. 调用Run方法
        _application.Run();

        // 3. 验证预期行为
        _fakeGame.Verify(g => g.NewGame(), Times.Once);
        // 可选:验证渲染逻辑是否执行过
        _fakeBoard.Verify(b => b.Evolve(), Times.Once);
        _fakeBoard.Verify(b => b.Print(), Times.Once);
    }
}

关键修改说明

  • 提前配置Mock:在调用_application.Run()之前,就把KeyAvailable和ReadKey的行为序列配置好,确保循环执行时能按预期响应。
  • 模拟嵌套循环退出:用SetupSequence来模拟两次按键事件,第一次让内层渲染循环退出,第二次让外层游戏重启循环退出,这样整个Run()方法就能完整执行完毕。
  • 可选的额外验证:可以添加对_board.Evolve()和_board.Print()的调用验证,确保渲染逻辑也正常执行了。

额外优化建议

如果不想精确控制KeyAvailable的返回次数,也可以简化Mock配置,直接让KeyAvailable第一次返回false,之后都返回true:

bool firstCall = true;
_fakeConsole.Setup(c => c.KeyAvailable).Returns(() => {
    if (firstCall) {
        firstCall = false;
        return false;
    }
    return true;
});

_fakeConsole.SetupSequence(c => c.ReadKey(It.IsAny<bool>()))
    .Returns(ConsoleKey.Escape)
    .Returns(ConsoleKey.Escape);

这样既保证渲染逻辑执行一次,又能顺利退出两层循环。

内容的提问来源于stack exchange,提问作者voxtm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:22:19