GitHub willnet
Comment8
Like
Created atMarch 16, 2019 20:53
Updated atMarch 16, 2019 20:53

Questions and feedbacks (8)

willnet
willnet commented 4 months

もしRailsを一から作り直すとしたら、今のRailsと違うものになりますか?なるとしたら、どの点が異なるでしょうか

Like(3)

willnet
willnet commented 4 months

@issei126

逆に shared_examples の使い所っていうのがあれば教えてほしいです。

現実世界でshared_examplesがバッチリハマってる!というケースはまだみたことがありません

さっき話したように、結局はDRYと可読性低下とのトレードオフを考えつつ、DRY側のメリットが上回ると判断した場合にやむをえず使う、というものだという認識です。

現実世界を離れると、RSpec公式のsharedexamplesの例は可読性低下がほぼなく sharedexamplesの使い所感があります。↓に引用しましたがsharedexamplesの定義側が依存しているコードがテストコード本体側に存在しないので、脳内マージが容易にできますよね。こういうコードが現実世界でもしあればsharedexamplesのメリットを活かせて便利だな、と思います。

require "set"

RSpec.shared_examples "a collection" do
  let(:collection) { described_class.new([7, 2, 4]) }

  context "initialized with 3 items" do
    it "says it has three items" do
      expect(collection.size).to eq(3)
    end
  end

  describe "#include?" do
    context "with an item that is in the collection" do
      it "returns true" do
        expect(collection.include?(7)).to be_truthy
      end
    end

    context "with an item that is not in the collection" do
      it "returns false" do
        expect(collection.include?(9)).to be_falsey
      end
    end
  end
end

RSpec.describe Array do
  it_behaves_like "a collection"
end

RSpec.describe Set do
  it_behaves_like "a collection"
end

shared examples - Example groups - RSpec Core - RSpec - Relish

Like(1)

willnet
willnet commented 4 months

@sue445

letのスコープは狭くするという話がありましたが、そうした場合contextの数(例:10個以上)によっては全く同じletを何個もコピペすることになってDRYにならないという問題があると思います。

こういう場合でもletは愚直に各contextに書くべきでしょうか?「○個以上なら外側に切り出す」という基準があるのであれば教えてほしいです。

口頭では「愚直に各contextに書く」と回答しました。僕の中ではDRYよりも脳に負担をかけないことが優先されるので、少しletをコピペするくらいなら問題ないと思っています。

あとで思いついた別案としては、共通部分を新しいcontextとして新設し、他のcontextをその配下に持ってくるとDRYと(ある程度の)読みやすさの両立ができるんじゃないかなと思いました。しかしあんまりネストが深くなりすぎても読みづらくなるのでこれもケースバイケースではあります。

Like(1)

willnet
willnet commented 4 months

@aeroastro

今回の可読性を現場で実践していく上で、重要だと考えているもの(方法論)があれば教えていただきたいです。 特に、既存のテストコードを、可読性が高い状態へと改善していく際のヒントを頂けると助かります。

チームメンバーに可読性の高いテストコードの書き方を布教する、というのが一番大事かつ一番大変なことだと思います。チームのうち一人だけ意識が高くても全体のコードの品質はなかなかあがりません。

知見を布教する、というのは結構難しいことです。仮に今回のスライドを共有しても、全員が読むとは限りません。読んでも読んだ内容を実践して身につけるまでいける人はあまり多くないのでは、と思います。

個人的にはペアプロやモブプロで既存のテストのリファクタリングを進めるのがオススメです。みんなで試行錯誤するのを続けていくと、きっと自然と知見を獲得していけるはず。

Like(1)

willnet
willnet commented 4 months

@indigolain

暗黙な依存が存在するfactoryに関して、これを良い感じにリファクタする方法やアプローチがあれば教えていただきたいです。

factorybotの定義を確認し、暗黙な依存があればそれを剥がした状態でテストを実行、コケたところを一つづつ修正していく、という地道な作業をしていくしか方法はなさそうです…。大変だと思いますが頑張ってください!

Like(0)

willnet
willnet commented 4 months

@hshimoyama

関連が複雑なモデルのレコード群で trait を使って制御しようとすると、入れ子の子・孫レコードを制御する trait をその親に書く> 必要があるなど、記述が煩雑になる問題があると思うのですが、そういうケースの場合どのように対処していますか?

