1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【実話】ローカルでは毎回パスするが、CIでは毎回落ちるテストの原因は○○だった

1
Posted at

はじめに:本当にあった怖い話

あなたはチームでプログラム開発をしています。

あなたは開発タスクをアサインされ、開発ブランチで次のようなコードとテストコードを書きました。

lib/bar.rb
class Bar
  def greet
    'Hello!'
  end
end
test/bar_test.rb
class BarTest < Minitest::Test
  def test_greet
    assert_equal 'Hello!', Bar.new.greet
  end
end

ローカルのテストは問題なくパスする ✅

単純な変更なので、他の既存のテストに影響はないと思いますが、念のためプロジェクト全体のテストを実行しておきましょう。

$ rake test
Run options: --seed 55749

# Running:

..

Finished in 0.000233s, 8583.6905 runs/s, 8583.6905 assertions/s.

2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

はい、問題なくすべてのテストがパスしましたね!

でもCI上だと・・・?

では、プルリクエスト(PR)を作りましょう。

このプロジェクトではPRを作るとGitHub Actions(CI)が全テストを実行してくれます。
とはいえ、ローカルで全テストがパスしているので、CIのテストも全部パスするはず・・・あれっ!?

Screenshot 2026-07-27 at 9.24.08.png

なぜかテストが落ちてしまいました。
実行結果は以下のように、既存のテストであるfoo_test.rbが落ちています。

Run options: --seed 36787

# Running:

F.

Finished in 0.000465s, 4303.1019 runs/s, 4303.1019 assertions/s.

  1) Failure:
FooTest#test_calc [test/foo_test.rb:8]:
Expected: 5
  Actual: 1

2 runs, 2 assertions, 1 failures, 0 errors, 0 skips
rake aborted!
Command failed with status (1)

調査、調査!!

もしかすると落ちたのは「フレーキーなテスト(たまに落ちるテスト)」なのでしょうか?

とりあえず、テストが落ちてしまったFooクラスとfoo_test.rbを覗いてみましょう。

lib/foo.rb
class Foo
  def calc(a, b)
    a + b
  end
end
test/foo_test.rb
class FooTest < Minitest::Test
  def test_calc
    assert_equal 5, Foo.new.calc(3, 2)
  end
end

うーん、どこをどう見ても「これで落ちるわけがない」というぐらい、シンプルなテストですよね。。

しかし、CI上ではテストを何度実行しても毎回同じように落ちてしまいます。

Screenshot 2026-07-27 at 9.31.30.png

いったい、なんでー!!??

答え:mainブランチのテストが壊れていたから

実はこれ、他の開発者がうっかりテストが壊れた状態のコード(アプリ側のコードだけ修正して、テストを修正していないPR)をmainにマージしてしまったせいです。

Screenshot 2026-07-27 at 10.53.43.png

Screenshot 2026-07-27 at 10.53.36.png

PRではベースブランチと仮マージされたコードが実行される

ちなみに、このプロジェクトのGitHub Actionsの設定は以下のようになっています。

.github/workflows/test.yml
name: Test

on:
  push:
    branches:
      - main
  # ↓PRが作られるとベースブランチと仮マージしたコードでテストが実行される
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
      - run: rake test

上でコメントを入れたとおり、on: pullrequest: が実行のトリガーになっている場合は、PRのベースブランチと仮マージしたコードでテストが実行されます。

ゆえに、CIでテストを実行した場合は「壊れたmainブランチのテスト」と一緒にテストが実行されるため、CIで実行したときだけ、テストが毎回失敗するのでした!!

実務ではどんな感じだったのか

今回は説明用にごくシンプルなコードを使いました。
これぐらいの規模であれば、ちょっと調査すればすぐに原因がわかったかもしれません。
実際、Claude Codeに原因の調査を依頼したら、原因を突き止めてくれました。

しかし、僕が実際に遭遇したのは実務で使っている巨大なコードベースです。
しかも(あまりよくないですが)フレーキーなテストもちょこちょこあるプロジェクトだったので、僕も最初は「フレーキーなテストが原因だろう」と予想していました。
ですが、ローカルでは毎回パスするので「ローカルだとなかなか同じ落ち方をしないな」と首をかしげていました。

また、Claude Codeに原因調査を依頼しても、ずっと右往左往して、いくら待っても原因にたどり着きませんでした。

このときは結局、最新のmainブランチをgit pullしてローカルでテストを回すと、CIと同じようにテストが落ちることに気付き、そこから「壊れたテストをマージしたPR」の存在に気付いたのでした。

「そもそも壊れたテストをmainブランチにマージするなよ」という話ではあるんですが、前述のとおり、このプロジェクトではフレーキーなテストがいくつかあり、なかなか一発で全テストがパスしない状態でした。
なので、チームメンバーは「今回もたまたま落ちただけだろう」と高をくくってマージしてしまったのかもしれません。

よくないですね!
やはりテストは毎回一発で全パスするようにしないといけない!!

というわけで、このあと時間をかけてフレーキーなテストが落ちないように修正を加えていきました。
これでテストの狼少年化は避けられるはずです!

まとめ(今回の教訓)

というわけで、今回の教訓をまとめると以下のようになります。

  • ローカルでは毎回パスするが、CIでは毎回落ちるテストがある場合、PRのベースブランチのテストが壊れている可能性がある
  • フレーキーなテストを放置してはいけない。放置するとテストが狼少年になってしまい「また落ちてるけど大丈夫だろう」とテストが壊れたままPRをマージしてしまう恐れがある

みなさんも気をつけてくださいね!!

おまけ:サンプルコードはこちら

この記事を書くために作ったサンプルコードはこちらに置いています。
よかったら参考にどうぞ。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?