Clean Test Code Revisedの発表内容について


#1

昨日のRails Developers Meetup 2019で可読性の高いテストコードの書き方について発表しました。発表を聞いた人や資料を読んだ人からの感想を聞くと「総論としては賛成なんだけど各論については違う意見を持っている」という人が結構いるな、という印象です。

みなさんが具体的にどういう視点でどういう選択をしているのか知りたいので「自分ならここはこう書く、なぜなら〜」みたいなコメントをいただけるととてもうれしいです(\( ⁰⊖⁰)/)!!!

Clean Test Code Revised - Speaker Deck


#2

Twitterに書いた後にDiscourseのスレに気づいたので、こちらにも投稿しておきます。

ただ、個人的にはFactoryBotのデフォルトはランダム値じゃなくて、DBのデフォルト値が好きかな。ユニットテストで品質的な事をテストする必要は無いと思うし、微妙なランダム値だとFlaky Testになったりするので。


#3

自分の発表の裏番組だったので残念ながら聞けなかったんですが、資料読みました。ほぼ同意です。
差分はshared_context, shared_examples`は使わない、bangなしのletも使わない、くらいですかね。letは呼ぶ・呼ばないで事前条件を変えるので、脳のメモリを無駄遣いする&事故の元と思ってます。

微妙なランダム値といえば、ランダムデータ生成はFakerを使っていたのですが、衝突してテストが落ちることがあるので、文字列の属性にはSecureRandom.urlsafe_base64 を使うようになりました。結果のdiffが読みづらいことがありますが、まあ誤差の範囲という印象。


#4

差分はshared_context, shared_examples`は使わない、bangなしのletも使わない、くらいですかね。letは呼ぶ・呼ばないで事前条件を変えるので、脳のメモリを無駄遣いする&事故の元と思ってます。

なるほどなるほど。チームで開発するならこれくらい縛った方が安全な気もしますね…

微妙なランダム値といえば、ランダムデータ生成はFakerを使っていたのですが、衝突してテストが落ちることがあるので、文字列の属性にはSecureRandom.urlsafe_base64 を使うようになりました。結果のdiffが読みづらいことがありますが、まあ誤差の範囲という印象。

Fakerで衝突を防ぎたい場合はFaker::Name.unique.nameみたいにできる、ということをこないだ知りました(まだ試してない)

stympy/faker: A library for generating fake data such as names, addresses, and phone numbers.


#5

微妙なランダム値だとFlaky Testになったりする

ちょっと説明が足りていなかったので、補足を書きます。

Flaky Testの例

例えば、「チームにユーザーを追加できる機能」をテストしようとして、以下のようなテストを書くとする。

let(:owner) { create :user }
let!(:team) { create :team, owner: owner }
let!(:outsider) { create :user }

it '画面にチームメンバーの名前が表示されること' do
  visit team_path(team)

  within "#team_#{team.id} .member-list" do
    expect(page).to have_text owner.name
    expect(page).not_to have_text outsider.name
  end
end

このテストはownerとoutsiderの名前が偶然一緒になると、テストが落ちてしまう。
ランダム値を使ったテストはこういうテストが生まれやすく・・・

とここまで書いて思ったけど、これ単に「テストコード内に書かれていない暗黙的な条件をExpectationに使うのは :no_good_woman: 」という話な気がしてきました。

やっぱりFlaky Testで困る

資料を読み直して気づきましたが、資料に記載されていた「rspec --seed で再現できる」では解決できないです。
seedで再現できるのはテストの実行順序だけなので、Factoryのランダム値までは再現できないはず。
一番の問題はこれかな。


#6

FactoryBotのデフォルトはランダム値じゃなくて、DBのデフォルト値が好きかな。ユニットテストで品質的な事をテストする必要は無いと思うし、微妙なランダム値だとFlaky Testになったりするので。

私はFactoryBotのデフォルトはランダム値派です。

Flaky Testの例
「チームにユーザーを追加できる機能」をテストしようとして、以下のようなテストを書くとする。

このテストで同一の名前だと落ちると気付けるのはメリットになりませんか?

このテストが落ちた時に同一の名前だと落ちるけど別に気にしなくていい場合は名前が違うという前提条件をcreate :user, name: 'owner'にすることになると思います。見えていない前提条件が足りなかったということになります。

それはFlaky Testというよりはテストが適切ではなかったということになりませんか?

この例でわかるランダムのメリットは意図していない前提条件に気付けること / 前提条件があるならばそのテストに書いてある状態になるということです。


#7

これは僕も発表前に「本当に大丈夫なんだっけ?」となったので手元で確認済みです。FactoryBotのランダム値を例えば発表資料にあるようにstatus { %i[ready doing dome].sample }とした場合でも–seedで結果を固定できます。

なぜ固定できるかというと、rails g rspec:installしたときに生成されるconfig/spec_helper.rbに次のような設定がデフォルトで生成され、srandが–seedで指定された値になるからです。

Kernel.srand config.seed

↑が消されたりコメントアウトされていたりすると固定できないですが、ここをいじる人はきっとそれほどいないはず…。


#8

このメリットはすごく分かります。
ただ、稀に落ちるテストに遭遇した時に、人はリトライせずに前提条件をちゃんと直すのか…というところが少し気になるところです。
みんな気軽にリトライしないで…と。

たしかに!気づきませんでした👀
そうなると、個人的に一番の問題が無くなったので、ランダム値で良さそうと思いました :game_die:


#9

めっちゃわかる。「テストがコケたときに原因を探らずにとりあえず再実行」みたいなのをどうにかしないといけないですね…