標準入力をStringIOに置き換えてテストする
標準入出力をStringIOに置き換える
helloコマンドは標準入力を表示します。オプションtmp_optionは特に使われていません。
require 'thor'
require_relative '../lib/my_app'
class Command < Thor
desc 'hello', 'command for test'
option :tmp_option, :aliases =>'-t', :type => :string
def hello
puts($stdin.eof? ? "no pipe" : $stdin.read)
end
end
この標準入力をターミナル上ではなく、テストコードから入力します。全く同じには出来ないのですが、同じような動作をするStringIOをモックとして使います。StringIOはStringにIO、つまり入出力のメソッドがついたものです。
putsなどの出力は、StringIOをバッファとして溜め込まれます。そしてバッファから内容を出力してテストをするわけです。標準入力はグローバル変数$stdinに入っていますので、レシーバに$stdinを指定しているなら、これにStringIOを直接入れてもいいかもしれません。StringIOの詳細な動きは、最後にテストコードを貼っておきます。
$stdin.eof?で、標準入力がない場合を判断しています。
class HelloTest < Test::Unit::TestCase
test "hello" do
$stdin = StringIO.new("hello\nworld\n")
out = capture_output { Command.start(%w{hello -t hoge}) }[0]
assert_equal out, "hello\nworld\n"
end
標準入出力を切り替えられるようにする
直接$stdinに入れると、$stdinの影響がどこまであるか考える必要があります。なので、標準入出力を切り替えれるコネクタをインスタンス変数で用意して置くと、より安全になります。もちろんputsなど自分のコードで使っている同じメソッドがあるオブジェクトを使います。
class MyApp
attr_accessor :input, :output
def initialize
self.input = $stdin
self.output = $stdout
end
def say
output.puts(input.gets.chomp)
end
end
require 'thor'
require_relative '../lib/my_app'
class Command < Thor
desc 'hello', 'command for test'
option :tmp_option, :aliases =>'-t', :type => :string
def hello
puts $stdin.read
end
desc 'say', 'command for test'
option :tmp_option, :aliases =>'-t', :type => :string
def say
app = MyApp.new
app.input = StringIO.new(options['tmp_option'])
app.say
end
end
class HelloTest < Test::Unit::TestCase
test "hello" do
$stdin = StringIO.new("hello\nworld\n")
out = capture_output { Command.start(%w{hello -t hoge}) }[0]
assert_equal out, "hello\nworld\n"
end
test "say" do
$stdin = StringIO.new("hello")
out = capture_output { Command.start(%w{hello -t hoge}) }[0]
assert_equal out, "hello\n"
end
end
StringIOのテスト
putsなどの出力をStringIOに溜め込み、readやgetsで取り出します。readは全てを、getsは一行ずつ読み込みます。getsを使うと、プロパティlinenoが進められます。注意点としては、この2つを使う前にrewindを使ってポインタを先頭に戻す必要があります。この場合のポインタとは、内容をどこから読み込むかという目印です。putsなどで出力していくとポインタは末尾にあるので、それを先頭に戻してから読み込むわけです。
require 'test/unit'
class StringIOTEST < Test::Unit::TestCase
test "standard test" do
io = StringIO.new
assert_equal io.lineno, 0
io.puts('abcd')
io.puts('efg')
assert_equal io.lineno, 0
assert_equal io.read, ''
assert_true io.eof?
io.rewind
assert_false io.eof?
assert_equal io.lineno, 0
assert_equal io.read, "abcd\nefg\n"
assert_equal io.lineno, 0
assert_equal io.read, ''
io.rewind
assert_equal io.gets, "abcd\n"
assert_equal io.lineno, 1
assert_equal io.gets, "efg\n"
assert_equal io.lineno, 2
assert_nil io.gets
assert_equal io.lineno, 2
io.lineno = 0
assert_equal io.lineno, 0
assert_nil io.gets
assert_equal io.lineno, 0
io.rewind
assert_equal io.gets, "abcd\n"
assert_equal io.lineno, 1
io = StringIO.new("abcdefg")
assert_equal io.read, 'abcdefg'
end
end