Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Minitestの仕組みをソースコードで徹底解説!Rubyテストフレームワークの内部動作

Minitestとは?

MinitestRubyのテストライブラリで、TDD(テスト駆動開発)スタイルでコードのテストを書くためのツールです。

Railsのデフォルトのテストフレームワークであり、Railsの生みの親であるDHH(David Heinemeier Hansson)のお気に入りでもあります。

主な競合であるRSpecと比較して、シンプルでコード量が少ない点を評価してMinitestを選ぶ開発者も多くいます。

下の画像をご覧ください:

Minitestの仕組みをソースコードで徹底解説!Rubyテストフレームワークの内部動作

ただし、この記事の目的は「どちらを選ぶべきか」「どちらが優れているか」を語ることではありません。

この記事で扱うのは、Minitestが実際にどのように動作しているのかという内部の仕組みです。

もし迷っているなら:好きな方を使えばOKですが、両方のフレームワークに精通しておくことは大切です🙂

物事の仕組みを深く理解するのが好きな方なら、普段使っているテストライブラリが何であれ、きっとこの記事を楽しめるはずです。

フードの下を覗いてみよう

私自身を含め、多くの開発者が推奨している学習方法の一つがソースコードを読むことです。ソースコードを読むことは、システムがどのように動作しているのかを学ぶ絶好の機会であるだけでなく、これまで知らなかった新しいRubyのテクニックを吸収できる素晴らしい方法でもあります。

私は実際にMinitestのソースコードを読んでみたので、そこで得た知識をここで共有したいと思います。

まずは具体的なテストコードから見ていきましょう。これをもとに、Minitestが内部的に何をしているのかを考察します。

class Thingy < Minitest::Test
  def test_it_works
    assert_equal 1, 1
  end
end

では、Minitestはどのようにしてtest_it_worksのようなテストメソッドを見つけ出し、実行しているのでしょうか?

その答えこそ、メタプログラミングの「魔法」です:

def self.methods_matching(re)
  public_instance_methods(true).grep(re).map(&:to_s)
end

このコードは、lib/minitest.rb内で定義されているRunnableクラスから来ています。このコードは現在のクラスのすべてのインスタンスメソッドを取得し、正規表現に一致するものだけを抜き出します。

つまり、methods_matching(/^test_/)を呼び出せば、test_で始まるすべてのメソッド名を配列として受け取ることができるのです。

Minitestの動作の流れ

Minitestはこうしてtest_メソッドを見つけたら、次にそれらを呼び出します。

この処理はlib/minitest/test.rbファイル内で行われています。より正確には、runnable_methodsメソッドが担当しており、このメソッドはメソッドのリストをランダムな順序で返す点にも注目してください。

重要なポイント
この仕組みが成り立つのは、Minitest::TestRunnableのサブクラスだからです。

パズルの最後のピースとなるのが、Runnable上のrunクラスメソッドです。このメソッドは追加のフィルタリングを行った後、各メソッド名とレポーターオブジェクトを引数としてrun_one_methodを呼び出します。

該当するコードはこちら

filtered_methods.each do |method_name|
  run_one_method self, method_name, reporter
end

そして最終的に、Minitest::Testrunインスタンスメソッドが呼び出されます:

capture_exceptions do
  before_setup; setup; after_setup

  self.send self.name
end

sendはメタプログラミング用のメソッドで、文字列やシンボルを使ってオブジェクトの任意のメソッドを動的に呼び出すことができます。

capture_exceptionsブロックは、テストの失敗やコードから発生した例外を記録するために使用されています。

def capture_exceptions # :nodoc:
  yield
rescue *PASSTHROUGH_EXCEPTIONS
  raise
rescue Assertion => e
  self.failures << e
rescue Exception => e
  self.failures << UnexpectedError.new(e)
end

コードの読み方のコツ

私が好んで行うコードの読み方は、まず読んでいるコードの中から一つの側面や機能に焦点を絞り、玉ねぎの皮をむくように層を一つずつ剥がしていくことです。

yieldキーワードのように意味が分からないものが出てきたら、その都度調べましょう。

それ自体が学習プロセスの一部なのです!

まとめ

この記事では、Minitestがメタプログラミングを活用してテストメソッドを検出し、実行する仕組みを学びました。さらに、テスト中のエラーや例外がレポート表示用の配列にキャプチャされる過程についても理解できましたね。

このような「コード解析」系の記事はお好きですか?

ぜひコメント欄で教えてください🙂

また、お気に入りのSNSでのシェアもお忘れなく。スキルアップを目指す7000人以上のRuby開発者コミュニティにまだ参加されていない方は、下記のニュースレターを購読してみてください!

  1. Rubyでの静的分析入門!parser gemでメソッド定義を抽出する方法

    ソースコードを解析して、すべてのメソッドがどこで定義され、どんな引数を受け取るのかを把握したいと思ったことはありませんか? どうすれば実現できるのでしょうか? 最初に思いつくのは、正規表現(regexp)を書くことかもしれません。 しかし、もっと良い方法があるとしたらどうでしょう? 答えは「あります」! 静的解析(Static Analysis)とは、ソースコードそのものから情報を抽出するためのテクニックです。 これは、ソースコードをトークンへと変換する(パースする)ことで実現されます。 それでは早速見ていきましょう! parser gemを使う Rubyには標準ライブラリとしてRipper

  2. Excelで「データ分析」ツールを追加する方法|たった2ステップで完了

    データ分析(Data Analysis)は、Excelのアドインプログラムの一つで、複雑な統計分析や財務分析を簡単に行えるようにしてくれる便利な機能です。相関関係、フーリエ解析、移動平均、回帰分析など、さまざまな数学的操作を手軽に実行できます。ただし、このデータ分析ツールパック(Analysis ToolPak)はデフォルトではインストールされていません。アドインを有効化すると、リボンの「データ」タブから利用できるようになります。本記事では、Excelにデータ分析ツールパックを追加する具体的な手順をわかりやすく解説します。それでは早速始めましょう。データ分析ツールパックの基本概要データ分析ツー