Rspec速度改善 #1

今回に５つのRspecの速度改善方法をご紹介したいと思います。

[1. Parallel Testing](#1-parallel-testing)
[2. Adding gem `test_prof`](#2-adding-gem-test_prof)
[3. Optimize Setup Time](#3-optimize-setup-time)
[4. Using `stubs` and `mock`](#4-try-using-build_stubbed-or-build)
[5. Using `trait` in Factory](#5-using-trait-in-factory)

---

# 1. Parallel Testing

## parallel_testsとは

- マルチコアCPUで並列実行するテスト用のGem

## parallel_testsを導入する方法

Gemの[ホームページ](https://github.com/grosser/parallel_tests)に参考してください。

## Note

- CPUのRamをよく食い尽くすので実行する間に他の作業を離してください。w

---

# 2. Adding gem test_prof

## 問題

```
# bad
it { is_expected.to be_success }
it { is_expected.to have_header("X-TOTAL-PAGES", 10) }
it { is_expected.to have_header("X-NEXT-PAGE", 2) }
its(:status) { is_expected.to eq(200) }

# good
it "returns the second page", :aggregate_failures do
  is_expected.to be_success
  is_expected.to have_header("X-TOTAL-PAGES", 10)
  is_expected.to have_header("X-NEXT-PAGE", 2)
  expect(subject.status).to eq(200)
end
```

* itを呼ぶと、もう一回subjectを呼ぶので、時間が遅くなっています<br>
→ itを一回だけ呼ぶようにした方が良いと思う

## 変更方法

- `test_prof`のGem導入: [ホームページ](https://github.com/palkan/test-prof)に参考してください
- Rubocop修正: [こちら](https://github.com/palkan/test-prof)に参考してください
- 以下のコマンドを実行したら、全部`#bad`の所を自動に`#good`になる

```
rubocop -r 'test_prof/rubocop' --only RSpec/AggregateFailures -a spec/
```

## Rspecを確認した結果

```
rspec spec/api/gmo/transaction_api_spec.rb

BEFORE:
Finished in 7.53 seconds (files took 7.74 seconds to load)

AFTER:
Finished in 2.08 seconds (files took 7.39 seconds to load)
```
- １ファイルだけ確認したが、早くなってきた！
- 皆さんもテスト書く時、注意してくださいね！

---

# 3. Optimize Setup Time

### BEFORE:

- テストのプロセスに変更されないデーターがあったら、`let`の代わりに`before(:all)`ブロックにインスタンス変数に移動した方が設定時間減らすできると思います。

```ruby
context "a" do
  ...
  let(:shop){create :shop}
  ...
end

context "b" do
  ...
  let(:shop){create :shop}
  ...
end

context "c" do
  ...
  let(:shop){create :shop}
  ...
end

# → テストケースによって`shop`を作る
```

### AFTER:

```ruby

before(:all){@shop = create :shop}

context "a" do
  # @shopを使える
end

context "b" do
  # @shopを使える
end

context "c" do
  # @shopを使える
end

# →　`shop`を一回だけ作る
```

---

# 4. Using `stubs` and `mock`

## `create`, `build`, `build_stubbed`の違い

- 多分他のTopicに説明が多いと思うですが、簡単に言うと

```
■ create: 対象のModelと関係あるModelがDBに作成される
■ build: 対象のModelではなく、関係あるModelしかがDBに作成されない
■ build_stubbed: 対象のModelも、関係あるModelも何もDBに作成されないが、
                 ちゃんと本物のModelとして使える
```

- `build_stubbed`が上の３つの中に一番早いなので、DBにあまり関係ないとか、Modelの関係が必要ないテストケースなどに使った方が速度は早くなります。
- できるだけ、この順番で使えばもっと早くなると思う<br>
`double → build_stubbed → build → create`
- build_stubbedの場合、DBに保存やアソシエーション先をDBに保存できませんので、ご注意ください。

## Using `allow`

```ruby:bad.rb

context "a" do
  before do
    form.available_from_kind_is_use = nil
    form.available_from_kind_backend = nil
    form.available_from_kind_frontend = nil
    form.save!
  end
end
```

```ruby:good.rb

context "a" do
  before do
    allow(form).to receive(:available_from_kind_is_use).and_return nil
    allow(form).to receive(:available_from_kind_backend).and_return nil
    allow(form).to receive(:available_from_kind_frontend).and_return nil
  end
end
```

- DBを変更必要はない場合、`allow`を使った方が良いです

---

# 5. Using `trait` in Factory

## BEFORE
```users.rb

FactoryGirl.define do
  factory :user do
    name "Hung"
    location build(:location)
  end
end
```

```user_spec.rb
it do
  user = build :user, name: "Hung"
  expect(user.name).to eq("Hung")
end
```

- 上のケースだと、`location`が必要ないけど、もう作られた

## AFTER
```ruby

FactoryGirl.define do
  factory :user do
    name "Hung"
    
    trait :with_location do
      location build(:location)
    end
  end
end
```

- `location`が必要なテストケースだけに`:with_location`使った方が良いと思います