この記事の続き。
前回の疑問
assert_not_equal
などはエイリアスとして設定されてるっぽい。なんでassert_not
はエイリアスじゃだめなんだろう。
コードを見比べる
Minitest の refute
def refute test, msg = nil msg ||= message { "Expected #{mu_pp(test)} to not be truthy" } assert !test, msg end
minitest/assertions.rb at master · seattlerb/minitest · GitHub
ActiveSupport の assert_not
def assert_not(object, message = nil) message ||= "Expected #{mu_pp(object)} to be nil or false" assert !object, message end
rails/assertions.rb at 77932446895bd70f38a3aaa1ab288bf8a7b7142c · rails/rails · GitHub
refute
と比べると、エラーメッセージの文言以外は同じようだ。
ActiveSupport の assert_not_equal
など
refute_equal
などのエイリアス(別名)になっている。
module ActiveSupport class TestCase < ::Minitest::Test Assertion = Minitest::Assertion # 中略 # test/unit backwards compatibility methods alias :assert_raise :assert_raises alias :assert_not_empty :refute_empty alias :assert_not_equal :refute_equal alias :assert_not_in_delta :refute_in_delta alias :assert_not_in_epsilon :refute_in_epsilon alias :assert_not_includes :refute_includes alias :assert_not_instance_of :refute_instance_of alias :assert_not_kind_of :refute_kind_of alias :assert_no_match :refute_match alias :assert_not_nil :refute_nil alias :assert_not_operator :refute_operator alias :assert_not_predicate :refute_predicate alias :assert_not_respond_to :refute_respond_to alias :assert_not_same :refute_same # 略 end end
rails/test_case.rb at 77932446895bd70f38a3aaa1ab288bf8a7b7142c · rails/rails · GitHub
alias new_method old_method
で、old_method
に new_method
という別名をつけられる。
alias :assert_not_equal :refute_equal
というコードによって、assert_not_equal
で refute_equal
が実行される。
なぜ assert_not
はエイリアスじゃないのか?
refute
と assert_not
のコードを見比べたところ、違いはエラーメッセージだ。エラーメッセージを変えたかったから、エイリアスではなく新しいメソッドとして定義したのだろうか。
ここまでは前回の復習。
こういうことをつらつらと書いていたところ、フィヨルドブートキャンプのメンターでチェリー本の著者でもある伊藤淳一さんにヒントをいただいた。
Railsがminitestをいろいろ独自拡張してるせいで、「あれ?」って思うことが結構ありますよね〜😅
— Junichi Ito (伊藤淳一) (@jnchito) 2020年8月6日
ちなみにEveryday Railsを買うと付いてくるこの本に「Minitestオリジナルのメソッド」と「Railsの独自拡張」の情報をまとめています。(少し情報が古いかもしれませんが)https://t.co/SmtzTIujxk pic.twitter.com/9sMJpEovwD
この後「assert_not
をわざわざ定義しているのは、エラーメッセージを変えたかったから?」という質問をしたところ、GitHub を見ればわかるかもしれないとアドバイスいただいた(本当はもっと詳しい説明とやり方まで動画で説明いただきました!ありがとうございます。フィヨルドブートキャンプ生なら見られます)。
あと RubyMine のコードジャンプ便利そう。ただ就職したときにどうなるのかが不安…。まずは VSCode でどのくらいできるか調べてみよう。
assert_not
が定義された経緯を調査
コードをもう一度よく見たら、上に何やらコメントがたくさん。英語でしかもコメントだと、まるでそこに存在していないかのように無視してしまうことがよくある…。これ先に気づきたかった。
# Asserts that an expression is not truthy. Passes if <tt>object</tt> is # +nil+ or +false+. "Truthy" means "considered true in a conditional" # like <tt>if foo</tt>. # # assert_not nil # => true # assert_not false # => true # assert_not 'foo' # => Expected "foo" to be nil or false # # An error message can be specified. # # assert_not foo, 'foo should be false' def assert_not(object, message = nil) message ||= "Expected #{mu_pp(object)} to be nil or false" assert !object, message end
親切に例示まである。← これが RDoc というやつ?
英語は一生懸命勉強中だが、とりあえず上の3行を DeepL 翻訳にかけてみてから、自分でも考える。
Asserts that an expression is not truthy. Passes if object is nil or false. "Truthy" means "considered true in a conditional" like if foo.
表現が truthy ではないことを主張します。オブジェクトが nil か false の場合にパスするのです。"truthy" は if foo のように "条件付きで true とみなされる" ことを意味します。
という感じ?「エラーメッセージを変えたかったから、エイリアスではなく新しいメソッドとして定義した」という仮説は正しそうだ。
もう一つ、GitHub から履歴を見る方法を教えてもらった。
Blame
をクリックすると、コードの変更履歴を見ることができる。assert_not
の部分は8年前に変更されていることがわかった。
いくつかコメントがついていた。頑張って訳してみる。(Special Thanks: DeepL)
Introduce assert_not to replace 'assert !foo' · rails/rails@f75addd · GitHub
'assert !foo' の代わりに assert_not を導入
なぜ Minitest の refute
を再実装するのですか?
美しさと後方互換性のために、従来の assert_* API
を維持しています。これでニッチな部分を埋めることができます(このメソッドは異なるメッセージと戻り値を使うので、エイリアスはつけていません)。
説明ありがとうございます。後方互換とはどういうことですか? Rails master は >>1.9.3 ではないのですか?
super
を呼び出すことで、メッセージをカスタマイズできます。
def refute obj, message = nil message ||= "Expected #{mu_pp(object)} to be nil or false" !super(obj, message) end alias :assert_not :refute
(戻り値を考慮して編集)
考え中です。それは#refute
を再利用していますが、その戻り値を変更しています。そしてassert_not用のRDocは、オリジナルのメソッドではなくエイリアスとして表示されます。
refuteを変更してassert_notをエイリアス化するよりも、assert_notがrefuteを呼び出すようにした方が良いと思います。しかし、そうするとコードは次のようになります。
!refute(object, message)
vs
assert !object, message
後者(オブジェクトが真でないと主張する)の方が、前者(オブジェクトが真であることを否定しない)よりも明確だと考えられます。
(訳などおかしかったら教えてください)
まとめ
(8月12日追記:相当勘違いしていたのでまた書き直します)
エラーメッセージの表現が適切ではないと考えられたため、エイリアスではなくassert_not
というメソッドを別に定義したことがわかった。
refute
を上書きしてエイリアスにすることも検討されたが、戻り値を変更するのにドキュメント上はエイリアスとして表示されるのは不適切であることから、別のメソッドとして定義した方がよいと考えられて今の形になっている。
ということは、refute
を使ったときは Minitest のエラーメッセージが表示されるのか??という新たな疑問が。(続く)