先日、 表参道.rb に参加させていただき、LTをさせていただきました。
Matz さんがいるとは知らず、驚きました。
なんとそこでMatzさんから「本家 RSpec にPR出したら?」とコメントをいただき、PRの出し方のアドバイスまでいただきました( 煽り大事 とのこと)。
まだ本家にPRは出せていないのですが、意外と興味持ってくださる方が多いということがわかったので、まだまだ作って間もないライブラリですが、興味ある方に意見をいただきたいので、紹介記事を投稿することにしました。
RSpecZ とは
RSpecの問題点である、 長い 、 レビューつらい の解決を目指す RSpec の拡張ライブラリです。
現状では、RSpec の、 let
, context
, subject
などを拡張したメソッドなどを用意しています。
将来的には、Generatorや構文解析などをしてRSpecの書き方まで導くライブラリにしたいと思ってます。
簡単にRSpecの効果を画像で示すと、こうなります。
これは、私が以前開発していたプロジェクトの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
これは、冗長ですね。 context
と let
で同じことを言っています。
ただ、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のレポジトリを見ていただけると大変うれしいです。
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