Slide 1

Slide 1 text

私のRSpecの書き方 2024.03.22 Fri SmartHR LT大会 #4 とみた まさひろ SmartHR PdE

Slide 2

Slide 2 text

自己紹介 とみたまさひろ https://twitter.com/tmtms https://bsky.app/profile/tmtms.net https://blog.tmtms.net 長野県在住

Slide 3

Slide 3 text

私のRSpecの書き方 ❌ RSpecの正しい書き方 ❌ RSpecはこう書くべき ⭕ 自分はこう書いてる 異論は認める

Slide 4

Slide 4 text

テストファーストではない ある程度実装ができてからテストを書き始める 「実装よりも先にテストを書く」というのは古き良き 「実装 よりも先にドキュメントを書く」と同じような感じ に思えてまどろっこしい

Slide 5

Slide 5 text

まず通常の振る舞いを書く describe HogeClass do
 it '〇〇となる' do
 expect(...).to eq hoge
 end
 end


Slide 6

Slide 6 text

ひとつの it に複数の expect を書いてもいい describe HogeClass do
 it '〇〇となる' do
 expect(...).to eq hoge expect(...).to eq hoge expect(...).to eq hoge
 end
 end
 その方が速いしね

Slide 7

Slide 7 text

expect に依存関係なければ aggregate_failures で括ると便利 describe HogeClass do
 it '〇〇となる' do
 aggregate_failures do
 expect(...).to eq hoge # ここで失敗しても
 expect(...).to eq hoge # これ以降も評価してくれる
 expect(...).to eq hoge
 end
 end
 end


Slide 8

Slide 8 text

異常系を追加 describe HogeClass do
 it '〇〇となる' do
 ...
 end
 it '〇〇の場合エラーとなる' do
 expect { ... }.to raise_error ErrorClass, "message"
 end
 end
 「〇〇の場合」の結果がひとつなら context は書かない

Slide 9

Slide 9 text

複数の it を含む場合に context describe HogeClass do
 context '〇〇の場合' do
 it '□□となる' do
 ...
 end
 it '△△となる' do
 ...
 end
 end
 end


Slide 10

Slide 10 text

複数の it で前処理が共通なら before に切り出してもいい describe HogeClass do
 context '〇〇の場合' do
 before { ... }
 it '□□となる' do
 ...
 end
 it '△△となる' do
 ...
 end
 end
 end
 別に切り出さなくてもいいけど、同じなのか異なるのかわかりやす くなってた方がいい

Slide 11

Slide 11 text

無理に共通化しない before を共通化したせいで before の中で複雑な条 件分岐とかするんだったら共通化しない方がマシ

Slide 12

Slide 12 text

let はテストケース間で条件を変えたい場合に使う 1回しか参照されてなくて変更もされない let は意味あるのかなぁ let(:hoge) { ... }
 it '...' do
 nanika(hoge)
 end


Slide 13

Slide 13 text

実はインスタンス変数でいいのでは しかし Rubocop (RSpec/InstanceVariable) に怒られるのだった ぐぬぬ… before { @hoge = ... }
 it '...' do
 nanika(@hoge)
 end


Slide 14

Slide 14 text

subject はあまり使わない 「subject の中で何やってるんだっけ?」って上の方を見にいかないといけない のがイマイチ 使うとしても subject そのものではなく副作用を期待するようなのは書かない expect(subject).to ... # OK
 expect(subject.hoge).to ... # OK
 
 subject
 expect(hoge).to eq ... # NG
 


Slide 15

Slide 15 text

described_class は使わない 「described_class って何だっけ?」って上の方を見にいかないといけ ないのがイマイチ しかし Rubocop (RSpec/DescribedClass) に怒られるのだった… ぐぬぬ… describe Hoge do
 before { Hoge.new } # OK
 before { described_class.new } # NG
 end


Slide 16

Slide 16 text

shared_examples は使わない 「この shared_examples ってなんだっけ?」って他 のところを見にいかないといけないのがイマイチ

Slide 17

Slide 17 text

マッチャむずい 多くて覚えられない be, eq, eql, equal, be ==, be >, be >=, be <=, be <, be_between, match, be_within, start_within().of(), start_with, end_with, be_instance_of, be_kind_of, respond_to, be_truthy, be_falsey, be_nil, exist, include, match_array, contain_exactly, cover, change().from().to(), change().by(), change().by_at_least(), change().by_at_most(), satisfy, output().to_stdout, output().to_stderr, raise_error, throw_symbol, yield_control, yield_with_no_args, yield_with_args, yield_successive_args, ... あとむりやり英語っぽくしててちょっとキモい

Slide 18

Slide 18 text

Ruby で書けばいいのでは? rspec-power_assert 便利 it '...' do
 is_asserted_by { hoge.upcase.reverse == 'ABCDEFG' } end


Slide 19

Slide 19 text

エラー時に途中の状態を表示してくれる 最近プライベートで書くコードはもっぱら power_assert 
 Failure/Error: is_asserted_by { hoge.upcase.reverse == 'ABCDEFG' }
 
 is_asserted_by { hoge.upcase.reverse == 'ABCDEFG' }
 | | | |
 | | | false
 | | "GFEDCBA"
 | "ABCDEFG"
 "abcdefg"


Slide 20

Slide 20 text

便利なマッチャもある たとえば include は入れ子でも使えて便利 hash = {
 id: 999,
 hoge: {
 id: 999,
 fuga: 123
 }
 }
 
 expect(hash).to include(hoge: include(fuga: 123))


Slide 21

Slide 21 text

でもまあ今ならパターンマッチ使えばいいかも hash = {
 id: 999,
 hoge: {
 id: 999,
 fuga: 123
 }
 }
 
 is_asserted_by { hash in {hoge: {fuga: 123}} }


Slide 22

Slide 22 text

おわり