こんなテストコードは嫌だ(テストコードの失敗例)

f:id:ryoutaku_jo:20190503203133p:plain

【結論】

・ idに依存している

・ドキュメントとしての読みづらい

・letとlet!が正しく使い分けられていない

【目次】

【本題】

Qiitaでコメントを頂いた

ゴールデンウィークは、個人開発を行って、その開発過程を毎日Qiitaに投稿していましたが、昨日投稿した記事にコメントを頂きました。 しかも、あの有名な「プロを目指す人のためのRuby入門」の著者である「伊藤淳一」さんからです! 更に、動画付きでした!!

(コメントが飛んできただけでもビックリなのに、この展開なので、正直ビビりまくっていました・・・)

※追記(5/4):伊藤さんからのコメントを「マサカリ」と表現していましたが、「強い批判を指す場合に使用するのが適当」とご指摘頂いたので、文言修正しています。

↓該当の記事

【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装 - Qiita

↓動画

www.youtube.com

ご指摘は、私が記述したテストコードを関する内容です。 今回は、そのご指摘の内容をまとめて見ました。

修正前・修正後

指摘箇所を解説する前に、 まず私が書いたコードと、伊藤さんが修正して下さったコードを紹介します。

↓私が書いたコード

require 'rails_helper'

RSpec.describe 'post_comment', type: :system do
  let!(:user) { create(:user) }
  let(:post) { create(:post, id: 1, user_id: 1) }
  let(:post_description) { create(:post_description, post_id: 1) }
  before { login_user(user) }

  it 'creates post' do
    visit posts_path
    click_on(I18n.t('common.button.new'))

    fill_in 'post[title]', with: 'テストタイトル1'
    fill_in 'post[post_description_attributes][description]', with: 'テスト詳細1'
    click_button(I18n.t('common.button.submit'))

    expect(page).to have_content('テストタイトル1', 'テスト詳細1')
  end

  it 'edits post' do
    post
    create(:post_description, post_id: 1)
    visit edit_post_path(post)

    fill_in 'post[title]', with: 'テストタイトル2'
    fill_in 'post[post_description_attributes][description]', with: 'テスト詳細2'
    click_button(I18n.t('common.button.submit'))

    expect(page).to have_content('テストタイトル2', 'テスト詳細2')
  end
end

↓伊藤さんが修正して下さったコード

require 'rails_helper'

RSpec.describe 'post_comment', type: :system do
  let(:user) { create(:user) }

  before do
    login_user(user)
  end

  describe 'Create post' do
    it 'creates post' do
      visit posts_path
      click_on '新規作成'

      fill_in '記事のタイトル', with: '世界最強のRSpec入門'
      fill_in '記事の詳細', with: 'jnchitoの解説記事はもう古い!これからはこれがスタンダード。'
      click_button '投稿する'

      expect(page).to have_content '世界最強のRSpec入門', 'jnchitoの解説記事はもう古い!これからはこれがスタンダード。'
    end
  end

  describe 'Update post' do
    let(:post) { create(:post, user: user) }
    let!(:post_description) { create(:post_description, post: post) }

    it 'edits post' do
      visit edit_post_path(post)

      fill_in '記事のタイトル', with: '宇宙最強のRSpec入門'
      fill_in '記事の詳細', with: 'jnchitoの解説記事はもうオワコン!これからはこれがスタンダード。'
      click_button '更新する'

      expect(page).to have_content '宇宙最強のRSpec入門', 'jnchitoの解説記事はもうオワコン!これからはこれがスタンダード。'
    end
  end
end

idに依存している

まず、ご指摘があったのは、このコードからです。

  let(:user) { create(:user) }
  let(:post) { create(:post, id: 1, user_id: 1) }
  let(:post_description) { create(:post_description, post_id: 1) }

こちらidをベタ打ちで指定していますが、テスト環境では、テストを実行する度に、新しいidが割り当てられる為、基本的に以前のidは使い回しません。 その為、この様なid依存の記述をしてしまうと、別の実行環境ではテストが正常に動作しなくなり、再現性が無くなります。

ですので、オブジェクトを生成する際には、原則id指定は避けたほうが良いという事です。

では、どうすれば良いかというと、 下記の様に参照を渡して、外部キーを指定すれば、ベタ打ちをせずに済みます。

  let(:user) { create(:user) }
  let(:post) { create(:post, user_id: user) }
  let(:post_description) { create(:post_description, post_id: post) }

ドキュメントとしての読みづらい

テストコードは、何もテストだけが目的ではなく、それ自体がドキュメントとしての役割を担うという事も意識する必要がある。 そういった意味では、I18nやname属性で対象のリンクやフォームを指定する私の方法だと、直ぐにイメージがしづらい欠点があった。

頻繁に更新される項目であれば、I18nは有用だが、ある程度文言が固まっているのであれば、画面の表示そのものを記述した方が、理解しやすくなるというアドバイスを頂いた。

修正前

fill_in 'post[post_description_attributes][description]', with: 'テスト詳細1'
click_button(I18n.t('common.button.submit'))

↓修正後

fill_in '記事の詳細', with: 'jnchitoさんの解説記事は最高!これからもこれがスタンダード。'
click_button '投稿する'

とはいえ、まだフロントの実装イメージが固まっていないので、ここの修正は後々に繰り越して、フロント実装時に変更しようと考えています。

letとlet!が正しく使い分けられていない

qiita.com

宣伝

お礼の意味も込めて、伊藤さんが書かれた書籍を紹介しておきます。

leanpub.com

https://www.amazon.co.jp/dp/B077Q8BXHC/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1www.amazon.co.jp

いずれもRuby(Rails)で開発する人間であれば、必携の書籍では無いでしょうか?

参考情報

Qiitaに載っていたRSpecのコードを勝手にコードレビューしてみた - YouTube

《今日の学習進捗(3年以内に10000時間に向けて)》

Qiitaで初めて技術的なアドバイスを頂いたが、コメントを送って頂いた方が伊藤さんという事もあり、非常に丁寧に訂正して下さったので、本当に理解が深まる良いきっかけになった。 自分の間違いが白日の下に晒されるというのは非常に恥ずかしい事だけど、成長して行くには避けては通れない道だと、改めて痛感した。 キャリア積むほど求められるクオリティも高くなるので、早いうちから、アウトプットして、アドバイス貰いまくるのは重要だと考えている‼️

学習開始からの期間 :147日
今日までの合計時間:1431h
一日あたりの平均学習時間:9.8h
今日までに到達すべき目標時間:1342h
目標との解離:89h
「10,000時間」まで、

残り・・・「8569時間!」