前提として、traitで関連先を作るのは、下に載せたコードのように適当な関連先を作るときのみかと思います(例: postが0個の状態でuserを作成するとバリデーションエラーになってしまうから、なんでもいいのでpostを作りたい)。テストに必要な値を設定した具体的な関連先が欲しい場合はtraitではなくテストコード本体に書くべきです。

関連が複雑なモデルで「適当な関連先を作りたい」というケースに遭遇したことがないのですが(たいてい具体的に関連先の値を設定したいので、テストコード本体で関連先を作っている)、もしそのようなケースがあったら普通にtraitで定義してしまいそうです。これで回答として合っているかわからないので、はずしていたらもうちょっと具体的なケースを教えてもらえるとありがたいです

FactoryBot.define do
  factory :user do
    sequence(:name) { |i| "username#{i}" }

    trait(:with_posts) do
      after(:create) do |user, evaluator|
        create_list(:post, 2, user: user)
      end
    end
  end
end

Like(0)

willnet
willnet commented 4 months

@cobafan

willnetさんの話されている内容だとファイルが大きくなりそうですが、 そのために工夫されていることはありますか? 例えば、ネストは何階層までにしている等あればお聞きしたいです。

例えばユニットテストを書く場合、各クラスを小さく保つ(例えば100行以内にする)ことで、自然と対象となるテストコードの大きさも読める範囲に収まります。クラスが大きくなりそうだったら別のクラスに分割していきます。

E2Eのテストを書く場合は、controllerの各アクションごとに1ファイル、という単位で分割することが多いです。この単位で分割すればそこまで大きくはならないはず…。

ネストの数は特に制限しておらず、気になったら考えるというやり方をしています。ネストが深くてもそれほど読みづらくないテストも存在しているので。例えば↓のテストはcontextが5つもネストしていますが上のスコープについて考える必要が少ないので、それほどは読みづらくないかなと思っています

RSpec.describe '友達にメッセージを送る', js: true do
  let!(:user) do
    create :user,
           :with_condition,
           twitter_uid: login_twitter_uid,
           twitter_name: '@netwillnet'
  end
  let!(:friend) do
    create :user,
           :with_condition,
           github_uid: login_github_uid
  end
  before { Friendship.make!(user, friend) }

  context 'ログインして友達のページへ遷移し、"メッセージを送る"リンクを押したとき' do
    before do
      login_by_twitter(user)
      click_on friend.name
      click_on 'メッセージを送る'
      page.has_content?("#{friend.name} さんとのメッセージ")
    end

    it 'メッセージ用のページが表示されていること' do
      expect(page).to have_content("#{friend.name} さんとのメッセージ")
    end

    context 'かつ、メッセージを書いて"送信する"ボタンを押したとき' do
      before do
        fill_in 'body', with: 'こんにちは!'
        submit = find('[data-target="messages.submitButton"]')
        10.times do
          break unless submit.disabled?

          sleep 0.5
        end
        submit.click
      end

      it 'メッセージが表示されていること' do
        expect(page).to have_content 'こんにちは!'
      end

      context 'さらに友達のユーザがログインしたとき' do
        before do
          logout
          login_by_github(friend)
        end

        it '通知欄が"1"になっていること' do
          within('.navigation__link-to-messages') do
            expect(page).to have_content('1')
          end
        end

        context 'かつメッセージ一覧に遷移したとき' do
          before { find('.navigation__link-to-messages').click }

          it '新着通知の表示が消えていること' do
            expect(page).to have_no_css('.navigation__notification-number')
          end

          it '未読の表示になっていること' do
            expect(page).to have_css('.list-group-item-success')
          end

          context 'かつ、メッセージ詳細を表示してから一覧に戻ってきたとき' do
            before do
              click_link 'こんにちは!'
              page.has_content?("#{user.name} さんとのメッセージ")
              find('.navigation__link-to-messages').click
            end

            it '既読の表示になっていること' do
              expect(page).to have_content 'メッセージ一覧'
              expect(page).to have_no_css '.list-group-item-success'
            end
          end
        end
      end
    end
  end
end

Like(0)

willnet
willnet commented 4 months

@expajp

let!の多用はDBアクセスが増えてテストにかかる時間が増えてしまうと思うのですが、なにか対策はありますでしょうか?

(先程口頭で回答しましたが)letでも実行されればlet!同様DBアクセスが増えます。DBアクセスが本当に必要なときだけlet!を使うことで、DBアクセスをletを使ったとき同様最小限に防げると思います。

Like(0)

This software is available as open source under the terms of the MIT License.
Copyright © 2018 Yoshiyuki Hirano