如何使用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全绿:
- 按实际交互顺序模拟用户输入:流程中先读取菜单选项,再读取猜测值/其他操作输入,不要只返回单一输入值。
- 第一次执行完目标业务方法后,修改
@game.win的返回值为true,让wrapper走到游戏结束分支,不再递归调用choose_option。 - 分三个场景写用例,覆盖所有分支:选择选项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
相关产品推荐
相关产品推荐

