書きやすく、レビューしやすいRSpecのためのRSpec拡張ライブラリ(Gem) RSpecZ の紹介

先日、 [表参道.rb](https://omotesandorb.connpass.com/) に参加させていただき、LTをさせていただきました。

[Matz さん](https://twitter.com/yukihiro_matz)がいるとは知らず、驚きました。

なんとそこでMatzさんから「本家 RSpec にPR出したら？」とコメントをいただき、PRの出し方のアドバイスまでいただきました（ **煽り大事** とのこと）。

まだ本家にPRは出せていないのですが、意外と興味持ってくださる方が多いということがわかったので、まだまだ作って間もないライブラリですが、興味ある方に意見をいただきたいので、紹介記事を投稿することにしました。

# RSpecZ とは

RSpecの問題点である、 **長い** 、 **レビューつらい** の解決を目指す RSpec の拡張ライブラリです。

https://github.com/RSpecZ/RSpecZ

現状では、RSpec の、 `let` , `context` , `subject` などを拡張したメソッドなどを用意しています。
将来的には、Generatorや構文解析などをしてRSpecの書き方まで導くライブラリにしたいと思ってます。


簡単にRSpecの効果を画像で示すと、こうなります。

![スクリーンショット 2018-07-06 00.48.59.png](https://qiita-image-store.s3.amazonaws.com/0/67424/d672ce2f-a0cb-f0d0-2700-b20e8b8ed5fd.png)


これは、私が以前開発していたプロジェクトのRSpecに対して、RSpecZを適用したときのものです。

なぜこんなことが起こるか、RSpecZの機能を紹介していきます。

# 基本的な機能
## contextの拡張
RSpecの `context` は自由につかえて非常に汎用性が高いメソッドです。
ただ、汎用性が高いがゆえに、わかりにくい、かつ冗長になりやすいです。

例えばこんな `context` があります。

```.rb
context 'When phone_number is 090-1234-5678' do
  let(:phone_number) { '090-1234-5678' }
  it { expect(phone.save).to be_truthy }
end
```

これは、冗長ですね。 `context` と `let` で同じことを言っています。

ただ、RSpecではこのようなケースが起こりがちです。

また、RSpecの問題点の一つとして、 `context` は、このメソッド自体には `意味` を持たないということがあります。
`context` の引数の文字列がそのコンテキストの状態を表していて、それを見なければどんなテストを行っているのかわかりません。

例えば上記コードでは、 `phone_number` に電話番号として正しい値を入れてます。
RSpecZ では、このような場合に使えるよりわかりやすいメソッドを用意しています。

```.rb
set_valid(:phone_number, '090-1234-5678') do
  it { expect(phone.save).to be_truthy }
end
```

`set_valid` は、簡単に言うと、context と letを合わせたメソッドです。
また、有効な値を入れる、ということをメソッド名で示すことができるメソッドになっています。

レビューを行う人は、 `set_valid` ということは有効な値を入れている、ということがメソッド名を見た時点でわかり、かつ行数が減って読みやすくなります。

また、複数の値を同時に検証したいということもよくあります。 RSpecZ は、そのために `set_valids` を用意しています。

```.rb
set_valids(:phone_number, '090-1234-5678', '080-9566-8466', '070-4567-8901', '09045678945' ) do
  it { expect(phone.save).to be_truthy }
end
```

このコードは、RSpecで書くと下記のようになります。

```rb
%w[090-1234-5678 080-9566-8466 070-4567-8901 09045678945].each do |phone_number|
  context "When phone_number is #{phone_number}" do
    let(:phone_number) { phone_number }
    it { expect(phone.save).to be_truthy }
  end
end
```

この２つのコードを見比べると、レビューのしやすさが格段に上がっていることがわかると思います。

RSpecZ では、 `set_valid` だけでなく、 `set_invalid` という不正な値を入れる場合のメソッドも用意しており、
ケースごとに使い分けることでレビューのしやすさを改善しています。

## let の拡張
RSpecでは、 `context` で簡単に入力や引数を設定するために、下記のような書き方をすることがあります。

```.rb
let(:name) { 'test-name' }
let(:phone_number) { '090-1234-5678' }
let(:address) { 'test-address' }
let(:params) { {
  name: name,
  phone_number: phone_number,
  address: address
} }

subject { get '/api/test', params: params }
```

こうすることで、 `let` の内容を一つ変えることで、デフォルト値から一つだけ値が違うものをテストしたりできて、柔軟なテストが書けます。
ただ、これも結構冗長で、そういう `let` を使うのがわかりきっているなら、これを省略した形にできます。

このためのメソッドが、 `create_params` メソッドです。 `create_params` メソッドを使うと、上記のコードはこう書けます。

```.rb
let(:name) { 'test-name' }
let(:phone_number) { '090-1234-5678' }
let(:address) { 'test-address' }
create_params :name, :phone_number, :address

subject { get '/api/test', params: params }
```

`create_params` は、すでに定義されている `let` を利用して、 `params` という `let` を定義するメソッドです。


実は、 `create_params` には、定義されていなければ、自動的に 'test-xxx' という文字列を作成した `let` を作ってくれるという機能があるので、こうも書けます。

```.rb
let(:phone_number) { '090-1234-5678' }
create_params :name, :phone_number, :address

subject { get '/api/test', params: params }
```

このように、RSpecZでは、よく使う `let` のパターンを簡単にするメソッドをこれからも増やしていきたいと思っています。

## その他の機能
RSpecZでは、上記で説明したメソッド以外にもメソッドを用意しています。
興味を持っていただいた方は、RSpecZのレポジトリを見ていただけると大変うれしいです。

https://github.com/RSpecZ/RSpecZ

# RSpecZの効果測定

この記事を書くにあたって、RSpecZにどの程度の効果があるのか、過去の社内プロジェクトと比較して効果を測定してみようと思い、データを取得してみました。

## 前提
- 完全に別のプロジェクトなので、ビジネスロジックなどが全く異なる
- コードのベースを作ったのは、どちらも私。（どちらも基本的には同じような書き方で書いているプロジェクト）
- RSpecZを利用しているプロジェクトは、作り始めて３ヶ月しか経っていないので、コード量が少ない
- 現時点でRSpecZに入っていない便利機能なども使っているため、短くなっているのは現時点のRSpecZだけの力ではない

## 結果

||　RSpecの行数 | テスト数 |
|:--:|:--:|:--:|:--:|
| 新プロジェクト(RSpecZ利用) | 2183 | 1223 |
| 旧プロジェクト(RSpecZ未利用) | 20023 | 5143 |

RSpecの行数: specファイルの中から、 request_spec, supportファイルの総行数(弊社では、基本的に request_spec しか書かないので)
テスト数: example数

## 結果について

結果から、１テストあたりの行数が **半分** になることがわかりました。
（注：前提のところで書いたように、色々な条件が異なるため、実際に数値だけで比較することはできません）

正確ではないとはいえ、コード量が半分になるっていうのはかなりの効率化だと思います。

ここからさらに **レビューしやすいRSpec** を目指してこれからさらに効率化できる仕組みを作っていきたいと思います。

# 終わりに
今回はRSpecZの機能を紹介をしました。

RSpecZはまだ開発を始めて数ヶ月です。
コミッターも私ともう一人くらいしかいません。

RSpecをより良いものにするための、RSpecZの方針に共感していただける方がいれば、
コメントやTwitterなどでご意見、ご要望を送っていただけると大変うれしいです！
(もちろんコミッターになっていただける方も絶賛募集中です！）




この活動をしている会社(playground)はこんなところ↓
https://www.wantedly.com/companies/playground