標準添付ライブラリにある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と入れても新着のリストに出てきません。エンガチョされているのでしょうか?ポンコツで入れると出てくるようです。ええ、そうですよ。どーせ、わたしは、ポンコツですよ。