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

如何使用RSpec测试choose_option方法通过SimpleCov覆盖率校验

RSpec覆盖递归调用choose_option方法的实现方案

问题描述

  • 已完成choose_option方法及配套辅助方法开发,目标是编写RSpec测试用例覆盖该方法逻辑,实现SimpleCov覆盖率100%。
  • 初始编写的测试代码存在笔误(except应为expect),代码如下:
allow(adapter).to receive(:gets).and_return('my_guess')
except(adapter).to receive(:adapter_my_guess)
  • 修正笔误后运行测试,出现调用次数不匹配报错:
expected: 1 time with any arguments
received: 2 times
  • 排查确认报错核心原因:wrapper方法会在游戏未获胜时递归调用choose_option,形成循环调用链路,导致方法被多次触发,不符合单次调用的断言预期。

涉及的业务代码如下:

def adapter_my_guess(guess)
  @game.instance_variable_set(:@secret_code, %w[1 2 3 4])
  if @game.attempts.positive?
     puts show_guess_result(@game.my_guess(guess))
  else
     puts I18n.t(:lose_game, secret_code: @game.instance_variable_get(:@secret_code))
  end
end

def wrapper(method_for_wrap)
  puts method_for_wrap
  @game.win == false ? choose_option : Finishe.new(@game, @statistics).finishe
end

def choose_option
  option = input(I18n.t(:start_game_options, option1: OPTION_1, option2: OPTION_2, exit: EXIT))
  case option
  when OPTION_1 then wrapper(adapter_my_guess(input(I18n.t(:puts_guess, exit: OR_EXIT))))
  when OPTION_2 then wrapper(adapter_give_hints)
  else
    raise(StandardError, WRONG_OPTION)
  end
rescue StandardError => e
  puts e
  choose_option
end

根因说明

测试代码未打断递归循环链路:第一次进入choose_option触发adapter_my_guess执行完wrapper逻辑后,因为@game.win默认返回false,会再次进入choose_option流程,第二次触发输入和adapter_my_guess调用,最终统计到2次调用,和once的断言冲突。如果不做链路打断,甚至会出现测试无限循环卡死的问题。

正确测试编写方案

核心思路是手动终止递归链路,按流程顺序模拟输入,分场景覆盖所有代码分支,即可通过断言并实现SimpleCov全绿:

  1. 按实际交互顺序模拟用户输入:流程中先读取菜单选项,再读取猜测值/其他操作输入,不要只返回单一输入值。
  2. 第一次执行完目标业务方法后,修改@game.win的返回值为true,让wrapper走到游戏结束分支,不再递归调用choose_option。
  3. 分三个场景写用例,覆盖所有分支:选择选项1猜数字、选择选项2拿提示、输入非法选项触发异常重试,避免遗漏rescue代码块的覆盖。

参考测试代码

场景1:选择选项1走猜数字流程

it '选择猜数字选项时正确调用adapter_my_guess' do
  # 按调用顺序返回输入值:第一次选菜单选项1,第二次输入猜测内容1234
  allow(adapter).to receive(:gets).and_return(OPTION_1, '1234')
  # adapter_my_guess执行完成后,将游戏状态改为获胜,打断递归
  allow(adapter).to receive(:adapter_my_guess).and_wrap_original do |origin_method, *args|
    origin_method.call(*args)
    allow(adapter.instance_variable_get(:@game)).to receive(:win).and_return(true)
  end

  expect(adapter).to receive(:adapter_my_guess).once
  adapter.choose_option
end

场景2:选择选项2走提示流程

it '选择提示选项时正确调用adapter_give_hints' do
  allow(adapter).to receive(:gets).and_return(OPTION_2)
  allow(adapter).to receive(:adapter_give_hints).and_wrap_original do |origin_method, *args|
    origin_method.call(*args)
    allow(adapter.instance_variable_get(:@game)).to receive(:win).and_return(true)
  end

  expect(adapter).to receive(:adapter_give_hints).once
  adapter.choose_option
end

场景3:输入非法选项触发重试

it '输入非法选项时打印错误并重试选择' do
  # 第一次返回非法选项,第二次返回选项2走正常流程
  allow(adapter).to receive(:gets).and_return('invalid_option', OPTION_2)
  allow(adapter).to receive(:adapter_give_hints).and_wrap_original do |origin_method, *args|
    origin_method.call(*args)
    allow(adapter.instance_variable_get(:@game)).to receive(:win).and_return(true)
  end

  expect { adapter.choose_option }.to output(/#{WRONG_OPTION}/).to_stdout
  expect(adapter).to receive(:adapter_give_hints).once
end

注意:不要在未处理递归逻辑的前提下直接断言方法调用次数,递归场景必须手动终止循环,否则必然出现调用次数不匹配、测试卡死的问题。所有分支覆盖完成后,SimpleCov即可达到全绿要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:15:44