テストにおける環境変数の一時的な上書きを共通ヘルパーに集約
Pumaのテストコードで重複していた環境変数の一時保存・復元パターンを、with_temp_env ヘルパーメソッドに集約しました。これにより、テストコード間の一貫性が向上し、環境変数を扱う新規テストの実装が簡潔になります。
背景
Pumaのテストコード内には、環境変数を一時的に設定して ensure ブロックで元に戻すパターンが複数箇所で重複していました。#3875 で報告された主な重複箇所は以下の3つです:
-
test/test_log_writer.rb:ENV["PUMA_DEBUG"]の保存・復元 -
test/test_error_logger.rb:with_debug_modeヘルパーでのENV["PUMA_DEBUG"]ラッピング -
test/test_cli.rb:APP_ENV/RAILS_ENVの設定とensureでのクリーンアップ
これらは同じ目的のコードが異なる形で実装されており、新しいテストを追加する際にどのパターンに従うべきか不明瞭でした。本PRは、このパターンを共通化して保守性を向上させています。
技術的な変更
with_temp_env メソッドの追加
test/helper.rb に環境変数の一時的な設定と復元を行う共通ヘルパーが追加されました。このメソッドは2つのパラメータを受け取ります:
def with_temp_env(temp_env, del_env={}, &block)
original_env = {}
temp_env.transform_keys(&:to_s).each do |k, v|
original_env[k], ENV[k] = ENV[k], v
end
del_env.transform_keys(&:to_s).each { |k, v| ENV[k] = v }
yield
ensure
# Restore original values
original_env.each { |k, v| ENV[k] = v }
# Remove keys that were added via del_env
del_env.transform_keys(&:to_s).each { |k, _| ENV.delete(k) }
end
第1引数 temp_env は既存の環境変数を一時的に上書きし、ブロック実行後に元の値へ復元します。第2引数 del_env は新しい環境変数を追加し、ブロック実行後に削除します。この2つのパラメータにより、「既存の値を保存して復元」と「一時的に追加して削除」の両方のパターンに対応しています。
既存テストでの適用例
test/test_cli.rb の test_environment_rails_env では、以下のように書き換えられました:
変更前:
def test_environment_rails_env
ENV.delete 'RACK_ENV'
ENV['RAILS_ENV'] = @environment
cli = Puma::CLI.new []
conf = cli.instance_variable_get(:@conf)
conf.clamp
assert_equal @environment, conf.environment
ensure
ENV.delete 'RAILS_ENV'
end
変更後:
def test_environment_rails_env
ENV.delete 'RACK_ENV'
with_temp_env({}, { "RAILS_ENV": @environment }) do
cli = Puma::CLI.new []
conf = cli.instance_variable_get(:@conf)
conf.clamp
assert_equal @environment, conf.environment
end
end
del_env パラメータに RAILS_ENV を渡すことで、ブロック終了時に自動的に削除されます。ensure ブロックの記述が不要になり、テストのロジックに集中できます。
test/test_error_logger.rb では、ローカルメソッド with_debug_mode が削除され、共通の with_temp_env に置き換えられました:
変更前:
def test_debug_with_debug_mode
with_debug_mode do
_, err = capture_io do
Puma::ErrorLogger.stdio.debug(text: 'non-blank')
end
assert_includes err, '% non-blank'
end
end
private
def with_debug_mode
original_debug, ENV["PUMA_DEBUG"] = ENV["PUMA_DEBUG"], "1"
yield
ensure
ENV["PUMA_DEBUG"] = original_debug
end
変更後:
def test_debug_with_debug_mode
with_temp_env({ "PUMA_DEBUG": "1" }) do
_, err = capture_io do
Puma::ErrorLogger.stdio.debug(text: 'non-blank')
end
assert_includes err, '% non-blank'
end
end
テストクラスごとに定義されていたプライベートメソッドが不要になり、コードの重複が解消されています。
ヘルパーメソッドのテスト
test/test_helper.rb に with_temp_env 自体の動作を検証するテストが追加されました:
def test_with_temp_env
original_puma_debug_env = ENV["PUMA_DEBUG"]
with_temp_env({ "PUMA_DEBUG": "1" }, { "APP_ENV" => "test" }) do
refute_equal original_puma_debug_env, ENV["PUMA_DEBUG"]
assert_equal "1", ENV["PUMA_DEBUG"]
assert_equal "test", ENV["APP_ENV"]
end
assert_operator original_puma_debug_env, :==, ENV["PUMA_DEBUG"]
refute ENV.key?("APP_ENV"), "Expected the APP_ENV key to be removed"
end
ブロック内での値の上書きと、ブロック終了後の復元・削除が正しく動作することを確認しています。ヘルパーメソッド自体の品質を保証するテストケースです。
設計判断
2つのパラメータによる使い分け
temp_env と del_env という2つのパラメータを設けることで、「既存の値を一時的に変更」と「新しい値を一時的に追加」の両方のユースケースに対応しています。test_environment_app_env では両方のパターンが混在しています:
with_temp_env(
{ "RACK_ENV": @environment },
{ "RAILS_ENV": @environment, "APP_ENV": "test" }
) do
cli = Puma::CLI.new []
conf = cli.instance_variable_get(:@conf)
conf.clamp
assert_equal 'test', conf.environment
end
RACK_ENV は元の値を保存して後で復元し、RAILS_ENV と APP_ENV はテスト後に削除されます。この設計により、単一のヘルパーで複雑な環境変数の操作パターンにも対応できます。
Symbol/String両対応
transform_keys(&:to_s) により、ハッシュのキーがSymbolでもStringでも統一的に処理されます。テストコードでは { "PUMA_DEBUG": "1" } のようにSymbolを使う例と { "APP_ENV" => "test" } のようにStringを使う例の両方が存在しますが、ヘルパー内で統一されるため呼び出し側は自由に選択できます。
まとめ
本PRは、テストコード間で重複していた環境変数の保存・復元パターンを with_temp_env ヘルパーに集約し、コードの一貫性と保守性を向上させました。2つのパラメータによる使い分けとキーの型変換により、既存の多様なパターンに対応しつつ、新しいテストでの利用も簡潔にしています。ヘルパー自体のテストケースも追加され、安全な環境変数操作が保証されています。