RSpecで標準入出力をどんどんテストする。

paizaで1年前とけなかった問題がとけて嬉しがって回答コードをブログに載せたら当たり前のように怒られるという体験をしました。ごめんなさい。

ところで標準入出力のテストがしたくなった場合

こういう標準入出力のあるやつに対して

最初の入力値が入力回数になる、Cの教科書などでもお馴染みの形式です。

#
# 本体
#
class Work
  attr_accessor :data

  def initialize(data)
    self.data = data
  end

  def run
    data.join('::')
  end
end

#
# 入出力担当
#
class Keeper
  class << self
    def run
      total_input = STDIN.gets.to_i

      data = (1..total_input).to_a.map do
        STDIN.gets.chomp
      end

      STDOUT.puts(Work.new(data).run)
    end
  end
end

#
# ruby ./std.rb された時のみ
#
if __FILE__ == $0
  Keeper.run
end

こんな感じでスタブしておいて

require './std.rb'
require 'rspec'

describe do
  let(:input) { @input }

  before :each do
    allow(STDIN).to receive(:gets) do
      input.shift.to_s + "\n"
    end

    allow(STDOUT).to receive(:puts) do |val|
      val.to_s + "\n"
    end
  end

  # 下記のテストが入る
end

テストはこんな感じで

@inputに入力値を配列で設定します。

  describe '入力した文字が連結されて帰ってくる' do
    it do
      @input = %w(3 a b c)
      expect(Keeper.run).to eq("a::b::c\n")
    end

    it do
      @input = %w(4 b c a a)
      expect(Keeper.run).to eq("b::c::a::a\n")
    end
  end

exampleがすっきりしているのがタイトルのどんどんっぽい部分です。

テスト

$ rspec std_spec.rb 

  入力した文字が連結されて帰ってくる
    should eq "a::b::c\n"
    should eq "b::c::a::a\n"

Finished in 0.00484 seconds (files took 0.0609 seconds to load)
2 examples, 0 failures

標準入出力のケツには改行が入るので、それを意識するのがコツと言えばコツです。

ところでoutput().to_stdoutというのがある

putsではなくSTDOUT.putsを使っていると、以下の記法ではexpected block to output "b::c::a::a\n" to stdout, but output nothingになります。

expect { Keeper.run }.to output("b::c::a::a\n").to_stdout

putsだとどっちでもOK。

うーん?