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

**URL:** <https://discourse.clean-rails.org/t/clean-test-code-revised/259>\
**Category:** Uncategorized\
**Created:** [2019 年 3 月 24 日午後 3:22 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259 "2019-03-24T15:22:32Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2019 年 3 月 24 日午後 3:22 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/1 "2019-03-24T15:22:33Z")

</div>

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

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

[Clean Test Code Revised - Speaker Deck](https://speakerdeck.com/willnet/clean-test-code-revised)

---

<div class="post-metadata">

**Author:** ![sinsoku](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/sinsoku/32/887_2.png) [@sinsoku](https://discourse.clean-rails.org/u/sinsoku)\
**Post date:** [2019 年 3 月 24 日午後 3:33 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/2 "2019-03-24T15:33:46Z")

</div>

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

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

---

<div class="post-metadata">

**Author:** ![fujimura](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/fujimura/32/898_2.png) [@fujimura](https://discourse.clean-rails.org/u/fujimura)\
**Post date:** [2019 年 3 月 24 日午後 4:41 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/3 "2019-03-24T16:41:13Z")

</div>

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

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

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2019 年 3 月 25 日午前 3:29 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/4 "2019-03-25T03:29:31Z")

</div>

> 差分は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.](https://github.com/stympy/faker#ensuring-unique-values)

---

<div class="post-metadata">

**Author:** ![sinsoku](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/sinsoku/32/887_2.png) [@sinsoku](https://discourse.clean-rails.org/u/sinsoku)\
**Post date:** [2019 年 3 月 25 日午前 4:21 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/5 "2019-03-25T04:21:43Z")

</div>

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

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

## Flaky Testの例

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

```ruby
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に使うのは 🙅‍♀️ 」という話な気がしてきました。

## やっぱりFlaky Testで困る

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

---

<div class="post-metadata">

**Author:** ![bluerabbit](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/bluerabbit/32/889_2.png) [@bluerabbit](https://discourse.clean-rails.org/u/bluerabbit)\
**Post date:** [2019 年 3 月 25 日午前 5:23 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/6 "2019-03-25T05:23:05Z")

</div>

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

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

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

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

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

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

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

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2019 年 3 月 25 日午後 1:53 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/7 "2019-03-25T13:53:34Z")

</div>

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

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

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

```auto
Kernel.srand config.seed

```

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

---

<div class="post-metadata">

**Author:** ![sinsoku](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/sinsoku/32/887_2.png) [@sinsoku](https://discourse.clean-rails.org/u/sinsoku)\
**Post date:** [2019 年 3 月 25 日午後 2:31 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/8 "2019-03-25T14:31:01Z")

</div>

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

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

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

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

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2019 年 3 月 27 日午前 12:06 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/9 "2019-03-27T00:06:04Z")

</div>

> [@sinsoku](#):
>
> ただ、稀に落ちるテストに遭遇した時に、人はリトライせずに前提条件をちゃんと直すのか…というところが少し気になるところです。  
> みんな気軽にリトライしないで…と。

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

---

<div class="post-metadata">

**Author:** ![JunichiIto](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/junichiito/32/885_2.png) [@JunichiIto](https://discourse.clean-rails.org/u/JunichiIto)\
**Post date:** [2019 年 6 月 4 日午前 1:00 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/10 "2019-06-04T01:00:27Z")

</div>

[https://blog.willnet.in/entry/2019/03/27/092642](https://blog.willnet.in/entry/2019/03/27/092642) にコメントを書こうとしましたが、こちらの存在に気づいたのでこっちにコメントしておきます↓

スライドと動画を改めて拝見させてもらいました。  
「そうそう、そういうケース、よくあるよね〜」という「RSpecあるある」を見事にピックアップしていて素晴らしい内容だと思いました！

ところで、スライドを見ていて気づいたのですが、64枚目の`let!(:sender)`はもしかすると`let!(:todo)`が正ではないでしょうか？  
スライドの修正漏れか何かかな？と思ったので、念のためご報告しておきます。

あとは、個人的に僕はこう考えるかな〜と思った点がいくつかあるので以下にメモしておきます。  
あくまで個人の一意見ですが、ご参考までに〜。

56、57枚目  
エディタをスクロールしている間に脳内メモリが揮発しがち  
この例はうまい解決策がない

- テスト全体で当たり前に使われるレコード（userを束ねるcompanyとか）や当たり前の前処理（単純なログインとか）であれば、ファイルの冒頭にあってもあまり脳に負担をかけないはず
- スクロールしなくても、エディタ上で上下分割しながらテストコードを読むことはできる
- あまりにも脳に負担がかかるようであれば、ネストをやめるのはアリ
- もしくはspecファイルそのものをわける（user\_spec.rbと、user\_for\_special\_case\_spec.rbのように）のも有効

59枚目  
別ファイルの定義を行ったり来たりしている間に脳内メモリが揮発しがち

- ヘルパーメソッドにいい名前が付けられていれば（うまく抽象化できていれば）比較的脳内の負担は少ない（1回実装を見たら、あとは見なくて済む）
- もちろん、他のファイルで再利用しなければ、同じファイルに定義した方がよい

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2019 年 6 月 5 日午前 7:19 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/11 "2019-06-05T07:19:41Z")

</div>

> [@JunichiIto](#):
>
> ところで、スライドを見ていて気づいたのですが、64枚目の `let!(:sender)` はもしかすると `let!(:todo)` が正ではないでしょうか？  
> スライドの修正漏れか何かかな？と思ったので、念のためご報告しておきます

報告ありがとうございますー。スライド修正しました！

> [@JunichiIto](#):
>
> 56、57枚目  
> エディタをスクロールしている間に脳内メモリが揮発しがち  
> この例はうまい解決策がない
> 
> - テスト全体で当たり前に使われるレコード（userを束ねるcompanyとか）や当たり前の前処理（単純なログインとか）であれば、ファイルの冒頭にあってもあまり脳に負担をかけないはず
> - スクロールしなくても、エディタ上で上下分割しながらテストコードを読むことはできる
> - あまりにも脳に負担がかかるようであれば、ネストをやめるのはアリ
> - もしくはspecファイルそのものをわける（user\_spec.rbと、user\_for\_special\_case\_spec.rbのように）のも有効
> 
> 59枚目  
> 別ファイルの定義を行ったり来たりしている間に脳内メモリが揮発しがち
> 
> - ヘルパーメソッドにいい名前が付けられていれば（うまく抽象化できていれば）比較的脳内の負担は少ない（1回実装を見たら、あとは見なくて済む）
> - もちろん、他のファイルで再利用しなければ、同じファイルに定義した方がよい

ご意見に同意です(( ⁰⊖⁰)/)。ちゃんとした書き方で書いていればそこまで負担ではないのですが、ときどきお仕事で極端な書き方をしている(例: ネストがめちゃめちゃ深い、メソッドの名前付けが下手で何をしているのかすぐにわからないなど)のを見かけるのでした。テストコードで冗長さを減らして簡潔に書こうとすると可読性が下がることがおおいので、よいバランスで書いていきたいですね…。

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2023 年 11 月 22 日午前 9:08 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/12 "2023-11-22T09:08:15Z")

</div>

thoughtbot社はlet(let!)でもインスタンス変数でもなくて、都度ヘルパメソッドを定義しようという方針で、これは新しい派閥だな〜と感じました

[guides/testing-rspec at main · thoughtbot/guides](https://github.com/thoughtbot/guides/tree/main/testing-rspec)

> - Avoid `let` (or `let!`) in RSpec. Prefer extracting helper methods, but do not re-implement the functionality of `let`. [Example](https://github.com/thoughtbot/guides/blob/main/testing-rspec/avoid_let_spec.rb).

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2023 年 11 月 24 日午前 10:01 UTC](https://discourse.clean-rails.org/t/clean-test-code-revised/259/13 "2023-11-24T10:01:16Z")

</div>

関連記事 (ruby-jp より)

- [Rails: RSpecが好きでないことを思い出したテスト（翻訳）｜TechRacho by BPS株式会社](https://techracho.bpsinc.jp/hachi8833/2023_02_13/126094)
- [RSpec では context 間の違いを表現するときにのみ let を使う - id:onk のはてなブログ](https://onk.hatenablog.jp/entry/2023/02/26/221616)
