Minitest::Assersionsのassert系メソッドを全部試してみた

# はじめに
この記事は [Ruby Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ruby) 21日目の記事です。
今回はRubyのテストフレームワークであるMinitestのassert系メソッドを全部試してみました。
この記事がMinitestを使っている人に少しでも役立てばいいなと思います。
環境は以下のとおりです。

**Ruby**

```
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]
```

**Minitest**

```ruby
Minitest::VERSION #=> "5.13.0"
```

それでは頑張っていきましょう。

# assert(test, msg = nil)
`test`が真（`nil`、`false`以外）の場合にアサーションが成功します。
`msg`を指定するとアサーション失敗時に指定したメッセージを表示します。
（これ以降紹介するメソッドでも`msg`を引数に取れるメソッドがありますが、すべて同じ機能のため、ここでのみ紹介します。）

**成功例**

```ruby
assert true
assert 0
assert :sym
assert "hello"
```

**失敗例**

```ruby
assert false
```
```
Expected false to be truthy.
```

**失敗例（メッセージあり）**

```ruby
assert nil, "nilだよ"
```

```
nilだよ
```

**ドキュメント**
http://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-assert

# assert_empty(obj, msg = nil)
`obj.empty?`が真の場合にアサーションが成功します。
`empty?`を実装していないオブジェクトを渡した場合はメッセージが表示され、アサーションに失敗します。

**成功例**

```ruby
assert_empty({}) # {}がブロックと判定されてしまうため、（）で括る
assert_empty []
assert_empty ""
```

**失敗例**

```ruby
assert_empty({name: "TomoProg"})
assert_empty [1, 2, 3]
```
```
Expected {:name=>"TomoProg"} to be empty.
Expected [1, 2, 3] to be empty.
```

**失敗例（empty?を実装していない）**

```ruby
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`を使うように警告が出ます。

**成功例**

```ruby
assert_equal 1, 1
assert_equal "sample", "sample"
```

**失敗例**

```ruby
assert_equal 2, 3
```

```
Expected: 2
  Actual: 3
```

**expにnilを指定した場合**

```ruby
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`以下の場合にアサーションに成功します。
浮動小数点数の比較の際に誤差を許容する場合に使うようです。

**成功例**

```ruby
assert_in_delta 0.001, 0.002
assert_in_delta 0.3, (0.1 + 0.2), 0.000001
```

**失敗例**

```ruby
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)`を実装していないオブジェクトを渡した場合はメッセージが表示され、アサーションに失敗します。

**成功例**

```ruby
assert_includes [1, 2, 3], 2
assert_includes({name: "TomoProg"}, :name) # Hash#include?(key)はハッシュにkeyがあれば真となる
```

**失敗例**

```ruby
assert_includes [1, 2, 3], 0
```
```
Expected [1, 2, 3] to include 0.
```

**失敗例（include?(obj)を実装していない）**

```ruby
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)`が真の場合にアサーションに成功します。

**成功例**

```ruby
assert_instance_of Array, [1, 2, 3]
assert_instance_of Integer, 1
```

**失敗例**

```ruby
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`はサブクラスでもアサーションに成功します。

**成功例**

```ruby
class Base; end
class SuperClass < Base; end

assert_kind_of Base, SuperClass.new
assert_kind_of SuperClass, SuperClass.new
```

**失敗例**

```ruby
assert_kind_of Integer, 1.2
```
```
Expected 1.2 to be a kind of Integer, not Float.
```

**assert_instance_ofとの違い**

```ruby
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`が真の場合にアサーションに成功します。

**成功例**

```ruby
assert_match /rin/, "string"
assert_match /\d{3}-\d{4}-\d{3}/, "123-4567-890"
```

**失敗例**

```ruby
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`を返します。

**サンプルクラス**

```ruby 
class Sample
  def initialize(obj)
    @obj = obj
  end

  def call
    @obj.method1
  end
end
```

**成功例**

```ruby 
# モックにメソッドを定義する
# （第1引数にメソッド名、第2引数に戻り値を指定する）
mock = Minitest::Mock.new
mock.expect "method1", true

# Sample#callを呼び出す
Sample.new(mock).call

# モックに定義したすべてのメソッドが呼ばれたため、このアサーションは成功する
assert_mock mock
```

