Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】An Exit Code Cannot Say Whether Anything Happened

2026年09月23日に「Dev.to」が公開したITニュース「An Exit Code Cannot Say Whether Anything Happened」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Exit Code 0は「エラーなし」だが、何も実行されなかった場合も0になる問題がある。これにより、テストが機能せずとも気づかれにくい。didrunツールは、実行・失敗・適切な失敗かを区別する4つの終了コードで、サイレントエラーを防ぎ、見過ごされがちな問題を解決する。

ITニュース解説

システムエンジニアが日々開発を行う中で、プログラムやコマンドの実行結果を判断する際に「終了コード」というものが非常に重要になる。一般的に、終了コード0は「成功」を意味し、それ以外の数値は「何らかの失敗」を意味する。多くのシステムでは、この終了コード0を見て「問題なく処理が完了した」と判断する。しかし、この常識が思わぬ落とし穴になることがある。

ニュース記事は、まさにその落とし穴について深く掘り下げている。終了コード0は確かに「失敗しなかった」ことを示すが、「意図したことが起こった」ことを意味するわけではない、という本質的な問題を提起する。例えば、1万件のテストを実行して全てパスした場合も、テストが一つも実行されなかった場合も、多くのテストツールは終了コード0を返してしまう。これは、一見すると「成功」しているように見えるが、実際には何も確認されていない「サイレントエラー」と呼ばれる状態である。このような状況では、誰も問題に気づかず、チェックが機能しなくなっている状態が長期間続く可能性がある。ファイルが見つからない、特定の要素が選択されないといった状況でも、コマンド自体は「エラーなく終了」したと判断され、緑色の成功表示がされると、誰もがそれが正しいと信じてしまうのだ。

記事では、具体例としてGo言語のテストコマンドgo test ./...を挙げている。このコマンドをテストファイルが一つもないプロジェクトディレクトリで実行すると、出力には[no test files]と表示されるにもかかわらず、終了コードは0を返す。つまり、「テストは失敗しなかった」と報告するが、「テストが実行された」という証拠は何もないのだ。このような状況は、システムの健全性を判断する上で非常に危険だ。

このサイレントエラーという根本的な問題に対処するために開発されたのが、didrunというツールである。didrunは、単に「成功したか失敗したか」だけでなく、「実際に実行されたか」「失敗したとして、それは期待通りの失敗だったか」という三つの問いを明確に区別して答えることを目指している。この区別を曖昧にしてしまうことが、この種の欠陥の根本原因だと指摘している。

didrunは、プログラムの終了状態を従来の数字の終了コードだけでなく、より詳細な4つのカテゴリで表現する。

  1. ran-and-passed (終了コード0): コマンドが期待通りに実行され、成功したことを示す。これは従来の「成功」に最も近い。
  2. ran-and-failed (元のコマンドの終了コード): コマンドが実行されたが、そのコマンド自体が何らかの理由で失敗したことを示す。didrunは元のコマンドが返した終了コードをそのまま返す。
  3. did-not-run (終了コード3): コマンドは実行されたものの、didrunが設定した条件(後述する「述語」)を満たさなかったため、実質的に何も処理しなかった、あるいは意図した動作が行われなかったと判断される状態。例えば、「テストファイルがない」といったケースがこれに該当する。「テストが失敗した」と「テストがない」は、開発者にとって全く異なる意味を持ち、異なる対処が必要となるため、明確に区別される。
  4. ran-and-failed-wrongly (終了コード4): コマンドは実行され、失敗したが、その失敗がdidrunの予期しない形で発生したことを示す。例えば、テストスイート自体が構文エラーでクラッシュした場合など、期待する結果を検出する以前の問題が発生した状況だ。「私のチェックが何かを検出した」という意味での失敗とは異なる。

didrunは、これらの状態を判断するために「述語(Predicate)」と呼ばれる条件を使用する。述語とは、コマンドの出力や副作用(ファイル生成など)をチェックし、実際に期待する動作が行われたかどうかを検証するためのルールだ。

