テクノロジー戦略本部の稲川です。
今日は私がRspecやrailsを触り始めてから10年以上たち
長年Rspecを触りながら考えていたことを記事にさせていただきます
1.Rspecとは
Rubyで作られたライブラリ(Gem)であり
Ruby on Railsのフレームワークで実装された
プログラムの振る舞いを記述するためのドメイン特化言語(DSL)を提供するフレームワーク
Pythonのunittestや、JavaのJUnit、C#などの MSTestなどと同じように
機能要件をテストコード実行で担保できるようにする
いわば、システムのメンテナンスを効率的にするためのもの
2.なぜ、Rspecを使用するのか
- 開発効率をあげるため
-
画面でポチポチして確認する工数を削減し効率的にテストを行えるようにするため
-
-
仕様(業務要件)の担保をspecでカバーすることでテストの漏れを少なくするため
-
品質担保を確実にすることでより安全なシステムの運用保守を行えるようにする
-
実装ロジックのデグレを実装時に把握できようにすることによりUT、ST時の不具合を少なくする
-
-
上記、2つにより開発時の心的負担を減らすため(安心して安全なシステムを保守、運用できるようにするため)
3.Rspec導入で可能なこと
-
仕様の担保(テスト)を自動化することにより効率的な保守開発が可能
-
仕様変更対応時に影響範囲の把握がだいたい可能
-
ただし、マイクロサービスの場合、Mock対象のAPIの挙動が変更した場合はこの限りではない
-
-
仕様変更対応時に影響範囲の把握しやすくなることによりより正確な工数の見積もりが可能
-
リファクタリングが可能
-
コアロジックのTOPに対してのspecがあった場合に限る
-
コアロジックのTOPに対するspecがなく、細かいspecだけの場合はどうなるか(以下、デメリット)
-
コアロジック全体の仕様を担保していないため不足の事態(バグ)に対応するのに工数がかかる
-
保守開発を行う上で自動化したテストの信頼性に欠けるため、テストの工数がかさむ
-
-
4.Rspec実装時の進め方(概論)
優先度の高いものから順に以下を記載
describeとcontextをきる
-
テスト設計に該当
-
実装者は有識者に上記観点で仕様を満たせるか(仕様を担保できるか)確認をとる
-
レビュアーは上記、テストの観点で仕様を満たせるかレビュー時に確認する
beforeやletでテスト実施に必要な前提条件を作成(ここまでできればテストの8割は完成)
-
テスト実施(前提条件作成)に該当
subject or before or it でテスト実施+ itの中で検証内容の確認を行う
-
テスト実施+想定結果の確認に該当
spec追加当初はデバッグして以下を確認すること
-
前提条件となるデータが想定したデータになっていること
-
振る舞いの検証内容があっていること
-
ファイル単位で実行してオールグリーンになること
-
全体実行してオールグリーンになること
5.Rspec実装時の進め方(各論)
specの導入箇所の見極め方
基本はコアロジックのTOPに対してinputの条件ごとにoutputの振る舞いの検証を行う
(コアロジックがどこにあるかを見極めることが重要)
-
コアロジックのTOPに該当する箇所に対してspecを追加
-
inputに対するoutputの確認が行える一番TOPクラスにてspecでの確認を追加
-
inputのチェックの場合-> request_paramsの specを追加
-
APIの場合-> controllerのrequest specを追加
-
describeとcontextの切り方について
describe (振る舞いの対象)
-
controllerのspecの場合
-
アクションごとにdescribeをきる
-
controllerのspecの場合
-
アクションごとにdescribeをきる
-
# instance methodの前に#をつける -> HTMLのDOMをid指定するときと同じ describe '#create' do end
-
-
-
-
-
上記以外の場合
-
class methodの場合(例:class method名: suggest)
# method名の前にドット(.)をつける -> HTMLのDOMをclass指定するときと同じ describe '.suggest' do end
-
instance methodの場合
-
# instance methodの前に#をつける -> HTMLのDOMをid指定するときと同じ describe '#create' do end
-
-
context (振る舞いの対象の条件)
-
request spec(APIとなるcontrollerのspec)の場合
-
例)
Rspec.describe UsersController, type: :request do describe '#create' do context 'success' do end context 'failed' do end end describe '#show' do context 'success' do end context 'not found' do end end end
-
-
request paramsの場合(一般的にはmodelのvalidationのcontextと切り方は同じ)
-
例)
Rspec.describe UserRequestParams do describe '#validate!' do context 'valid' do # この中に細かい条件があった場合にはその条件毎にcontextきる end context 'invalid' do # この中に細かい条件があった場合にはその条件毎にcontextきる end end end
-
-
上記以外の場合
-
例)
Rspec.describe UserService do describe '#method name' do context '正常系のコンテキスト名' do end context '正常系じゃないコンテキスト名' do end end end
-
subject、before、let、it の実装する順番や実装内容について
-
実装する順番
-
subject → before → let → itの順で実装
-
-
実装例)
Rspec.describe UserController do
describe '#create' do
context 'success' do
# subjectのaliasをきる
subject(:create_result) { 振る舞いの検証対象 }
before do
* 振る舞いの検証を行う前に必要な前提条件をここに実装
* 特にFactoryBotでcreateしたものについてはここに入っていること
(理由)入っていないと実ロジックからspecの it ブロック(検証内容)に戻ってきたときに
DBにあるはずのデータがないことになってしまうため
end
let(:created_users) { create_list(:user, 2) }
let(:expected) do
レスポンスの戻り値をここで作る
end
it do
# APIのレスポンスが期待通りになっていることを確認(subject or create_resultの結果を確認)
# Rspecでは一度subjectで実行された結果はitブロック内でキャッシュされる
expect(create_result).to eq expected
## 以下、(SSのspecは特に)DBの確認内容
# レコードの件数確認
expect(User.count).to eq (created_users.count + 1)
# DBにinsertされたデータが期待通りになっていること
# insertしたテーブルの件数分以下を追加
expect(User.last).to have_attributes(
first_name: '氏名(姓)',
last_name: '氏名(名)'
)
end
end
end
end
it ブロックでの検証内容について
-
it ブロックで1行で書く場合(subjectを使った場合)
-
request spec(APIのspec)の場合
-
共通で確認する内容(output の確認)
-
レスポンスの確認
-
HTTP statusの確認
-
エラーの確認(raise_errorでエラーになったclass名を確認)
-
-
DBに対して何かしらのアクションをするAPIの場合
-
insertする場合
-
個数が想定通りの数になっていること
-
insertしたレコードが想定通りの値でinsertしていること
-
-
deleteする場合
-
個数が想定通りの数になっていること
-
想定したレコードがなくなっていること
-
-
-
-
上記以外の場合
-
レスポンスの確認(output の確認)
-
-
上記以外で更に確認が必要な場合
-
Rspecのmatcherを調べてよしなに使う
-
shared_contextとshared_exampleの使い方について
spec実行時に前提条件や検証内容が同じである場合に共通化する場合には以下を使用
(ファイル単位で切り出すことは可能、ただし、その場合には切り出したファイルを requireすること)
-
shared_context
-
before、letを記載
-
it はここには入れないようにすること
-
shared_exampleを実行するときには include_context を使用すること
-
https://relishapp.com/rspec/rspec-core/docs/example-groups/shared-context
-
-
shared_example
-
let、itなどを記載
-
shared_exampleを実行するときには it_behaves_like を使用すること
-
https://relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples
-
-
例
Rspec.describe UserController do describe '#create' do shared_context 'prepare' do before { ここに前提条件を書く } let(:expected) { result } end shared_example 'OK' do let(:result) { 成功した場合のレスポンス } it { is_expected.to eq expected } end shared_example 'NG' do let(:result) { 失敗した場合のレスポンス } it { is_expected.to raise_error expected } end context 'success' do include_context 'prepare' it_behaves_like 'OK' end context 'failed' do include_context 'prepare' it_behaves_like 'NG' end end end
バイセルテクノロジーズではエンジニアを募集しています。
もし興味をお持ちいただけましたらぜひご連絡下さい!
6.参考資料
スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)
Rspecの書き方について
https://relishapp.com/rspec/rspec-rails/docs
テスト駆動開発(TDD)とは?TDDの進め方をステップ毎に解説!
https://www.valtes.co.jp/qbookplus/1069
RSpec の概要と、RSpec on Rails
Rspecのmatcher
https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
Rspecのshared_contextの使い方
https://relishapp.com/rspec/rspec-core/docs/example-groups/shared-context
Rspecのshared_exampleの使い方
https://relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples