SlideShare a Scribd company logo
1 of 47
Download to read offline
Randomly Failing Specs
〜稀に落ちるテストとの戦い方〜
Rails Developers Meetup 2017
2017/12/09(土) TECH PLAY SHIBUYA
自己紹介
● 名前: 正徳 巧
● Twitter: 神速(@sinsoku_listy)
● GitHub: sinsoku (@sinsoku)
● 所属: 株式会社grooves
の開発を担当
@sinsoku_listy
@sinsoku
最近Railsの同人誌を書きました
タイトル: Clean Code for Rails
イベント: 技術書典3
頒布価格: 1,000円
イラスト: Ixy
(可愛いのは表紙・裏表紙だけです)
在庫を持ってきているので、興味ある人はぜひ声かけて!!
稀に落ちるテストとは
基本的に成功するが、CIでテストを実行し続けていると
稀に失敗するテスト。
たいていは「リトライ」すると直る。
原因は分かり辛く、再現させるのが難しい。
リトライで誤魔化し、原因を調査するのは後回しになりがち
そんな稀に落ちるテストとの
戦い方を紹介します
今日話すこと
● テストが落ちる事例の紹介
○ ランダム値を使うテスト
○ 実行順序によって落ちるテスト
○ JavaScriptを使うFeature Spec
● 基本的な戦い方
テストが落ちる事例の紹介
ランダム値を使うテスト
ランダムな数字やFaker、現在日時を扱うテストは気をつけないと
稀に落ちる可能性があります。
特に下記の2つに注意してください。
● expectでランダム値を使用する
● uniquness制約の属性にランダム値を使う
事例を紹介します。
事例1: ソート順を指定した後に削除するテスト
feature "xxx" do
# ランダムな1桁の数字
let(:old_sort_order) { Faker::Number.number(1) }
scenario "xxx" do
# ページを表示し、要素を削除する処理
expect(page).to_not have_content old_sort_order
end
end
画面内の"+1"の文字があり、1/10で失敗
事例2: factory_bot + Faker + ユニーク制約
Fakerの値は意外と被る
irb> require 'faker'
irb> 100.times.map { Faker::Lorem.word }.uniq.size
#=> 67
irb> 1000.times.map { Faker::Internet.user_name }.uniq.size
#=> 960
irb> 10000.times.map { Faker::Internet.email }.uniq.size
#=> 9999
Fakerのuniqueメソッドを使う
引用: https://github.com/stympy/faker/tree/v1.8.5#ensuring-unique-values
factory_botのsequenceを使う
実行順序によって落ちるテスト
事例3: Globalな値を上書きしているテスト
RSpec.describe "new feature", type: :request do
context "on production env" do
before { Rails.env = "production" }
it "not displays" do
get "/new_feature"
expect(response).to have_http_status(:not_found)
end
end
end
production になってしまうと、他のテストで意図しないエラーが
起こる可能性がある
事例3: 修正方法
RSpec.describe "new feature", type: :request do
context "on production env" do
before { allow(Rails.env).to receive(:production?) { true } }
it "not displays" do
get "/new_feature"
expect(response).to have_http_status(:not_found)
end
end
end
代わりにstubを使う。stubは別のテストに影響しない
事例4: RSpecのstub_constの罠
引用: https://github.com/rspec/rspec-mocks/issues/1079
事例4: RSpecのstub_constの罠
引用: https://github.com/rspec/rspec-mocks/issues/1079
Failure/Error: Model.new
NoMethodError:
undefined method `new' for #<Module:0x000000089a8be0>
事例4: RSpecのstub_constの罠
stub_constは指定した定数が未定義の場合、新しいModuleを作
成します。
事例4: 修正方法
module StubConstAutoLoader
def stub_const(constant_name, value, options = {})
constant_name.deconstantize.safe_constantize
super
end
end
RSpec::Mocks::ExampleMethods.prepend StubConstAutoLoader
JavaScriptを使うFeature Spec
...の前に Capybara の基本
Capybara の基本的な動き
RSpec.feature "xxx", type: :feature do
after { DatabaseRewinder.clean }
scenario do
visit "/"
click_link "hello"
expect(page).to have_content "Hello"
end
end
Capybara(rack_test)の仕組み
visit expect clean
app.call(env)
click_link
app.call(env)
簡単ですね
次はJavaScriptを使う場合
JavaScriptの処理がある場合の動き
RSpec.feature "xxx", type: :feature, js: true do
after { DatabaseRewinder.clean }
scenario do
visit "/"
click_link "hello"
expect(page).to have_content "Hello"
end
end
JavaScriptの処理がある場合の動き
visit expect clean
boot
click_link
req
(別プロセス)
GET
click
GET
(別スレッド)
res res
Ajaxを入れます
Turobolinks(Ajax)がある場合の動き
visit expect clean
boot
click_link
req
(別プロセス)
GET
click
(別スレッド)
res res
turbolinks
要素が現れるまで待つ
事例5: 稀に起きるActiveRecord::NotFound
事例5: 稀に起きるActiveRecord::NotFound
RSpec.feature "xxx", type: :feature, js: true do
after { DatabaseRewinder.clean }
scenario do
visit "/" # 初期ページに "Hello" も文言が存在する場合
click_link "hello"
expect(page).to have_content "Hello"
end
end
事例5: 稀に起きるActiveRecord::NotFound
visit expect clean
boot
click_link
req
(別プロセス)
GET
click
(別スレッド)
res res
turbolinks
遷移前のページで
expect が成功する
事例5: Ajaxの後は必ずDOMをチェックする
RSpec.feature "xxx", type: :feature, js: true do
after { DatabaseRewinder.clean }
scenario do
visit "/" # 初期ページにも "Hello" が存在する
click_link "hello"
expect(page).to have_content "Hello#show"
expect(page).to have_content "Hello"
end
end
sleep はできるだけ使わない。テストが遅くなります。
基本的な戦い方
基本的な戦い方
● seed値を指定してテストを実行する
● たくさんテストを実行してみる
● test.logを眺める
● 推測してsleepやprintを入れる
● 再現したら、原因を直す
RSpecのseed値
RSpecのテスト実行順序をランダムにしていた場合、実行順序が
原因になっていることがあります。
seed値を指定すると、同じ実行順序を再現できます。
$ rspec --seed SEED spec/user_spec.rb
たくさんテストを実行してみる
$ for n in {1..20};
do rspec spec/user_spec.rb:100 || break; done
ためしに20回ほど実行してみると再現することがあります。
test.logを眺める
ログファイルを眺めていると、意図しないリクエスト、SQLクエリに
気づくことがあります。
$ tail -f log/test.log
まとめ
● ランダム値は気をつけて使用する
● Globalな値は代入じゃなくてstubを使う
● Capybaraの気持ちを理解する
○ Ajaxの後は必ずDOMをチェックする
sleepとrspec-retryが無くても動くテストを書きましょう!
おまけ: AjaxでDOMの更新が起きない場合
これは Capybara では対応できません。
このケースに対応するため GhostPictures という gem を作成
中です。(すみません、間に合いませんでした)
https://github.com/sinsoku/ghost_pictures

More Related Content

More from sinsoku listy

自己修復的なインフラ -Self-Healing Infrastructure-
自己修復的なインフラ -Self-Healing Infrastructure-自己修復的なインフラ -Self-Healing Infrastructure-
自己修復的なインフラ -Self-Healing Infrastructure-sinsoku listy
 
毎日gemをアップグレードする生活
毎日gemをアップグレードする生活毎日gemをアップグレードする生活
毎日gemをアップグレードする生活sinsoku listy
 
Rails 5.2: credentials
Rails 5.2: credentialsRails 5.2: credentials
Rails 5.2: credentialssinsoku listy
 
技術的負債とリファクタリング
技術的負債とリファクタリング技術的負債とリファクタリング
技術的負債とリファクタリングsinsoku listy
 
Git 初心者講座 by forkwell
Git 初心者講座 by forkwellGit 初心者講座 by forkwell
Git 初心者講座 by forkwellsinsoku listy
 
ES2015のカバレッジ計測
ES2015のカバレッジ計測ES2015のカバレッジ計測
ES2015のカバレッジ計測sinsoku listy
 
CSSのカバレッジツール
CSSのカバレッジツールCSSのカバレッジツール
CSSのカバレッジツールsinsoku listy
 
本当にあった怖い話 7つの幽霊 7つの成仏
本当にあった怖い話 7つの幽霊 7つの成仏本当にあった怖い話 7つの幽霊 7つの成仏
本当にあった怖い話 7つの幽霊 7つの成仏sinsoku listy
 
Awsでwindowsゲームを動かす
Awsでwindowsゲームを動かすAwsでwindowsゲームを動かす
Awsでwindowsゲームを動かすsinsoku listy
 
Action pack variantsの話
Action pack variantsの話Action pack variantsの話
Action pack variantsの話sinsoku listy
 
LT_Gitのfast fowardと継続的デリバリー
LT_Gitのfast fowardと継続的デリバリーLT_Gitのfast fowardと継続的デリバリー
LT_Gitのfast fowardと継続的デリバリーsinsoku listy
 
バージョン管理とGit
バージョン管理とGitバージョン管理とGit
バージョン管理とGitsinsoku listy
 
Git天空闘技場_ハンズオン
Git天空闘技場_ハンズオンGit天空闘技場_ハンズオン
Git天空闘技場_ハンズオンsinsoku listy
 
20101001 5分でわかるtrac pluginの作り方_slideshare
20101001 5分でわかるtrac pluginの作り方_slideshare20101001 5分でわかるtrac pluginの作り方_slideshare
20101001 5分でわかるtrac pluginの作り方_slidesharesinsoku listy
 

More from sinsoku listy (16)

自己修復的なインフラ -Self-Healing Infrastructure-
自己修復的なインフラ -Self-Healing Infrastructure-自己修復的なインフラ -Self-Healing Infrastructure-
自己修復的なインフラ -Self-Healing Infrastructure-
 
毎日gemをアップグレードする生活
毎日gemをアップグレードする生活毎日gemをアップグレードする生活
毎日gemをアップグレードする生活
 
Rails 5.2: credentials
Rails 5.2: credentialsRails 5.2: credentials
Rails 5.2: credentials
 
技術的負債とリファクタリング
技術的負債とリファクタリング技術的負債とリファクタリング
技術的負債とリファクタリング
 
Git 初心者講座 by forkwell
Git 初心者講座 by forkwellGit 初心者講座 by forkwell
Git 初心者講座 by forkwell
 
Swift on Docker
Swift on DockerSwift on Docker
Swift on Docker
 
ES2015のカバレッジ計測
ES2015のカバレッジ計測ES2015のカバレッジ計測
ES2015のカバレッジ計測
 
CSSのカバレッジツール
CSSのカバレッジツールCSSのカバレッジツール
CSSのカバレッジツール
 
本当にあった怖い話 7つの幽霊 7つの成仏
本当にあった怖い話 7つの幽霊 7つの成仏本当にあった怖い話 7つの幽霊 7つの成仏
本当にあった怖い話 7つの幽霊 7つの成仏
 
Awsでwindowsゲームを動かす
Awsでwindowsゲームを動かすAwsでwindowsゲームを動かす
Awsでwindowsゲームを動かす
 
Action pack variantsの話
Action pack variantsの話Action pack variantsの話
Action pack variantsの話
 
LT_Gitのfast fowardと継続的デリバリー
LT_Gitのfast fowardと継続的デリバリーLT_Gitのfast fowardと継続的デリバリー
LT_Gitのfast fowardと継続的デリバリー
 
バージョン管理とGit
バージョン管理とGitバージョン管理とGit
バージョン管理とGit
 
Git天空闘技場_ハンズオン
Git天空闘技場_ハンズオンGit天空闘技場_ハンズオン
Git天空闘技場_ハンズオン
 
DVCSとGitの基礎
DVCSとGitの基礎DVCSとGitの基礎
DVCSとGitの基礎
 
20101001 5分でわかるtrac pluginの作り方_slideshare
20101001 5分でわかるtrac pluginの作り方_slideshare20101001 5分でわかるtrac pluginの作り方_slideshare
20101001 5分でわかるtrac pluginの作り方_slideshare
 

Randomly Failing Specs