業務でrspecを使っているのですが、
rspecは車輪の再発明だと言われたり、
DHHはminitestがおすすめだったり、
minitestのほうが速いと言われていたりする記事を見て、
「なんかminitestのほうが強そうやん」という思いを馳せ、
調べてみたのでご報告でございます。
基本的に、以下2つを参考にいたしました
・RSpecユーザのためのMinitestチュートリアル サンプル
・RSpecとMinitest、使うならどっち?
expect(A).to eq B
はassert_equal B, A
比較は以下になります。
rspecでexpect(A).to eq B
でしたが、
minitestではassert_equal B, A
のようになります。
この違いはrspecの記述がDSL(ドキュメント風)なのに対して、
minitestはピュアRubyであるという違いから生まれます。
[rspec]
RSpec.describe Category, type: :model do
example "updated_at降順で並び替える" do
Category.create(id: 1, name: "category 1", updated_at: Time.current + 1.days)
Category.create(id: 2, name: "category 2", updated_at: Time.current + 2.days)
expect(Category.order("updated_at DESC").map(&:id)).to eq [2,1]
end
end
[minitest]
class CategoryTest < ActiveSupport::TestCase
test "updated_at降順で並び替える" do
Category.create(id: 1, name: "category 1", updated_at: Time.current + 1.days)
Category.categories.create(id: 2, name: "category 2", updated_at: Time.current + 2.days)
assert_equal [2, 1], Category.order("updated_at DESC").map(&:id)
end
end
テストデータの生成はrspecもminitestも変わりません。
処理速度比較
「expect(A).to eq Bはassert_equal B, A」を例に、
テストの処理速度は以下のようになりました。
minitestの方が約5.5倍速いことが分かります。
これだけを見るとかなり速いですね
rspec | minitest |
---|---|
Finished in 0.33855 seconds (files took 4.03 seconds to load) |
Finished in 0.063430s, 15.7654 runs/s, 15.7654 assertions/s. |
しかし、このスライドの速度比較ではminitestのほうが遅いです。
(厳密に比較したい場合はお手元のコードでお試しください)
minitest形式の記述だとdescribe,contextがない。
minispec-metadataを使えばdescribe,contextのようなことができるのですが、
デフォルトだとないみたいです。
テストコードがごちゃごちゃしそうですが、
minispec-metadataを入れるか、
コメントとかで説明するか、
またspec形式でもminitestは書けるみたいなので、複雑さによって使い分けるか、
それぐらいしか今のところ思いついていません。
[参考]xUnit形式とspec形式のminitestの書き方。
xUnit形式のminitest
spec形式のminitest
[xUnit形式minitest]
class CategoryTest < ActiveSupport::TestCase
test "updated_at降順で並び替える" do
Category.create(id: 1, name: "category 1", updated_at: Time.current + 1.days)
Category.categories.create(id: 2, name: "category 2", updated_at: Time.current + 2.days)
assert_equal [2, 1], Category.order("updated_at DESC").map(&:id)
end
end
[spec形式minitest]
require "test_helper"
require "minitest/autorun"
describe Category do
before do
theme = Theme.create(name: "theme 1")
theme.categories.create(id: 1, name: "category 1", updated_at: Time.current + 1.days)
theme.categories.create(id: 2, name: "category 2", updated_at: Time.current + 2.days)
end
describe "order" do
it "updated_at降順" do
Category.order("updated_at DESC").map(&:id)
end
end
end
大きめのrspecのコードをminitestに変換してみる
order_by_displayorder_and_updatedat
を例に書いていきます。
この並び替えメソッドの処理は、
⓪更新日順の並び替えよりも、表示順の並び替えを優先する。
①表示順(display_order
)を昇順に。
②更新日順(updated_at
)を降順に。
という並び替えを行います。
例えば、以下のような"元データ"を差し込んだ場合、
"並び替え後のデータ"は以下のようになります。
[元データ]
名前 | 表示順(display_order) | 更新日(updated_at) |
---|---|---|
カテゴリ1 | 2 | 2017/05/09 13:04 |
カテゴリ2 | 1 | 2017/05/09 13:05 |
カテゴリ3 | 1 | 2017/05/09 13:06 |
カテゴリ4 | 2017/05/09 13:07 | |
カテゴリ5 | 2017/05/09 13:08 |
[並び替え後のデータ]
名前 | 表示順(display_order) | 更新日(updated_at) |
---|---|---|
カテゴリ3 | 1 | 2017/05/09 13:06 |
カテゴリ2 | 1 | 2017/05/09 13:05 |
カテゴリ1 | 2 | 2017/05/09 13:04 |
カテゴリ5 | 2017/05/09 13:08 | |
カテゴリ4 | 2017/05/09 13:07 |
このrspecは以下のように書きました。
このrspecは[基礎]アンチパターンからrspecのdescribe,context,exampleの使い方を整理するからの引用なので、ご参照ください。🗿
[rspec]
require 'rails_helper'
RSpec.describe Category, type: :model do
describe "order_by_displayorder_and_updatedat" do
context "元データにdisplay_orderの違いがある場合" do
before(:each) do
create(:category, id: 1, name: "category 1", display_order: 2)
create(:category, id: 2, name: "category 2", display_order: 1)
end
example "display_orderを昇順に並び替える" do
expect(Category.order_by_displayorder_and_updatedat.map(&:id)).to eq [2, 1]
end
end
context "元データにupdated_atの違いがある場合" do
before(:each) do
create(:category, id: 1, name: "category 1")
create(:category, id: 2, name: "category 2")
end
example "updated_atを降順に並び替える" do
expect(Category.order_by_displayorder_and_updatedat.map(&:id)).to eq [1, 2]
end
end
context "元データにdisplay_order, updated_atの違いがある場合" do # ちょっと日本語がおかしいのはご容赦ください:bow:
before(:each) do
create(:category, id: 1, name: "category 1", display_order: 1)
create(:category, id: 2, name: "category 2")
end
example "display_orderを優先して並び替える" do
expect(Category.order_by_displayorder_and_updatedat.map(&:id)).to eq [1, 2]
end
end
end
end
rspecからフラットなxUnit形式minitestっぽく変換してみました。
describe的なのはコメントアウトで書きました。contextは消しました。
[minitest]
require "test_helper"
class CategoryTest < ActiveSupport::TestCase
# order_by_displayorder_and_updatedatメソッドのテスト
test "display_orderを昇順に並び替える" do
Category.create(id: 1, name: "category 1", display_order: 2)
Category.create(id: 2, name: "category 2", display_order: 1)
assert_equal [2, 1], Category.order("updated_at DESC").map(&:id)
end
test "updated_atを降順に並び替える" do
theme = Theme.create(name: "theme 2")
Category.create(id: 1, name: "category 1", updated_at: Time.current + 1.days)
Category.create(id: 2, name: "category 2", updated_at: Time.current + 2.days)
assert_equal [1, 2], Category.order("updated_at DESC").map(&:id)
end
test "display_orderを優先して並び替える" do
theme = Theme.create(name: "theme 3")
Category.create(id: 1, name: "category 1", display_order: 1, updated_at: Time.current + 1.days)
Category.create(id: 2, name: "category 2", updated_at: Time.current + 2.days)
assert_equal [1, 2], Category.order("updated_at DESC").map(&:id)
end
end
xUnix形式minitestでも構造化して書く方法もありますが、
「フラットでええやん」思想もあるみたいです。
RSpecとMinitest、使うならどっち?「テストを構造化する例(スライド 22~25)」
minitestへの印象(※注意:私にとってです)
「look good to me」な点
- 速いのめっちゃいい。(テストで10分待ちとかあるからそれが減るのはすごい幸せ)
- ピュアrubyな記述いいかも。(rspecの書き方慣れるのに時間がかかったし、今もわからなくなる時ある)
「look bad to me」な点
- minitestのメリットに速度があるみたいだが、extensions入れる度に遅くならないか確認するのか、どう測定するのかが疑問
- minitestのextensionsの管理が面倒くさそう(衝突とか)
- minitestでspec風に書けるけどピュアrubyで書けるメリット無くなりそう
- minitestはextension使えばrspecぐらい多機能なこともできるけど、extensionの選定がダルそう
調べた結果、あくまで書き方の違いしか明確な違いはないので、minitestちゃんと使ってみないとメリットもデメリットも体感しづらいと思いました。
ただ、業務では、
参考資料が多いrspecのほうが、
メンバー全体のためにベターなのかなと思いました。
まとめ
rspecに慣れた自分にはまだminitestを掴みきれていません
rspecとminitestの処理速度比較をちゃんとしてみたいなと思いましたが、
テストパターンがrspecと変わるので純粋な速度比較をしづらいように感じた上に、
どのような要素を試すべきなのか考えあぐねております
以上、rspecとminitest比較してみて返事が無いただの屍になった話でした。
ありがとうございました。