テストコード

テストコード

投稿日: 2025年07月26日

Tips
要約
  • テストコードはシステムの品質を担保し、バグを早期に発見する重要な役割を果たす。
  • RailsではFactoryBot、RSpec、Rswagを使ってテストコードを記述し、効率的にエンドポイントの品質をチェックする。
  • テスト駆動開発やテストケースの考え方を学びつつ、正確で意義のあるテストコードを作成できるよう努力する必要がある。
音声で記事を再生
0:00

はじめに

実務では必ず書くテストコード、、
私はオリアプ1つめでJESTを使用したテストコードを書きました。

その後ほぼ書く事なくきていましたが、4月から実務にガッツリ入り始めてからよく書くようになり、最近テスト駆動開発という名前を聞いたりテストについて質問されることもあったりでちょっと意識が上がってきたのでRailsの内容ですがテストについて書いてみようと思います。

テストコード

簡単そうに見えてテストコード書くのには結構時間がかかります。

このロジックのテストをするにはどんなテストケースを考えたら良いか、どこをモックすべきか、ダミーデータはどうするのが適切かなど、システムの品質を担保していると言えるテストになっているか考えることは普通に大変で難しいことです。

テストの重要性

  • 品質の担保

    コードが期待通りに動作するかを確認し、バグを早期に発見します。

  • 安全な変更の保証:

    コード修正時に既存機能への影響がないことを確認し、デグレを防ぎます。これにより、安心してリファクタリングや機能追加ができます。

  • 仕様の明確化

    テストコード自体が「コードが何をすべきか」を示す生きたドキュメントとなり、チームの理解を深めます。

  • 開発効率の向上

    バグ修正やデバッグにかかる時間を削減し、長期的には開発全体の速度を向上させます。

「リファクタしたつもりが挙動変えちゃった〜」って時にテストがあることで気づくと言うのもありますし、テスト駆動開発というのもあるらしく、正常系、異常系含めどう振る舞うべきか考えること自体が品質の向上につながりそうです。

教わったアンチパターン

前提としてテストはいつ誰が実行してもテスト結果は一緒になるべきです。

・境界値テストで現在時刻を使用

例えば、キャンペーン等である日時を境に出力結果を変えたいと言うケースがあったとします。
そのテストをする時に渡す日時が現在時刻だと実行するときによって変わってしまいますので、適切ではないです。
こういった同じコード・同じテストなのに、実行するたびに通ったり失敗したりするテストのことをFlakyなテストと言うそうです。

ある日時を境にちゃんと出力結果が変わるか見ないといけないので、必要なテストケースはその特定の日時の前後であることが必須ですよね、と言うことになります。
具体的には2025-08-01 00:00:00 を境に挙動が変わると言うことであれば、

  • 2025-07-31 23:59:59(境界の直前)

  • 2025-08-01 00:00:00(境界点そのもの)

  • 2025-08-01 00:00:01(境界の直後)

これらをテストすることでどこで分岐しているか正確にテストすることができます。

このように意味あるテストになるようにテストケースを考える必要があります。

こういうことまで詳しく教えてくれるボスはほんとに神だと思う

次には、実際のテストコードについてです。

使用しているツール

  • FactoryBot

  • Rspec

  • Rswag

主にDB設計してエンドポイント作るとこの3つでテストコードを書きます。
Railsの経験ある方なら当たり前なんですかね?

FactoryBot

テスト用のダミーデータ(テストオブジェクト)を簡単に作るツールです。
テーブルを作成した時に、そのテーブルのレコードをテストで使用する際に簡単にダミーデータを作成するために使用します。

Rspec

Ruby(特にRails)で書くテストコードのフレームワークです。
controllerなど単体のロジックのテストはこれで書きます。

Rswag

RSpecのAPIテストから自動でAPI仕様書(Swaggerドキュメント)を作るツールです。
リクエストとレスポンスといったAPIの挙動をテストします。

DB設計込みでエンドポイント作成すると、この3つのテストコードを必ず書きます。
足りてないままレビュー出すと「スキーマ(rswag)のテストも書いてくれ〜」と戻ってきますw

大変だなと感じるのは、テーブルのカラムの追加があった時に、modelとschemaの修正で終わりそうなところが、nil:falseとかだとテストにも影響することがあるので、ちょっとの修正でテストも修正しないといけんかったりがあるところです。。

一つずつ詳細みていきます。
正直存在しているコードを確認したりclaudeを使ったりしているとふんわりしかわからなかったので書きながらここで理解したいですw

FactoryBot

基本構造

FactoryBot.define do 
  factory :model_name do
    ...
  end
end

model_nameモデル用のfactoryを作成します。
使用する時はテストコードの中で以下のように記述します。

create(:model_name)

アソシエーション

関連モデルを定義します。
ファクトリー使用する際にこのファクトリーも生成されます。

association :model_hoge
association :model_fuga

アソシエーションで定義しているモデルのファクトリーないとテスト通らないので先に作っている必要があります

カラムのダミー値

name { Faker::Name.name }
name_kana { 'テスト' }
phone_number { Faker::PhoneNumber.subscriber_number(length: 10) }

Fakerでランダムなダミーデータが作成されます。
直接文字列を指定して書くこともできます。

Fakerが持っているメソッドなどはドキュメント確認すると良いです!


nil/false

deleted_at { nil }
is_default { false }

deleted_atは日付かnil(null)なのでnilをファクトリーのデータにはセット
is_defaultはbooleanなのでfalseをセットします。

trait

