Help us understand the problem. What is going on with this article?

rspecとminitestの比較 〜そうして僕は返事がない屍となった。〜

More than 3 years have passed since last update.

業務でrspecを使っているのですが、
rspecは車輪の再発明だと言われたり、
DHHはminitestがおすすめだったり、
minitestのほうが速いと言われていたりする記事を見て、
なんかminitestのほうが強そうやん」という思いを馳せ、
調べてみたのでご報告でございます。:tada:

基本的に、以下2つを参考にいたしました:rocket::waxing_gibbous_moon:
RSpecユーザのためのMinitestチュートリアル サンプル
RSpecとMinitest、使うならどっち?

expect(A).to eq Bassert_equal B, A

比較は以下になります。
rspecでexpect(A).to eq Bでしたが、
minitestではassert_equal B, Aのようになります。
この違いはrspecの記述がDSL(ドキュメント風)なのに対して、
minitestはピュアRubyであるという違いから生まれます。

[rspec]

spec/models/category_spec.rb
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]

test/models/category_test.rb
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倍速いことが分かります。
これだけを見るとかなり速いですね:eyes:

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のほうが遅いです。
(厳密に比較したい場合はお手元のコードでお試しください:bow:

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]

category_test.rb
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の使い方を整理するからの引用なので、ご参照ください。:moyai:🗿

[rspec]

category_spec.rb
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]

category_test.rb
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への印象(※注意:私にとってです:moyai:

「look good to me:thumbsup:」な点

  • 速いのめっちゃいい。(テストで10分待ちとかあるからそれが減るのはすごい幸せ:hugging:
  • ピュアrubyな記述いいかも。(rspecの書き方慣れるのに時間がかかったし、今もわからなくなる時ある:joy:)

「look bad to me:thumbsdown:」な点

  • minitestのメリットに速度があるみたいだが、extensions入れる度に遅くならないか確認するのか、どう測定するのかが疑問
  • minitestのextensionsの管理が面倒くさそう(衝突とか)
  • minitestでspec風に書けるけどピュアrubyで書けるメリット無くなりそう
  • minitestはextension使えばrspecぐらい多機能なこともできるけど、extensionの選定がダルそう

調べた結果、あくまで書き方の違いしか明確な違いはないので、minitestちゃんと使ってみないとメリットもデメリットも体感しづらいと思いました。:skull:
ただ、業務では、
参考資料が多いrspecのほうが、
メンバー全体のためにベターなのかなと思いました。

まとめ

rspecに慣れた自分にはまだminitestを掴みきれていません:joy:
rspecとminitestの処理速度比較をちゃんとしてみたいなと思いましたが、
テストパターンがrspecと変わるので純粋な速度比較をしづらいように感じた上に、
どのような要素を試すべきなのか考えあぐねております:skull::speech_balloon:

以上、rspecとminitest比較してみて返事が無いただの屍になった話でした。
ありがとうございました。

namitop
sight-visit
資格のオンライン予備校「資格スクエア」, 契約管理サービス「NINJA SIGN」を運営するスタートアップ
https://sight-visit.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away