RSpec の Request spec をチームで改善していった話

RSpec の Request spec をチームで改善していった話|ANDPAD Advent Calendar 2022

この記事は ANDPAD Advent Calendar 2022 の 7日目の記事です。

qiita.com

こんにちは、ydahです。
先日のRubyWorld Conference 2022で燗酒の美味しさに感動していたのですが、ふと気がついたら島根の日本酒がたくさん我が家にいました。

気が付くと何故か我が家にいらっしゃった方々(不思議だ...)

そして、また気がつくと枡や徳利、平盃も我が家にいらした(不思議ですね...)のでこれから寒くなるので、燗酒を飲んで温まっていこうと思います。

はじめに

本記事では私が所属しているANDPAD検査チームで取り組んだ以下のことについて紹介いたします。

  • Request specの改善についてやったこと
  • チームとして改善タスクやリファクタリングを推進するための仕組み作り

Request specの改善について

長年、様々な人によって書かれてきていたテストコードであったので、より良く構造化された状態にするということも目的としていましたが、仕様を学び直すというのも目的としていました。
テストコードの構造を見直していく過程で、どのような既存仕様が存在しているかをキャッチアップ出来ることも、これらの改善のモチベーションとなっていました。

rspec-request_describer を活用する

Request specをよりシンプルに記述できるように、r7kamuraさん作の rspec-request_describer が導入されていました。

github.com

シンプルに記述できる例を挙げると、describeメソッドを利用して記述した文章を元にHTTPリクエストを送ってくれるというものがあります。
例えば以下の例だと subject 内で /users に対してGETリクエストを発行してくれます。

RSpec.describe 'GET /users' do
  it 'returns 200' do
    subject
    expect(response).to have_http_status(200)
  end
end

他にもリクエストヘッダやリクエストパラメータをシンプルに記述することができます。
ですが、このGemは導入されていたのですが、既存のテストコードに反映するというところまではできていないという状態でした。

そのため、rspec-request_describerを活用する方式に変更をしました。

エンドポイントの単位でファイルを分割する

コントローラー単位でRequest specが書かれており、ファイル名も spec/requests/**/*_controller_spec.rb という命名規則になっていました。 これにより、様々な問題をはらんでいました。

  • 不必要に長くなってしまっており読みづらさがあった。
  • アクションごとにdescribeが切られることになり、ネストが深くなりがちだった。
  • 各アクションで共通して必要となるデータの準備がファイルの先頭にずらっと並んでいた。
    • また、それらを必要に応じて書きかえる方式になっており読みづらさとメンテナビリティが損なわれていた。

これらの問題によって、機能の変更や修正を加えた時にテストを読み解くのに時間がかかり、 とある機能を変更しただけで、テストの構造まで見直さないと意図したテストが出来ないようになってしまっていました。

この問題を解消するべく、アクション毎にファイルを分割して spec/requests/**/*_controller_action_name_spec.rb となるようにしました。
そして、それと並行して読みづらさを改善するために以下を実施しました。

RuboCopのTODO設定として無視されていた違反に対処する

リポジトリにはRuboCop RSpecが導入されていました。

github.com

これによって RSpec についての cop が有効になっており、 Linting や Formatting ができる状態になっていましたが、それらの多くは導入時に.rubocop_todo.ymlにて TODO として違反が挙がらないように抑制されてしまっていました。

これらの抑制の設定はファイル名を元に除外するようにしています。
なので、アクション毎の形式にファイル名を切り替えるタイミングで抑制の設定が外れることになります。
つまりそのタイミングで RuboCop の抑制されていたルールが有効になり、違反が上がるようになるのでRuboCopの指摘に対処するようにしました。

違反が上がっているのを解消していく中で、良かったと感じた cop として紹介させていただきたいのは RSpec/LetSetup です。
このcopを有効にするとlet!にしなくても良いものを検出してくれるようになります。

テストの前提となるオブジェクト、レコードを生成する場合、let, let!before を使うと思うのですが、 生成後に参照されていない、つまりbefore で書き換えられる let! が分かるようになります。

そのため、生成後に参照する/しないによる before / let, let! の意識的な使い分けをすることができます。

Example:

# bad
let!(:my_widget) { create(:widget) }

it 'counts widgets' do
  expect(Widget.count).to eq(1)
end

# good
it 'counts widgets' do
  create(:widget)
  expect(Widget.count).to eq(1)
end

# good
before { create(:widget) }

it 'counts widgets' do
  expect(Widget.count).to eq(1)
end

改善活動を継続する仕組みについて

進めるにあたって考えたことは、継続して改善を出来る仕組みを整えるということでした。
リファクタリングだけをするという訳には勿論いかず、日々のプロダクト開発をやっていく必要があります。
そのため、改善を継続するための仕組みや方針を考えて推進するようにしました。

チームとして進められるようにする

ANDPAD検査チームではスプリントの期間を1週間としたスクラムを導入しています。
そこで、週に実施するタスクの内、20%は改善タスクをしようという取り決めをしています。

気づいた時にリファクタリングしたいことをタスク化しておき、プロダクトバックログに積んでおくようにしています。
そして、スプリントプランニング時にスプリントのタスクとして取っていくようにしています。

また、時折祝日が平日にいくつかある週など、スプリントを中止する週があると思います。
その週には改善タスク消化デーとして、プロダクトバックログから短時間で解消可能な改善、リファクタリングタスクをやっていく日を作り改善を進めていっています。

一つのタスクであれもこれもしないようにする

リファクタリングタスクをしていると、あれもこれもしたくなってくることはよくあると思います。
そうなると往々にして go down the rabbit hole になってしまいます。

RSpecは柔軟かつ表現力豊富なテストを書く機能が備わっています。
裏を返せば色んな表現ができるので、リファクタリングの途中であれもしたい。これもしたいというのが出てくると思います。

リファクタリング中に新しく気づいた点は、前述のプロダクトバックログに違うタスクとして積んでいくようにしています。
基本的にプロダクト開発を進める合間に進めるので、合間にサッと対応するのが難しくなるとなかなか手をつけにくいという状態になってしまいます。

どんどんやることが増えていくと、隙間時間に気軽に始めるというのが難しくなってしまいます。
なので、あくまでやることを決めておき、やることリストは小さく保っておくのが良さそうに思っています。

おわりに

先日、ANDPAD検査の全てのRequest specのファイルについてこの記事で紹介している改善を完了することができました。
やっていること自体は小さな改善に過ぎないかもしれませんが、継続してリファクタリングを進めるという小さな成功体験を元にして今後も改善活動を進めていきたいと思います。

現在、アンドパッドでは一緒に働く仲間を大募集しています。
ご興味を持たれた方はカジュアル面談や情報交換のご連絡をお待ちしております。

engineer.andpad.co.jp

https://hrmos.co/pages/andpad/jobs/0000130hrmos.co

明日は、リアーキテクティングチームの白土 (@kei_s) さんが Kaigi on Rails 2022 で登壇した際のセッションで伝えきれなかったことをブログにしてくれるそうです!お楽しみに!