はじめに
この記事は Ruby Advent Calendar 2019 21日目の記事です。
今回はRubyのテストフレームワークであるMinitestのassert系メソッドを全部試してみました。
この記事がMinitestを使っている人に少しでも役立てばいいなと思います。
環境は以下のとおりです。
Ruby
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]
Minitest
Minitest::VERSION #=> "5.13.0"
それでは頑張っていきましょう。
assert(test, msg = nil)
test
が真(nil
、false
以外)の場合にアサーションが成功します。
msg
を指定するとアサーション失敗時に指定したメッセージを表示します。
(これ以降紹介するメソッドでもmsg
を引数に取れるメソッドがありますが、すべて同じ機能のため、ここでのみ紹介します。)
成功例
assert true
assert 0
assert :sym
assert "hello"
失敗例
assert false
Expected false to be truthy.
失敗例(メッセージあり)
assert nil, "nilだよ"
nilだよ
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert
assert_empty(obj, msg = nil)
obj.empty?
が真の場合にアサーションが成功します。
empty?
を実装していないオブジェクトを渡した場合はメッセージが表示され、アサーションに失敗します。
成功例
assert_empty({}) # {}がブロックと判定されてしまうため、()で括る
assert_empty []
assert_empty ""
失敗例
assert_empty({name: "TomoProg"})
assert_empty [1, 2, 3]
Expected {:name=>"TomoProg"} to be empty.
Expected [1, 2, 3] to be empty.
失敗例(empty?を実装していない)
assert_empty 0
Expected 0 (Integer) to respond to #empty?.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_empty
assert_equal(exp, act, msg = nil)
exp == act
が真の場合にアサーションが成功します。
Minitestのバージョン6からはexp
にnil
を指定するとアサーションが失敗するようになるようです。
そのため、exp
にnil
を指定した場合、assert_nil
を使うように警告が出ます。
成功例
assert_equal 1, 1
assert_equal "sample", "sample"
失敗例
assert_equal 2, 3
Expected: 2
Actual: 3
expにnilを指定した場合
assert_equal nil, nil
DEPRECATED: Use assert_nil if expecting nil from test/sample_test.rb:5.
This will fail in Minitest 6.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_equal
assert_in_delta(exp, act, delta = 0.001, msg = nil)
exp
とact
の差分((exp - act).abs
)がdelta
以下の場合にアサーションに成功します。
浮動小数点数の比較の際に誤差を許容する場合に使うようです。
成功例
assert_in_delta 0.001, 0.002
assert_in_delta 0.3, (0.1 + 0.2), 0.000001
失敗例
assert_in_delta 1.256, 1.255
Expected |1.256 - 1.255| (0.001000000000000112) to be <= 0.001.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_in_delta
assert_in_epsilon(exp, act, epsilon = 0.001, msg = nil)
誤差に関しての理解不足もあり、このメソッドに関してはよく分かりませんでした。
assert_in_delta
と同じように浮動小数点数の誤差を許容する場合に使うようですが、
assert_in_delta
が絶対誤差を許容するのに対し、こちらは相対誤差を許容するようです。
参考になりそうなリンクを貼っておきます。
[Minitestで絶対誤差と相対誤差を利用したテストを実行する] (https://qiita.com/jnchito/items/d2e95190daa63edaaff1)
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_in_epsilon
assert_includes(collection, obj, msg = nil)
collection.include?(obj)
が真の場合にアサーションに成功します。
include?(obj)
を実装していないオブジェクトを渡した場合はメッセージが表示され、アサーションに失敗します。
成功例
assert_includes [1, 2, 3], 2
assert_includes({name: "TomoProg"}, :name) # Hash#include?(key)はハッシュにkeyがあれば真となる
失敗例
assert_includes [1, 2, 3], 0
Expected [1, 2, 3] to include 0.
失敗例(include?(obj)を実装していない)
assert_includes 1, 1
Expected 1 (Integer) to respond to #include?.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_includes
assert_instance_of(cls, obj, msg = nil)
obj.instance_of?(cls)
が真の場合にアサーションに成功します。
成功例
assert_instance_of Array, [1, 2, 3]
assert_instance_of Integer, 1
失敗例
assert_instance_of Integer, 1.2
Expected 1.2 to be an instance of Integer, not Float.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_instance_of
assert_kind_of(cls, obj, msg = nil)
obj.kind_of?(cls)
が真の場合にアサーションに成功します。
assert_instance_of
に似ていますが、assert_kind_of
はサブクラスでもアサーションに成功します。
成功例
class Base; end
class SuperClass < Base; end
assert_kind_of Base, SuperClass.new
assert_kind_of SuperClass, SuperClass.new
失敗例
assert_kind_of Integer, 1.2
Expected 1.2 to be a kind of Integer, not Float.
assert_instance_ofとの違い
class Base; end
class SuperClass < Base; end
assert_instance_of Base, SuperClass.new # assert_instance_ofではサブクラスを指定すると失敗する
assert_kind_of Base, SuperClass.new # assert_kind_ofではサブクラスでも成功する
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_kind_of
assert_match(matcher, obj, msg = nil)
matcher =~ obj
が真の場合にアサーションに成功します。
成功例
assert_match /rin/, "string"
assert_match /\d{3}-\d{4}-\d{3}/, "123-4567-890"
失敗例
assert_match /rig/, "string"
Expected /rig/ to match # encoding: UTF-8
# valid: true
"string".
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_match
assert_mock(mock)
mock.verify
が真の場合にアサーションに成功します。
Minitestではメソッド呼び出しが正しく行われているかを確認するために、モックを作ることができます。
verify
はモックに定義したメソッドがすべて呼び出されていればtrue
を返し、呼び出されていないメソッドがあればMockExpectationError
を返します。
サンプルクラス
class Sample
def initialize(obj)
@obj = obj
end
def call
@obj.method1
end
end
成功例
# モックにメソッドを定義する
# (第1引数にメソッド名、第2引数に戻り値を指定する)
mock = Minitest::Mock.new
mock.expect "method1", true
# Sample#callを呼び出す
Sample.new(mock).call
# モックに定義したすべてのメソッドが呼ばれたため、このアサーションは成功する
assert_mock mock
失敗例
# モックにメソッドを定義する
# (第1引数にメソッド名、第2引数に戻り値を指定する)
mock = Minitest::Mock.new
mock.expect "method1", true
mock.expect "method2", true
# Sample#callを呼び出す
Sample.new(mock).call
# method2が呼ばれていないため、このアサーションは失敗する
assert_mock mock
MockExpectationError: expected method2() => true
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_mock
assert_nil(obj, msg = nil)
obj.nil?
が真の場合にアサーションに成功します。
成功例
assert_nil nil
失敗例
assert_nil 1
Expected 1 to be nil.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_nil
assert_operator(o1, op, o2 = UNDEFINED, msg = nil)
o1.__send__(op, o2)
が真の場合にアサーションに成功します。
2項演算子をテストするためのアサーションです。
o2
がUNDEFINED
の場合は後述するassert_predicate
が呼び出されます。
UNDEFINED
定数はMinitest::Assersions
で定義されており、Object.new
が代入されていました。
成功例
assert_operator 5, :<=, 6
失敗例
assert_operator 5, :<=, 4
Expected 5 to be <= 4.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_operator
assert_output(stdout = nil, stderr = nil) { || ... }
標準出力がstdout
、標準エラー出力がstderr
の場合にアサーションに成功します。
ブロックに標準出力、標準エラー出力を扱う処理を記述して使います。
正規表現を渡すことも可能です。
また、nil
を指定した場合はテストされません。
成功例
# 標準出力、標準エラー出力をテストする
assert_output("sample\n", "stderr_sample\n") do
puts "sample"
warn "stderr_sample" # Kernel.#warnは指定された文字列を標準エラー出力に出力する
end
# 正規表現も指定できる
assert_output(/str/) do
puts "string"
end
# nilを指定した場合はテストされない
assert_output(nil, "stderr_sample\n") do
warn "stderr_sample"
end
失敗例
assert_output("sample\n") do
puts "saaaample"
end
In stdout.
--- expected
+++ actual
@@ -1,4 +1,2 @@
-# encoding: UTF-8
-# valid: true
-"sample
+"saaaample
"
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_output
assert_path_exists(path, msg = nil)
File.exist?(path)
が真の場合にアサーションに成功します。
ディレクトリ構成
sample/
test1.txt
成功例
assert_path_exists "sample/test1.txt"
失敗例
assert_path_exists "sample/test2.txt"
Expected path 'sample/test2.txt' to exist.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_path_exists
assert_predicate(o1, op, msg = nil)
o1.__send__(op)
が真の場合にアサーションに成功します。
テストしたいop
が複数ある場合は配列で持たせると綺麗に書けそうです。
成功例
ops = %i(all? any?)
ops.each { |op| assert_predicate [1, 2, 3], op }
失敗例
assert_predicate [1, 2, 3], :empty?
Expected [1, 2, 3] to be empty?.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_predicate
assert_raises(*exp) { || ... }
*exp
に指定した例外がどれか一つでもブロック内で発生した場合にアサーションに成功します。
最後に文字列を指定するとメッセージとして扱われ、アサーションの失敗時に出力されます。
また、アサーションに成功した場合は例外クラスを返します。
成功例
assert_raises(ZeroDivisionError) do
1 / 0
end
# 例外が戻り値となる
exp = assert_raises(ZeroDivisionError, ArgumentError) do
[1, 2, 3].include?
end
assert_kind_of ArgumentError, exp
# ブロック内で例外が複数起きる場合は先に起きた例外が戻り値となる
exp = assert_raises(ZeroDivisionError, ArgumentError) do
1 / 0
[1, 2, 3].include?
end
assert_kind_of ZeroDivisionError, exp
失敗例
assert_raises(ZeroDivisionError) do
1 / 1
end
ZeroDivisionError expected but nothing was raised.
失敗例(メッセージ有り)
assert_raises(ZeroDivisionError, ArgumentError, "No Error") do
[1, 2, 3].include?(1)
end
No Error.
[ZeroDivisionError, ArgumentError] expected but nothing was raised.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_raises
assert_respond_to(obj, meth, msg = nil)
obj.respond_to?(meth)
が真の場合にアサーションに成功します。
成功例
assert_respond_to([1, 2, 3], :shift)
assert_respond_to({name: "TomoProg"}, :dig)
失敗例
assert_respond_to([1, 2, 3], :unknown_method)
Expected [1, 2, 3] (Array) to respond to #unknown_method.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_respond_to
assert_same(exp, act, msg = nil)
obj.equal?(act)
が真の場合にアサーションに成功します。
equal?
はobject_id
で比較するため、assert_equal
とは違う結果となる場合があります。
成功例
assert_same 1, 1
assert_same :sym, :sym
assert_same nil, nil
失敗例
assert_same [1, 2, 3], [1, 2, 3]
Expected [1, 2, 3] (oid=47357985755040) to be the same as [1, 2, 3] (oid=47357985755100).
assert_equalと違う結果になる例
# 中身が同じ配列を準備する
array1 = [1, 2, 3]
array2 = [1, 2, 3]
# assert_equalでは成功する
# ==で比較するため、中身が同じであれば真となる
assert_equal array1, array2
# assert_sameでは失敗する
# equal?で比較するため、object_idが異なると偽となる
assert_same array1, array2
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_same
assert_send(send_ary, m = nil)
このメソッドは非推奨のようです。
理由を探したところ、下記のサイトに記述がありました。
日々雑記 — Minitest 5.10.0でdeprecateになった機能
assert_sendがdeprecateになった理由がちょっとわからなかったのでtwitterで聞いてみた所、単純に使っている所を見たことが無い為、との事でした。
send_ary
に第一要素にレシーバとなる任意のオブジェクト、第二要素にメソッド名、 第三要素にパラメータをそれぞれ指定した配列を指定し、指定したオブジェクトのメソッドが真であればアサーションに成功します。
なぜかこのメソッドだけメッセージの引数名がmsg
ではなくm
でした。
成功例
assert_send [[1, 2, 3], "include?", 1]
DEPRECATED: assert_send.
失敗例
assert_send [[1, 2, 3], :include?, 4]
DEPRECATED: assert_send.
Expected [1, 2, 3].include?(*[4]) to return true.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_send
assert_silent() { || ... }
標準出力、標準エラー出力に何も出力されない場合にアサーションに成功します。
assert_output("", "")
と書くのと同じ意味です。
成功例
assert_silent { 1 + 1 }
失敗例(標準出力)
assert_silent { puts "hello" }
In stdout.
--- expected
+++ actual
@@ -1,3 +1,2 @@
-# encoding: UTF-8
-# valid: true
-""
+"hello
+"
失敗例(標準エラー出力)
assert_silent { warn "warning" }
In stderr.
--- expected
+++ actual
@@ -1,3 +1,2 @@
-# encoding: UTF-8
-# valid: true
-""
+"warning
+"
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_silent
assert_throws(sym, msg = nil) { || ... }
指定したブロック内でsym
に対してthrow
されるとアサーションに成功します。
成功例
assert_throws(:exit) do
[1, 2, 3].each do |a|
[4, 5, 6].each do |b|
throw :exit
end
end
end
失敗例
assert_throws(:exit) do
[1, 2, 3].each do |a|
[4, 5, 6].each do |b|
throw :unknown
end
end
end
Expected :exit to have been thrown, not :unknown.
ドキュメント
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert_throws
まとめ
Minitest::Assersionsのassert系のメソッドをすべて試してみました。
Minitestを業務で使い始めてもうすぐ3年が経ちますが、こんなにあったとは知りませんでした。
様々なassertメソッドを試してみて一番感じたのは
適切に使い分けることで失敗の原因がより分かりやすくなる
という点です。
例えば、以下のアサーションは結果はどちらも失敗です。
しかし、後者のほうが何が原因で失敗したのかを分かりやすく伝えてくれます。
assert [1, 2, 3].include?(0) # Expected false to be truthy.
assert_includes [1, 2, 3], 0 # Expected [1, 2, 3] to include 0.
適切なメソッドを使い分け、分かりやすいテストコードを書いていきましょう。
それではまた。
TomoProg