テストケースに応じてファクトリーのデータの値を変えたい時があるので、特定の条件付きで生成する際に使用します。

# デフォルトがtrueのデータを使いたい時
trait :default do
  is_default { true }
end

# 論理削除されたデータを使いたい時は時刻を入れる
trait :deleted do
  deleted_at { Time.current }
end

使い方は、 以下のようにします。

 create(:model_name, :default, :deleted)

Rspec

RSpec は、Ruby(特に Rails)で最もよく使われるテストフレームワークらしいです。
「describe」「context」「it」などの構造はTypeScriptで使用するJESTと変わらないなと言う印象を受けました!!

基本構造

RSpec.describe クラス名, type: :controller do
 describe 'メソッドや処理の名前' do
  context 'ある条件のとき' do
     it '期待する結果になること' do
        expect(処理).to eq(期待値)
     end
  end
  end
end

before / let

テスト内で使用するデータを予め定義しておきます。
beforeの中の処理はitの処理が走った時点で実行され、letは呼び出されたタイミングで実行されます。(時間とか!の有無でズレたりするので注意です⚠️)

let(:hoge) { create(:hoge) } # 遅延実行(:hoegが使われるとき実行)
let!(:fuga) { create(:fuga) } # 即実行
before do
  login(user)
end

モックとスタブ

モック・スタブは、本物のオブジェクトや処理を呼び出さずに、動作や戻り値を疑似的に定義することです。

スタブ(allow)

「このメソッドが呼ばれたらこう返す」という 挙動のすり替え

allow(SomeService).to receive(:call).and_return(result)

モック(expect)

「このメソッドが呼ばれたことを検証したい」という 期待の設定

expect(SomeService).to receive(:call).once

例えば、先ほどの時刻のモックを作成する場合

let(:fixed_time) { Time.zone.parse('2025-08-01 00:00:00') }
before do
 allow(Time).to receive(:current).and_return(fixed_time)
end

fixed_timeと言う変数に2025-08-01 00:00:00を入れておいて、Timeのcurrentが呼ばれたらfixed_timeの値を返すと処理自体をテスト向けに変えています。

リクエスト実行

get :index
post :create, params: { data: hoge_params }
patch :update, params: { id: hoge.id, data: update_params }
delete :destroy, params: { id: hoge.id }

ステータスコードの確認

expect(response).to have_http_status(:ok)            # 200
expect(response).to have_http_status(:created)       # 201
expect(response).to have_http_status(:unprocessable_entity) # 422
expect(response).to have_http_status(:not_found)     # 404

JSONレスポンスの確認

parsed = response.parsed_body
expect(parsed).to be_a(Hash)
expect(parsed['data']).to be_an(Array)
expect(parsed['data']['id']).to eq(hoge.id)
expect(parsed['errors']).to include("Name can't be blank")

レコード件数の増減(create/destroy系)

expect {
  post :create, params: { data: valid_params }
}.to change(Brand, :count).by(1)

expect {
  delete :destroy, params: { id: hoge.id }
}.to change(Hoge, :count).by(-1)

色々ありますが、こんな感じですねぇ

Rswag

Rswag は、RSpec で書いた API テストから Swagger 形式の API 仕様書を自動生成できるツールです。
Rails で API を開発していると、仕様書の整備・更新が結構大変だと思うのですが、Rswag を導入しておくとコードとドキュメントのズレが防げるので便利だと思います。



RSpec.describe 'api/v1/rswag_examples', type: :request do
  before { host! 'localhost:0000' }  # リクエスト先ホストを明示

  path '/rswag_hoge' do              # 対象となるAPIのエンドポイント
    get('hage') do                   # HTTPメソッドと説明(ここでは GET)

      tags 'RswagHoge'               # Swagger上のグループ名(タグ)
      produces 'application/json'    # レスポンスのContent-Typeを定義

      response(200, 'successful') do # ステータスごとのレスポンス定義
        run_test!                    # 実行とドキュメント生成を同時に行う
      end
    end
  end
end

ホスト名

before { host! 'localhost:0000’ }

リクエスト先ホストを明示します。
rswagで記述したパスをこのホストで解決するためです。

エンドポイント

path '/rswag_hoge' do
 ...
end

テスト対象のエンドポイントパスを定義します。
このスコープ内にこのエンドポイントの処理を書きます。

メソッド

get('hello world') do
 ...
end

HTTPメソッド(この例ではGET)と、その処理の説明を記述します。

タグ

tags 'RswagHoge'

APIドキュメントで分類するためのタグです。
Swagger UIでのグルーピングに使われます。

メディアタイプ

produces 'application/json'

レスポンスが返すメディアタイプ(Content-Type)を指定します。

ステータス

response(200, 'successful') do 
 ... 
end

HTTPステータスごとのテストとドキュメントの定義です。
テストコードと仕様の両方をこの中で一緒に書けます。

エラー場合もここに書いていく流れになります。

まとめ

ShiftB生の参考にはあんまりならない内容かなと思いますが、完全実務ベースな私個人のアウトプットとして書かせていただきました。

実装だけならそんなに時間かからないのに、テストコードあるから時間かかるって思うときもありますし、こういうのはAIに任せていいのではと思いつつわかってない人がA Iに任せてはいけないというのは最近特に感じているのでしっかり理解し、自分の頭で考えてすらすら書けるようになりたいです!!!(そうなってからAIに外注)

シェア!

Threads
user
吉本茜
山口在住/二児の母/エンジニア
Loading...
記事一覧に戻る
Threads
0