- はじめに
- Minitest
- レッツ Minitest
— サンプルコード
— テストコード
—– サンプルコードとテストコード
—– テストコード (1)
—– テストコード (2)
—– テストコード (3)
—– テストコード (最終形態) - 以上
はじめに
この記事は, プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで の読書メモ的な何かです. この書籍はテストの自動化について章を割いているし, 各章でもテストを書いてからサンプルコードを実装していくという流れになっていて, 実際の開発の流れを体感出来るのでとても良い本だと思います. ということで, 今回は書籍で取り上げている Minitest を触りながら Ruby のテストの入り口を覗いてみたいと思います.
尚, 本記事では特に断りが無い限りで, 以下のバージョン Ruby を利用して検証を行っています.
$ ruby --version ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux]
それでは, レッツテスト.
Minitest
Minitest とは, Ruby に始めから導入されているテストフレームワークで, 特にセットアップの必要なく利用できます.
require 'minitest/autorun' class FooTest < Minitest::Test def test_foo assert_equal 'foo', 'foo' end end
実行すると, 以下のように.
$ ruby foo_test.rb Run options: --seed 33722 # Running: . Finished in 0.001728s, 578.6997 runs/s, 578.6997 assertions/s. 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
ちなみに, Ruby にテストフレームワークについて, 以下の記事がとても面白かったです.
Rspec くらいしか知らなかったけど, Rspec 以外にも色んなテストフレームワークがあったんだなと. Ruby に歴史あり, テストフレームワークに歴史あり.
レッツ Minitest
サンプルコード
プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで 第七章「クラスの作成を理解する」の「7.7.7 protected メソッド」に掲載されている体重比較クラス.
class User attr_reader :name def initialize(name, weight) @name = name @weight = weight end def heavier_than?(other_user) other_user.weight < @weight end protected def weight @weight end end
以下のように, 利用します.
[2] pry(main)> foo = User.new('foo', 50) => #<User:0x000056280c7c0a58 @name="foo", @weight=50> [3] pry(main)> bar = User.new('bar', 60) => #<User:0x000056280c781970 @name="bar", @weight=60> [4] pry(main)> foo.heavier_than?(bar) => false [5] pry(main)> bar.heavier_than?(foo) => true
protected メソッドは同じクラスやサブクラス内から, レシーバ付きで呼び出すことが出来るというのが private メソッドと異なる点. public と private と比べて利用頻度が少なそうなんだけど, protected メソッドの使いドコロが具体的なコードで説明されていて参考になりました.
テストコード
サンプルコードとテストコード
ところで, プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで では, 各章のサンプルコードには殆どと言っていいほどテストコードが付いているので, この体重比較クラスについて, 以下のようなテストコードを考えてみました.
テストコード (1)
まず, User#heavier_than? をテストするだけのコード.
require 'minitest/autorun' require './weight' class WeightTest < Minitest::Test def setup @foo = User.new('Foo', 50) @bar = User.new('Bar', 60) end def test_foo_heavier_than_bar? assert_equal @foo.heavier_than?(@bar), false end def test_bar_heavier_than_foo? assert_equal @bar.heavier_than?(@foo), true end end
実行してみます.
$ bundle exec ruby weight1_test.rb Run options: --seed 41780 # Running: .. Finished in 0.000681s, 2936.4863 runs/s, 2936.4863 assertions/s. 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
いい感じ.
テストコード (2)
protected メソッドが外部から呼べないことを確認してみます.
require 'minitest/autorun' require "minitest/reporters" class WeightTest < Minitest::Test def setup @foo = User.new('Foo', 50) @bar = User.new('Bar', 60) end ... 略 ... def test_call_foo_weight assert_respond_to(@foo, :weight) end def test_call_bar_weight assert_respond_to(@bar, :weight) end end
MiniTest::Assertions#assert_respond_to を使うことで, 指定したオブジェクトでメソッドが呼び出し可能かをテストすることが出来ます.
$ bundle exec ruby weight_test.rb Run options: --seed 721 # Running: FF Finished in 0.000811s, 2466.9188 runs/s, 2466.9188 assertions/s. 1) Failure: WeightTest#test_call_bar_weight [weight_test.rb:27]: Expected #<User:0x000055794dafccd0 @name="Bar", @weight=60> (User) to respond to #weight. 2) Failure: WeightTest#test_call_foo_weight [weight_test.rb:23]: Expected #<User:0x000055794daf2ca8 @name="Foo", @weight=50> (User) to respond to #weight. 2 runs, 2 assertions, 2 failures, 0 errors, 0 skips
見事にコケたので, 呼び出すことは出来ないことが確認出来ました.
テストコード (3)
protected メソッドである User#weight のテストも追加してみます.
require 'minitest/autorun' require "minitest/reporters" class WeightTest < Minitest::Test def setup @foo = User.new('Foo', 50) @bar = User.new('Bar', 60) end ... 略 ... def test_foo_weight assert_equal @foo.send(:weight), 50 end def test_bar_weight assert_equal @bar.send(:weight), 60 end end
private メソッドや protected メソッドのテストは, 以下のように Object#send メソッドを利用して呼び出すようにしました.
$ bundle exec ruby weight2_test.rb Run options: --seed 53260 # Running: .... Finished in 0.000997s, 4013.8320 runs/s, 4013.8320 assertions/s. 4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
いい感じ.
テストコード (最終形態)
ちょっとテスト結果の出力が寂しいので minitest-reporters を利用して, テストの結果を少しだけ賑やかにしてみたいと思います.
github.com
minitest-reporters の詳細については割愛しますが, 以下のようなクラスを指定することで, 指定したフォーマットで出力させることが出来るようです.
Minitest::Reporters::DefaultReporter # => Redgreen-capable version of standard Minitest reporter Minitest::Reporters::SpecReporter # => Turn-like output that reads like a spec Minitest::Reporters::ProgressReporter # => Fuubar-like output with a progress bar Minitest::Reporters::RubyMateReporter # => Simple reporter designed for RubyMate Minitest::Reporters::RubyMineReporter # => Reporter designed for RubyMine IDE and TeamCity CI server Minitest::Reporters::JUnitReporter # => JUnit test reporter designed for JetBrains TeamCity Minitest::Reporters::MeanTimeReporter # => Produces a report summary showing the slowest running tests Minitest::Reporters::HtmlReporter # => Generates an HTML report of the test results
今回は, Minitest::Reporters::SpecReporter
を使って, 以下のように書きました.
require 'minitest/autorun' require "minitest/reporters" Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new] require './weight' class WeightTest < Minitest::Test def setup @foo = User.new('Foo', 50) @bar = User.new('Bar', 60) end def test_foo_heavier_than_bar assert_equal @foo.heavier_than?(@bar), false end def test_bar_heavier_than_foo assert_equal @bar.heavier_than?(@foo), true end def test_foo_weight assert_equal @foo.send(:weight), 50 end def test_bar_weight assert_equal @bar.send(:weight), 60 end end
実行すると, 以下のように出力されました.
$ bundle exec ruby weight_test.rb Started with run options --seed 56419 WeightTest test_bar_heavier_than_foo PASS (0.00s) WeightTest test_foo_heavier_than_bar PASS (0.00s) WeightTest test_foo_weight PASS (0.00s) WeightTest test_bar_weight PASS (0.00s) Finished in 0.00083s 4 tests, 4 assertions, 0 failures, 0 errors, 0 skips
ちなみに, minitest-reporters を利用しない場合には…
$ bundle exec ruby weight_test.rb Run options: --seed 10588 # Running: .... Finished in 0.001064s, 3758.5227 runs/s, 3758.5227 assertions/s. 4 runs, 4 assertions, 0 failures, 0 errors, 0 skips
ま, これでも悪くないですが.
以上
Minitest をちょっと触ってみました. プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法までのおかげでだいぶん Ruby のテストに対する心の壁が取り払われたような気がします.
有難う御座いました.