【问题标题】:Testing a complicated method测试一个复杂的方法
【发布时间】:2011-11-30 17:09:09
【问题描述】:

我正在开发一个跳棋实现,其中有许多易于测试的方法,但我不确定如何测试我的主要 #play_game 方法。在我的大多数方法很容易确定输入和输出的情况下,因此很容易测试,但这种方法是多方面的,实际上并没有容易辨别的输出。代码如下:

def play_game
    puts @gui.intro

    while(game_over? == false)
      message = nil
      @gui.render_board(@board)
      @gui.move_request
      player_input = gets 
      coordinates = UserInput.translate_move_request_to_coordinates(player_input) 

      message = MoveCheck.move_validator(coordinates[0], coordinates[1], coordinates[2], coordinates[3])
      puts message unless (message.nil? or message == "jumping move")
      if(message == nil or message == "jumping move")
        @current_player = switch_player unless (message == "jumping move" and jump_available? == true)
      end
    end
    puts @gui.display_game_ending_message  
  end

那么我该如何测试这个(使用 RSpec)或者我不应该担心它并且真的在我的其他综合测试中?

【问题讨论】:

  • FWIW,请注意,如果您先进行此测试,那么您当然会拥有易于测试的方法。
  • @MarnenLaibow-Koser,此外,您也已经进行了测试!

标签: ruby testing rspec


【解决方案1】:

所有play_game 真正做的是运行游戏循环。您真正想要测试的是游戏循环内部会发生什么。最简单的方法是将游戏循环的内容分解为更易于测试的方法。

一旦您将游戏循环仅作为一系列方法,您就可以更轻松地单独测试它们中的每一个。

【讨论】:

  • 您的建议非常合理,但在问题的上下文中我不理解它,因为代码已经使用了小方法(渲染、移动、翻译......),我没有不知道 OP 将如何进一步分裂它们。在我看来,真正的问题是他有一个循环运行整个游戏,这确实很难测试。
  • 绝对!您还应该考虑这样一个事实,即并非所有内容都易于测试,也不是所有内容都应该进行测试。对测试什么要务实……将逻辑分解为可测试的不同方法。如果您有一个打印消息的方法,然后调用一个执行繁重工作并一遍又一遍地执行此操作的方法,如果它不是很大,请考虑不对其进行测试,并考虑测试底层方法。但是,您应该考虑测试整个流程(按顺序调用方法 A、B、C 等等)。我希望这是有道理的...否则我可以通过示例详细说明。
  • @tokland 如上所述,运行循环内部发生的逻辑应该分解为单独的方法。例如:跳跃动作检查应该是它自己的方法......它更具可读性和可测试性。您可以测试导致该方法的所有场景,而无需接触游戏循环!
  • @vinnybad: 是的,毫无疑问,你可以进一步改进这个主循环并将其拆分为更多独立的方法,但最后还必须测试游戏的主逻辑,否则你可能会结束一堆小方法工作正常,主循环坏了!
  • @tokland 我明白你想说什么。让我试着换一种说法:如果您将逻辑分解为更高级别的方法,您可以为每个更高级别的方法创建每个输入场景......因此您正在测试您的主循环。如果可能的话,您应该考虑将主循环中的逻辑放入更高级别的方法中……这样就可以对其进行测试。请记住,并非每种方法都需要进行测试……想象一下测试内核调度程序!我在一次会议上从一个人那里听到的有趣事实:Linux 内核没有单元测试。公司发现错误并报告它们。
【解决方案2】:

我会将 while 循环内的所有代码移动到单独的方法中。通过这种方式,您可以轻松地对其进行测试,方法是向其提供不同的游戏状态作为输入,并检查它们是否已成功处理(以获得游戏状态)。请注意,遵循函数式编程原则的代码更容易测试 (new_state = process(old_state)),但无论如何,您仍然可以按原样进行测试,检查 @gui 是否按照您期望的方式更新了之前的状态。

您的主要play_game 方法现在很简单:

def play_game
  process(@gui) until game_over?
end

[编辑] 让我举一个真实的例子。为了使用 Raphael.js 和 CoffeeScript,我编写了一个黑白棋引擎(reversi.coffee,这里是spec)。您问题中的主循环代码在这里被隔离在一个无状态函数中:move。所以我可以做 new_state = move(old_state) 并用我认为合适的所有对 *old_state*/*expected_new_states* 进行测试。

【讨论】:

  • 喂食已知的游戏状态对,以及采取的行动,是一个好主意。特别是如果您将其构建到测试框架中,您可以轻松地针对在边缘情况下的游戏状态中发现的旧错误运行重复测试,并根据需要测试常见状态或它们的细微变化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-22
  • 2013-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多