Ruby
Rails
RSpec
Gem
RSpecZ

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

先日、 表参道.rb に参加させていただき、LTをさせていただきました。

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

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

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

基本的な機能

contextの拡張

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

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

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

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

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

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

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

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 を用意しています。

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

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

%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

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

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

let の拡張

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

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 メソッドを使うと、上記のコードはこう書けます。

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 を作ってくれるという機能があるので、こうも書けます。

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を利用しているプロジェクトは、作り始めて3ヶ月しか経っていないので、コード量が少ない
  • 現時点でRSpecZに入っていない便利機能なども使っているため、短くなっているのは現時点のRSpecZだけの力ではない

結果

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

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

結果について

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

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

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

終わりに

今回はRSpecZの機能を紹介をしました。

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

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

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