見出し画像

標準添付ライブラリにあるstringioの適用例

.stdio_coverage(ary = [{}], buf_in = '', buf_out = '')

Rubyを覚えたての頃、標準添付ライブラリのstringioって、なんか意味あるの?わしに関係あるんかいな?と不思議に思いながら、いつしか忘却の彼方へと捨て置いてしまう今日もポンコツ全開のわたしです。

class MyClass
  def initialize
    @asking         = 'あなたは男性ですか?女性ですか?(man/woman)'
    @reply_to_man   = 'さようなら。'
    @reply_to_woman = 'こんにちは。'
  end

  def greeting
    your_reply = ask
    my_reply(your_reply)
  end

  private

  def ask
    print "#{@asking} ==> "
    gets.chomp
  end

  def my_reply(your_reply)
    if your_reply == 'woman'
      print "#{@reply_to_woman}\n"
    else # 入力チェックが甘いために'woman'以外は全部'man'の扱いになってることナイショです
      print "#{@reply_to_man}\n"
    end
  end
end

随分とふざけた例題ですが、.greetingメソッドを同値テストでテストしたくなったのですが、はたと困ってしまいました。
getsは、返り値があるのですが、printって返り値がないんですよ!
取得もできない値をどうやってテストするん?
右往左往してたら、stringioのことを思い出して、
あ~、これ使うのかぁ~。
となり、出来たのが
.stdio_coverage(ary = [{}], buf_in = '', buf_out = '')

require 'stringio'
module TOOLS
  # Purpose: Switch the environment from StandardIO to StringIO
  #             to provide a test environment
  #   ary: Array of hashes
  #   buf_in, buf_out: Don't perform destructive operations
  def stdio_coverage(ary = [{}], buf_in = '', buf_out = '')
    # Simple usage:
    #   ary = [{ typing: "y\n", exp: "Hello\n" }, { typing: "n\n", exp: "bye\n" }]
    #   stdio_coverage(ary) do |h, buf_in, buf_out|
    #     buf_in << h[:typing]
    #     @object.method
    #     assert_equal h[:exp],
    #                  buf_out
    #   end
    #
    # Applied usage1: It can be used to check the branch status.
    #   This is an example if you accidentally typed Eys and retyped Yes.
    #   ary = [{ typing: "Eys\nYes\n", exp: "retry again\nHello\n"}]
    #   stdio_coverage(ary) do |h, buf_in, buf_out|
    #     buf_in << h[:typing]
    #     @object.method
    #     assert_equal h[:exp],
    #                  buf_out
    #   end
    #
    # Applied usage2: You can use it to suppress echo by doing the following.
    #   stdio_coverage do
    #     @object.method
    #   end
    # or
    #   exp = "expected display"
    #   stdio_coverage do |_, _, buf_out|
    #     @object.method
    #     assert_equal exp,
    #                  buf_out
    #   end
    #
    # notice: buf_in << ... Since you are working directly with the buffer,
    #                         you need to add additional data to the buffer from the beginning.
    #         buf_out ... It will be processed by the method if necessary,
    #                       but the buffer must not be destructively manipulated.
    $stdin  = StringIO.new(buf_in)
    $stdout = StringIO.new(buf_out)

    ary.each do |h|
      yield h, buf_in, buf_out

      buf_in  = ''; $stdin.string  = buf_in
      buf_out = ''; $stdout.string = buf_out
    end

    $stdin  = STDIN
    $stdout = STDOUT
  end
end

中身が少しなのにコメントがダラダラで申しわけないですが、ご勘弁。
やっていることは、皮(がわ)で標準入出力をstdioで入れかえて、後で戻して、ハッシュの配列をブロックでゴチョゴチョできるようにしてあるだけです。
キモは、ブロック内で参照できるように、StringIO.new(buf_in)/StringIO.new(buf_out)と言うようにbuf_in/buf_outと言うネームド・バッファ(名前付きバッファ)にしているところです。
ただし、buf_in/buf_outは、StringIOでどうもポインター管理されているようなので、破壊的な操作をすると大抵そこで死にます。実際の使用は参照だけと思ってください。後は、引数の(ary = [{}], buf_in = '', buf_out = '')の中のbuf_inとbuf_outは、呼び出しには通常使いません。使うのはもっぱら、aryだけです。

これに前回の投稿の.make_hash_list_in_array(list)を咬ませると次のテストコードになります。(make_hash_list_in_array(list)は、module TOOLSに既に存在すると仮定しています)

require 'minitest/autorun'
class MyClassTest < MiniTest::Test
  include TOOLS

  def setup
    @my_class = MyClass.new
  end

  def test_greeting
    question  = [
      :question,
      'あなたは男性ですか?女性ですか?(man/woman) ==> ',
      'あなたは男性ですか?女性ですか?(man/woman) ==> '
    ]
    typing    = [:typing,   "man\n", "woman\n"]
    reply     = [:reply,    "さようなら。\n", "こんにちは。\n"]
    list      = [question, typing, reply]
    hash_list = make_hash_list_in_array(list) do |h|
      h[:exp] = h[:question] + h[:reply]
    end

    stdio_coverage(hash_list) do |h, buf_in, buf_out|
      buf_in << h[:typing] # buf_inへの値の設定は、'<<'これで決め打ちです。
      @my_class.greeting
      assert_equal h[:exp],
                   buf_out
    end
  end
end

エピローグ:このメソッドも結構気に入っていて使ってます。条件分岐のテストに出力メッセージを追いかけて、分岐の検証にしてます。
自分はポンコツなので、stdio_coverageなんて名前を付けてしまってますが、合ってるんでしょうかね。物凄い恥ずかしいことしている気がしてなりません。ピッタリのメソッド名があったら、教えてくださいませ。
あと、自分で言うのもなんですが、投稿ペースが早すぎますよね。
あっと言う間にネタが尽きそうです。はしゃいでいるのが、モロばれなのがとても恥ずかしいです。あはは。

noteの検索で、rubyと入れても新着のリストに出てきません。エンガチョされているのでしょうか?ポンコツで入れると出てくるようです。ええ、そうですよ。どーせ、わたしは、ポンコツですよ。

この記事が気に入ったらサポートをしてみませんか?