**失敗例**

```ruby 
# モックにメソッドを定義する
# （第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?`が真の場合にアサーションに成功します。

**成功例**

```ruby 
assert_nil nil
```

**失敗例**

```ruby 
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)`が真の場合にアサーションに成功します。
２項演算子をテストするためのアサーションです。
`o2`が`UNDEFINED`の場合は後述する`assert_predicate`が呼び出されます。
`UNDEFINED`定数は`Minitest::Assersions`で定義されており、`Object.new`が代入されていました。

**成功例**

```ruby 
assert_operator 5, :<=, 6
```

**失敗例**

```ruby 
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`を指定した場合はテストされません。

**成功例**

```ruby
# 標準出力、標準エラー出力をテストする
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
```

**失敗例**

```ruby 
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
```

**成功例**

```ruby
assert_path_exists "sample/test1.txt"
```

**失敗例**

```ruby 
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`が複数ある場合は配列で持たせると綺麗に書けそうです。

**成功例**

```ruby
ops = %i(all? any?)
ops.each { |op| assert_predicate [1, 2, 3], op }
```

**失敗例**

```ruby 
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`に指定した例外がどれか一つでもブロック内で発生した場合にアサーションに成功します。
最後に文字列を指定するとメッセージとして扱われ、アサーションの失敗時に出力されます。
また、アサーションに成功した場合は例外クラスを返します。

**成功例**

```ruby
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
```

**失敗例**

```ruby
assert_raises(ZeroDivisionError) do
  1 / 1
end
```
```
ZeroDivisionError expected but nothing was raised.
```

**失敗例（メッセージ有り）**

```ruby
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)`が真の場合にアサーションに成功します。

**成功例**

```ruby
assert_respond_to([1, 2, 3], :shift)
assert_respond_to({name: "TomoProg"}, :dig)
```

**失敗例**

```ruby 
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`とは違う結果となる場合があります。

**成功例**

```ruby
assert_same 1, 1
assert_same :sym, :sym
assert_same nil, nil
```

**失敗例**

```ruby 
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と違う結果になる例**

```ruby
# 中身が同じ配列を準備する
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になった機能](https://y-yagi.tumblr.com/post/154438810385/minitest-5100%E3%81%A7deprecate%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E6%A9%9F%E8%83%BD)
>assert_sendがdeprecateになった理由がちょっとわからなかったのでtwitterで聞いてみた所、単純に使っている所を見たことが無い為、との事でした。

`send_ary`に第一要素にレシーバとなる任意のオブジェクト、第二要素にメソッド名、 第三要素にパラメータをそれぞれ指定した配列を指定し、指定したオブジェクトのメソッドが真であればアサーションに成功します。

なぜかこのメソッドだけメッセージの引数名が`msg`ではなく`m`でした。

**成功例**

```ruby
assert_send [[1, 2, 3], "include?", 1]
```
```
DEPRECATED: assert_send.
```

**失敗例**

```ruby 
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("", "")`と書くのと同じ意味です。

**成功例**

```ruby
assert_silent { 1 + 1 }
```

**失敗例（標準出力）**

```ruby 
assert_silent { puts "hello" }
```
```
In stdout.
--- expected
+++ actual
@@ -1,3 +1,2 @@
-# encoding: UTF-8
-#    valid: true
-""
+"hello
+"
```

**失敗例（標準エラー出力）**

```ruby 
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`されるとアサーションに成功します。

**成功例**

```ruby
assert_throws(:exit) do
  [1, 2, 3].each do |a|
    [4, 5, 6].each do |b|
      throw :exit
    end
  end
end
```

**失敗例**

```ruby
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メソッドを試してみて一番感じたのは
**適切に使い分けることで失敗の原因がより分かりやすくなる**
という点です。

例えば、以下のアサーションは結果はどちらも失敗です。
しかし、後者のほうが何が原因で失敗したのかを分かりやすく伝えてくれます。

```ruby
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