Capybaraの不安定なシステムテストの症状とその対策

概要 

Capybaraを使った時の不安定なシステムテストの症状とその対策について下記にまとめます。

環境

rails: 6.0
capybara

ランダムなテストデータによって落ちるテスト

症状

ランダムにテストデータが作成されている時に発生します。

例えば、テストユーザかどうかを示すフラグが1/2の確率でランダムに作成されており、ユーザが画面から出たり消えたりしてテストが落ちます。

f:id:TAMA_CHAN:20201021105616j:plain

テストユーザの例では、1/2の確率なので早期発見が可能ですがテストデータがより複雑なものでありデータ同士が関係性を持つものであれば、確率が減少して発見されにくくなります。

対処法

ランダムなテストデータは作らないようにしましょう。
また、不可解な落ち方をしたテストに対しては、テストの実装を見直すと共にテストデータも見直してみてください。 不定なテストデータにより落ちている可能性があります。

未ソートの配列によって落ちるテスト

症状

未ソートの配列によって落ちるテストは、バック側からソート処理を実行していない配列構造のデータが返却され テスト実装者がそれをソート済みだと誤認識した際に生じます。

配列構造にしたデータ
[{id 1, name: A}, {id: 2, name: B}, {id: 3, name: C}]

順番が定まっていないのでプルリクエストを出す時には運悪くテストが通ってしまいマージされてしまいます。
さらに運が悪い時には、何ヶ月が立った頃に急に配列の順番が変わりテストが落ち泣きながら直していく羽目になります。

対処法

そこまでレコード数が大量でなく処理速度に問題が無いのであればorderをかけてから返却しましょう。
それだけで時限爆弾のようなこの症状を抑えることが出来ます。

異常に長い時間稼働するテスト

症状

簡単なテストのはずなのに異常に長い時間テストが稼働している事があります。
例えば、page.allなどで存在しないDom要素をWaitし続けるとこの現象が発生します。
以下のようなプログラムを発見したら確認してみてください。

page.allでやらかしている例
# page.all(" .xxxx xxxx")でwaitがかかっておりn秒待つが
# domsの結果は0なのでeachの中に入らない
# 時間だけが経過する
doms = page.all(" .xxxx xxxx") 
doms.each do |e|
  assert_equal true, e.disabled?
end

doms = page.all(" .xxxx xxxx") 
doms.each do |e|
  assert_equal true, e.disabled?
end

doms = page.all(" .xxxx xxxx") 
doms.each do |e|
  assert_equal true, e.disabled?
end

...

私の場合、過去に25分ほどで終わるはずのテストが40分ほどに膨れ上がっていた事がありました。
開発効率の悪化につながるので、異常に長い時間稼働しているテストは修正した方がいいでしょう。

対処法

テストに対してn秒経過したら警告をログに出すなど早期発見できる仕組みを取り入れましょう。
例えば、20秒経過したら黄色い文字でコンソールにSlow Warningが出るようにする等。
警告が出たら例で挙げたようなコードが混ざっていないかを確認し修正してください。

サーバ側でのみ落ちる事があるテスト

症状

サーバー側でテストを稼働させた場合に
[ 自分が回しているテスト+チームメンバーが回しているテスト] となりローカルのPCで回すより負荷がかかります。
その負荷によってシステムテストが落ちてしまう事があります。
ローカルで普通に検証しても全く落ちないので注意が必要です。

対処法

stressコマンドなどの高負荷がかけられるコマンド・ツールを用意しローカルでも高負荷の状態で検証できるようにしましょう。
高負荷の状態で落ちたテストは「落ちやすいテスト」と認定し、片っ端から直していきます。

また、負荷がかかって落ちた時にシステムテストにリトライ処理を導入するのも手です。
Net::ReadTimeout,やCapybara::ElementNotFoundなどの特定のエラーが出た場合のみリトライさせましょう。
以下のgemが便利です。

github.com

アニメーションが豊富なUIに対するテスト

症状

アニメーションが豊富なUIを使用しているページのテストは他のページのテストより落ちやすくなる可能性が上がります。
また、サーバ側で実行すると負荷がかかりより落ちやすくなります。

ふわっと吹き出しが遅れてやってくるアニメーションを持つセレクトボックスや
押下して展開すると現在時間に設定されたカレンダーがふわっと展開されるクリック式のDatePickerが該当します。

対処法

アニメーションの終わりを待ってからdomの操作を開始しましょう。
適切に待つ処理をシステムテスト内に実装することで落ちる事を回避させます。
処理が膨れ上がる可能性があるので、ライブラリごとに独自の操作クラスを定義すると 他のテストでも使いまわせてスッキリします。
私は、この方法でelement-uiが入ったシステムテストを安定させました。

原因不明で不安定なテスト

症状

原因不明で不安定でありサーバーでもローカルでも稀に落ちるテストです。
原因の解明に半日ほど費やすのも珍しくありません。
すごい疲れるやつです。

対処法

何をやっても落ちやすいままになっているテストに対しては、とりあえずpage.execute_scriptを使用してみてください。
page.findやpage.allなどの使用を一旦止めましょう。
Capybaraのデフォルト設定が外れる事や自分でDOMの操作やWait処理を書くことで、安定する場合があります。