最も重要な述語の一つは--expect-countである。これは、コマンドの出力から特定の数値、例えば「パスしたテスト数」などを読み取り、その数がゼロではないことや、特定の閾値を超えていることを確認する。記事では、「0 passed」という出力の危険性を強調している。多くのテストランナーは、テストが0件パスした場合でも「0 passed in 0.01s with exit 0」のように表示し、終了コード0を返すことがある。これは、パターンマッチングでは「数字とpassedという文字列」が含まれているため成功と誤認されやすい。しかし、didrunの--expect-countは、実際に読み取った数値がゼロである場合にdid-not-runと判断することで、この種のサイレントエラーを防ぐ。つまり、「何かがパスした」という報告だけでなく、「いくつパスしたのか」という具体的な数値まで確認するのだ。

もう一つの重要な述語は--wrote PATHである。これは、単に指定されたファイルが存在するかどうかを確認するだけではない。昨日のテストで残ったjunit.xmlファイルが存在していても、今回の実行中にそのファイルが「作成、変更、または書き換えられた」ことを確認する。もしファイルの内容が以前と全く同じであれば、それは古いものであり、今回の実行で何も生み出されなかったと判断される。

さらに、didrunはタイムアウトや強制終了されたチェックについても、適切に分類して処理する。中断されたチェックは「パスした」と見なされることは決してなく、未完了として扱われる。また、didrunは全てのチェックが成功した場合でも、各述語が何を期待し、何を発見したのかをレポートに出力する。これにより、結果が「成功」か「失敗」かだけの情報に留まらず、なぜその結論に至ったのか、第三者が詳細に監査できる仕組みを提供する。

一部のテストランナー、例えばpytestは、テストが一つも収集されなかった場合に終了コード5を返すなど、同様の問題に対処しようとしている。jestやvitestも、デフォルトでテストが見つからない場合に失敗として扱う(ただし、--passWithNoTestsフラグでその挙動を変えることもできる)。しかし、go testや空のグロブパターンを与えられたリンター、そしてCI/CDパイプライン内の多くのシェルスクリプトのステップなど、多くのツールや状況では、依然としてこのサイレントエラーの問題が残っている。たとえ一部の良いテストランナーであっても、全てのテストがスキップされた場合に終了コード0を返すなど、完全には問題を解決できていないケースもある。例えばpytestは、全てのテストがスキップされた場合、「2 skipped」と表示しつつも終了コード0を返すことがあり、これも「何もチェックされなかった」状態の一種と言える。

didrunは、Python(pip install didrun)とNode.js(npm install -g @megapixel99/didrun)の両方で提供されている。名前が異なるのはnpm側の制約によるものだが、機能的な同等性が厳密に保証されている。徹底したテストが施されており、意図的にサイレントエラーを引き起こすような「変異」をコードに加えたとしても、それが正しく検出されることが確認されている。

先行技術の調査では、didrunが解決しようとしている問題領域は意外にも空白が多いことが分かった。特定のテストランナーの終了コードをカスタマイズするツールや、GitHub Actionsの証拠バンドルを事後監査するツールなどは存在するが、任意のコマンドをラップして「実際に何か起こったのか」を問うツールはほとんど見当たらなかった。これは、この種の失敗モードが非常に一般的であるにもかかわらず、奇妙なギャップだと言える。

結論として、didrunは、従来の終了コード0が持つ曖昧さを取り除き、「意図しない無活動」つまりサイレントエラーを明確に検出するための画期的なツールである。これにより、システムエンジニアは、表面的な「成功」に惑わされることなく、本当にシステムが期待通りに機能しているかを確信できるようになる。開発プロセスにおける品質と信頼性を向上させる上で、このようなツールの重要性は非常に高い。

関連コンテンツ

関連IT用語