😴
WEBアプリのE2Eテストが不安定(flaky)かを確かめる
背景
E2Eテストは、flakyになりがちですが、masterブランチにマージ後にflakyであることに気が付くことがよくあります。テストを書き上げた時点で気がつけることはできないだろうか?
また、CIでflakyなのに手元だと発生しないことがよくあります。何も考えずに簡単に再現させることはできないだろうか?という問題意識から着想を得ました。
概要
経験上、flakyなE2Eテストは待機ステップが不足していることが多く、偶然うまくいっている場合がほとんどです。このパターンを検出するための方法について述べます。
この方法を使用してテストを複数回実行することで、待機ステップ不足によるflakyテストを効果的に検出できます。
Railsでの実装例
rack middlewareを使用した実装例です。
後述する実装では、エンドポイントごとにsleepする時間がランダムで変わるようになっています。感覚的ですが、数十回くらい実行すれば、flakyではないと言えるでしょう。
使い方は、UNSTABLE=1
をセットしてE2Eテストを実行すると、本機能が有効になります。
また、 UNSTABLE _SEED=xxx
にseedを指定することで、エンドポイントごとに同じ時間sleepします。
# config/environments/test.rb
[...]
if((unstable_seed = ENV['UNSTABLE_SEED'].presence) || ENV['UNSTABLE']).present?
seed = unstable_seed || SecureRandom.hex(10)
require 'random_sleep_middleware'
config.middleware.use(
RandomSleepMiddleware,
seed: seed,
ignore_paths: ["/assets/", "/favicon.ico", "/robots.txt"],
sleep_range: 0..2
)
end
[...]
class RandomSleepMiddleware
def initialize(app, args = {})
@app = app
@seed = args[:seed]
@ignore_paths = args[:ignore_paths] || []
@sleep_range = args[:sleep_range] || (0..2)
Rails.logger.info "[RandomSleepMiddleware] Unstable mode enabled with seed: #{@seed}"
end
def call(env)
return @app.call(env) if @ignore_paths.any? { |path| env['PATH_INFO'].start_with?(path) }
path_seed = Digest::MD5.hexdigest("#{@seed}#{env['PATH_INFO']}").to_i(16)
sleep_time = Random.new(path_seed).rand(@sleep_range)
Rails.logger.info "[RandomSleepMiddleware] #{env['PATH_INFO']} sleep #{sleep_time} seconds"
sleep(sleep_time)
@app.call(env)
end
end
以上。
Discussion