テストの書き方

2025/09/02

どこのテストコードを書くのか

  • 信頼できない箇所
  • 将来コードの他部分を変更した際に不具合が発生しそうな箇所
  • セキュリティ上重要な箇所
  • 不具合があると致命的な箇所
  • システムの重要度が高いユースケース
    • 例:ネット販売
      • ユーザー登録→商品選択→購入の流れ
  • 複雑なロジックのあるコードが書かれている箇所
  • モンキーパッチや無理なハックをしたところ
  • 例外処理

どこのテストコードを書かないのか

  • Railsモデルのシンプルな関連(belongs_to, has_many)・バリデーション
    • バリデーションでも正規表現などのロジックが含まれている場合や独自メソッドでのバリデーションの場合はテストすべき
  • 自動生成で手を加えていないコード
  • 別のテストで動作確認できている箇所

書き方の基本ルール

  • 対象コードの使用がわかるように書く
  • 抜け漏れのない必要最小限のテスト項目を洗い出す
  • 中身のないdescribe, context, itを先に書く
  • contextには条件を入れて itには振る舞いを入れる
  • subject は無理に使わない
  • データの更新・削除に関わるテストはbefore/afterの両方を検証
    • beforeの状態があったことの確証がない
    • 例:更新
      • A→Bに変わる場合は、最初がAであることと、Bに変わったことの両方をテストする
    • 例:削除
      • Aを消す場合は、Aが最初に存在することと、削除されたことを検証
  • 上から下へ読み下せるコードにする
  • 過度なDRYはNG
  • 実際のユースケースが想像できるテストデータを使用する
    • NG:「テスト」
    • OK:「田中太郎」
  • 実行順序・環境・日時などに依存しない、いつ誰がどんな風に実行してもパスするテストにする
  • 期待値はベタ書きする
    • 例: eq price / 2 -> eq 500
  • 条件やループ処理はなるべく使わない
    • ループでテストをまとめるより、上から下に素直に読めるWETで良い

AAA(Arrange、Act、Assert)パターンをなるべく遵守する

Arrange(準備)

オブジェクトを初期化し、テスト対象のメソッドに渡されるデータの値を設定

Act(実行)

設定されたパラメータでテスト対象のメソッドを呼び出す

Assert(検証)

テスト対象のメソッドの操作が予測通り動作することを検証

AAAパターンをRSpecで活用

AAAで場所を分散させる

  • Arrange = let/let!、あるいはbeforeの中、場合によってはitの中
  • Act = itの中
  • Assert = itの中

例:一定期間を過ぎるとキャンセル料がかかるシステム

Arrange:beforeで日付を設定
Act:itでキャンセルを実行
Assert:上記it内でキャンセル料が発生したことを確認

ActとAssertを一緒に書くこともある

expect + change で、1行で「AするとBになる」を検証できるため、この場合は一回でAct , Assertを実施することになる。

テスト内容を決める順序

  1. 前提条件と想定結果を書き出す
    1. 先にdescribe, context, it(中身なし)を書くと良い
  2. 想定結果が同じもの、または検証内容が同じものをグループ化する
  3. その中から、検証内容が異なるものは別グループにする
  4. その中から、前提条件が違うものは別グループにする

Railsでのテストデータの書き方(モデル)

itの外側で let(:valid_attributes)としてvalidな雛形を定義する。
各テストではそれを活用しinvalidの場合は
invalid_attributes = valid_attributes.merge(web_site_url: nil)
のように雛形を変更する形で利用する。

let(:valid_attributes) do
	{
		name: '田中太郎',
		email: 'tanaka@example.com',
		tel: 01234567890,
		age: 54
	}
end

it '電話番号が無効な場合' do
	invalid_attributes = valid_attributes.merge(tel: '123')
	user = User.new(invalid_attributes)
	expect(user).not_to be_valid
end

it 'メールアドレスが無効な場合' do
	invalid_attributes = valid_attributes.merge(email: nil)
	user = User.new(invalid_attributes)
	expect(user).not_to be_valid
end

関連ページ

TDD

参考