Journey

技術に関することと覚書と

Active Storageをtask内で使おうとしたらハングした

タイトルのとおりですが

class User
  has_many_attached :files
end

みたいなモデルがあったときにあるタスク内で

user = User.first

%w[foo bar].each do |word|
  Tempfile.open(word) do |file|
    file.write(word)
    file.rewind
    user.files.attach(
      io: file,
      filename: word
    )
  end
end

のような複数ファイルをループ内で保存していくみたいな事しようとすると見事にログがピタッと止まりました。 はっきりとした原因はまだわかってないですが、どうやら ActiveStorage::AnalyzeJob がジョブのキューに積まれて、そのまま処理を続けようとするとハングしてしまうようです。

なのでそもそもここは非同期処理である必要がないので

ActiveSupport.on_load(:active_job) do
  self.queue_adapter = :inline
end

をループの前に書くことでちょっと無理やりですが同期処理に切り替えたところうまくいきました。

こういう問題の原因をぱっと予測できるエンジニアになりたい。

ちなみに参考リンクはseedのときも同じ問題が起こっていたみたいなのでそこからコード引っ張ってきました

参考

github.com

E2Eテストについて思っていること

なんかいろいろE2Eテストで思ってることがあるのでいくつか書こうかなと思います。

あと基本的にはこの記事はE2Eテストに重きをおいて、ユニットテスト、インテグレーションテストなどをあまり重要視していないコードに対するものです。

あとこの記事はあくまで、

「E2Eテストいらないよね。全部ユニットテストでいいよね。」ではありません。

簡単に言えばE2Eテストでがんばりすぎてない?っていう記事です。

E2Eテストはコストが高い

E2Eテストはコストが高いです。コストとは簡単に言えばテストの実行時間のことと捉えてもらって大丈夫です。E2Eテストを大量に書けば当然テスト実行の総実行時間は増えていきます。

実行時間の長いテストはそれだけで罪

長いテストはその存在自体が罪です。

ないよりましじゃないの?という意見もわかりますが、長いテストは他のテストの価値まで低下させる危険性をはらんでいます。例えばテストの実行に20分かかるとします。テストを実行するということはそれすなわちフィードバックを得るということです。つまり1度のフィードバックを得るのに20分もの時間が毎回かかることになります。

さらに言えば20分もかかるテストを何回も実行したいと思いますか?気軽に実行できてすぐ終了するテストなら何回実行してもそれは開発者の作業を止めることはないです。

ここでそのテストだけ実行しなければいいじゃないかという意見が出てくると思います。しかし、そのテストはE2Eです。つまりモデルに、コントローラーに、サービスクラスに、ビューに依存しているのです。この依存関係のうちのどれかが変更されたのであれば、当然該当するE2Eテストも走らせるべきとなってしまいます。

テストピラミッド

テストピラミッドというものがあります。

f:id:ippachi1218:20190729184435p:plain
テストピラミッド

この図はまさに上で説明していたことを表しています。ピラミッドの上に行けば行くほどにコストが高くなります。なのでこのピラミッドの形のようにE2Eテストでカバーすべきでないところはどんどんと下の層に移動してテスト全体のコストを下げるべきです。繰り返しになりますが実行時間の長さというのはそれだけで罪です。逆に言えば実行時間が短いに越したことはないのです。テストの実行時間にはこだわりましょう。

E2Eテストは与えてくれる情報が少ない

これはライブラリにもよるかもしれませんが例えばE2Eテストで、

ブログの記事を投稿したら、ブログの記事一覧に新しい記事が表示される

みたいなことをテストしようと思います。では仮にこのテストが成功しなかったとしましょう。さてこれはどこがまずかったのでしょうか?

モデルの保存のメソッドが壊れていたのでしょうか?

コントローラーで保存するメソッドが呼ばれなかったのでしょうか?

ビューの一覧表示のロジックがまずかったのでしょうか?

このE2Eテストからはどれがまずかったのかは特定できません。どれかがおかしいのでしょうが、E2Eテストが与えてくれた情報は

ブログの記事一覧に新しい記事が表示されていなかった

ということだけです。

E2Eテストは外から見た振る舞いしかテストできない

上の例からも分かる通りE2Eテストは基本的には外部から見たテストです。つまり私達がブラウザから得られる情報以上のことは得られないのです。例えば私達がツイートをしようとして "ツイートに失敗しました" というメッセージが表示されたとします。ここから私達が得られる情報は、おそらくツイートに失敗したのだろうということだけなのです。原因は全く予測できません。

ユニットテストは内部的な動作をテストできる

ユニットテストは内部的な動作をテストできます。例えば、あるモデルの save メソッドが呼ばれたかどうかのようなことをテストすることができます。つまりテストのスコープを非常に狭めることでテストがコケたことによってどこに問題がありそうかがすぐに分かります。

三角形を守ってみよう

結局なにが言いたいのかというとE2Eテストをベースに考えるのをやめてみようということです。

三角形を意識して、ユニットテスト、インテグレーションテストを増やせば、不必要なE2Eテストが消え、テストの実行時間が短くなり、開発が効率化します。なのでもしE2Eテストが増えてきたなと思ったら三角形を思い出してみてください

おわり

ザーッと書いたのであまりまとまってはいないですが、E2Eテストを重きとしているコード、文化に思ったことを書きました。繰り返しになりますが、E2Eテストも書くべきです。ただE2Eテストを書いていればユニットテスト、インテグレーションテストを疎かにしていいという意識を持ってしまいそうになったら注意してほしいです。