Rails製 APIにおける Request Spec(RSpec)の作成方針

未だ書き慣れたとは言い難いRSpecだが、APIのSpecを改めて書いてみたところ、ある程度方針が固まってきたのでまとめてみる。

RSpec共通

describe, it, contextを用いて test caseを羅列する

なにはともかく枠から作る。ここさえ固まれば以降大きくはブレない。出来るだけこの時点でレビューに出すのが望ましい。 この際contextはなるべく浅い階層になるよう作成する。

具体的には以下記事参考

RSpecえかきうた|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

簡単なアイテムから1コンテキスト分作り込む

ここからは具体的にSpecの中身を作り込んで行くが、基本的には「簡単なテストから1つずつ満足のいくように作り込む」方針を取るのが望ましいと考えている。

Specは各Context(なりit)で書き方が共通するパターンが多い。そのため途中で書き方を変えるとそれが広い範囲に波及し面倒なことになる。 それを避けるため、まずは1つ満足の行く形に1つテストを作り込み、そのやり方を他に適用して行く形が効率が良いと考えている。 またその際なるべく簡単なケースから作り込んでいけば、より難しいケースを作成する際に(部分的に作成方針が決まっているため)作成の難易度が下がる。

なるべくDLS等のRSpec固有の書き方は避ける

これは諸説あると思うが、私個人としてはなるべくRSpec固有の書き方は使わないようにしている。具体的には before do, subject, let, 各種マッチャ がそれに該当する。

理由としては、まぁ一言で表せばめんどくさいからだ。RSpecは様々な便利なツールを用意してくれてはいるが特に推奨はしていない。(推奨しているのはdescribeやitを使ったテストケースの分け方くらいだろうか) 公式があまりopinionatedで無い分、いわゆる「良い書き方」には色々な意見がある。以前は「Better Specs」と呼ばれるRSpecの機能をフル活用した書き方が推奨されていたようだが、昨今ではよりシンプルな書き方も支持されてきている。

サヨナラBetter Specs!? 雑で気楽なRSpecのススメ - Qiita

その上で私は「シンプルな書き方」を支持する。正直あまりRSpecでしか役に立たないような内容をガッツリ勉強しようとは思わないので。。。RubyRailsの書き方で実現できるならそっちの方が綺麗かなぁとすら思う。

基本シンプルに記載し、可読性やメンテナンス性の向上が非常に高く見込める時だけRSpec固有の書き方を利用する方針だ。

その上で具体的に使うものは

  • describe, context, it
  • expect , eq
  • let!(出来るだけ避ける)

このくらい。種々の便利なマッチャは出来るだけ使わず、ほぼ eq で通す(例外としてはraise_errorなどは使うけれども)

他には共通処理はbeforeを使わずdefを使う。問題なくテストは書ける。

FactoryBotはフル活用する。

こいつは単純に有能で利用することで可読性やメンテナンス性の向上が非常に高く見込めるのでフル活用していく。

AAA(Arange Act Assert)を意識する

itの中身はおおよそ、データの準備・テスト対象処理の実行・結果の検証(テスト)の3つに分けられる。itの中身もこの3種類が明確に分かれるように記載する方がわかりやすい。 3,4 行程度ならベタに書くで良いと思うが、長くなるようなら改行を挟んだり、defなりヘルパーなりを作成することで見やすくする等の工夫も必要になってくるかもしれない。

参考: 【アンチパターン】Arrange、Act、Assert(AAA)を意識できていないRSpecのコード例とその対処法 - Qiita

Request Spec 固有

ここからはRequest Spec固有の作成方針を記載する。

describe, context, itの書き方

describe

"GET /api/v1/stocks"の形で、httpsのメソッドとURIを記載する。

context

正常系や異常系、ログイン・未ログインなど階層化せず、利用ケースをフラットに記載する。

it

context毎におおよそ以下3種類を作成する。それぞれitを分ける。itを分けるかは諸説あるかもしれないが、私としては確認する内容が明確になるためこの形が好き。パフォーマンスの影響は考慮する必要があるかもしれない。

  • httpのステータスコードの確認
  • レスポンスの内容(bode)を確認
  • (post/patch/delete等の場合) 実データへの影響を確認

パラメータのチェック

APIから取得したパラメータのチェックはおそらくrequest specで頻出の内容と思うが、私としてはパラメータを1つ1つexpectを切って確認するやり方を取っている。 例えば以下のようなやり方だ。

        expect(actual[:name]).to eq params[:name]
        expect(actual[:colorNumber]).to eq params[:colorNumber]
        expect(Time.zone.parse(actual[:manufacturingDate])).to eq Time.zone.parse(params[:manufacturingDate])
        expect(actual[:quantity]).to eq params[:quantity]
        expect(actual[:condition]).to eq params[:condition]

明らかに冗長ではあるため改善の余地はある。

例えばhave_attributes を使うことが考えられる。ただhave_attributesは全てのパラメータが一致していないとテストがパスしないため、レスポンスにidが含まれる場合等も考慮すると柔軟性に欠ける。 もっと良い使い方もあるかもしれないが、、、 (別の記事の一部の記載では部分的なチェックもできると書かれていたが、私の環境ではうまくいかなかった。。。)
週刊Railsウォッチ(20200629前編)RSpecをメンテしやすくする9つのコツ、application.jsのrequireをimportに置き換え、HTTP 308 Permanent Redirectとはほか|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

またrspec-parameterizedの利用もありそうではある。折を見て今後検討したい。 github.com

ひとまずこのやり方でも最低限は要求を満たせると考えている。しばらくはこれで行く。

その他参考サイト

【翻訳】RSpecのリードメンテナだけど何か質問ある? - Qiita