【単体テスト自動化の落とし穴】グローバル変数がテストを壊す本当の理由と静的解析での防ぎ方
単体テストの自動化や静的解析に力を入れているにもかかわらず、「テストがたまに落ちる」「昨日は通ったのに今日は失敗する」といった経験はありませんか。私自身、プログラマーとして長年開発現場に携わる中で、何度もこの問題に悩まされてきました。
その原因を深掘りしていくと、高確率で浮かび上がってくる存在があります。それがグローバル変数です。本記事では、プログラマーやSEの方へ向けて、単体テストの自動化や静的解析の観点から「なぜグローバル変数がテストを壊すのか」を、実体験を交えながらわかりやすく解説していきます。
グローバル変数とは何かを改めて整理する
グローバル変数とは、プログラム全体のどこからでも参照・変更できる変数のことです。関数やクラスの外で定義され、複数の処理から共有されるのが特徴です。
一見すると便利そうに見えます。設定値を一か所で管理できたり、状態を簡単に共有できたりするからです。私も新人時代は、「あちこちに引数を渡すくらいならグローバル変数でいいじゃないか」と安易に使っていました。
しかし、単体テストを自動化し始めた途端、その便利さが一気に牙をむきます。
単体テストとは何を保証するものなのか
単体テストとは、「ある関数やクラスが、決められた入力に対して、必ず決められた出力を返す」ことを確認するテストです。
重要なのは「必ず」という点です。テストは毎回同じ結果を返さなければ意味がありません。テストを何百回、何千回と自動実行しても、結果が揺らがないことが品質の土台になります。
この「毎回同じ結果」が、グローバル変数によって簡単に壊されてしまうのです。
グローバル変数が単体テストを壊す3つの理由
① テスト同士が見えない形で依存してしまう
私が実際に経験した例をお話しします。あるプロジェクトで、設定情報をグローバル変数に保持していました。Aというテストではその変数を書き換え、Bというテストでは初期値を前提に処理を行っていました。
単体で実行すると、AもBも問題なく通ります。しかし、テスト全体を一括実行すると、Aの実行結果がBに影響し、Bが失敗するという現象が発生しました。
テストは独立しているはずなのに、グローバル変数を共有しているせいで、実行順序に依存するテストになってしまっていたのです。
② 状態が外から見えず、原因特定が困難になる
グローバル変数は、どこで書き換えられたのかが非常に追いづらいです。特に規模が大きくなると、「この値はいつ変わったのか?」をコードだけで追跡するのが困難になります。
テストが失敗したとき、原因がテスト対象のロジックではなく、別のテストや初期化漏れだったというケースを私は何度も経験しました。
③ 並列実行やCI環境で不具合が顕在化する
最近のテスト自動化では、実行時間短縮のためにテストを並列実行するのが一般的です。しかし、グローバル変数は並列処理と非常に相性が悪いです。
ローカル環境では問題なくても、CI環境で突然テストが不安定になる。その原因が、複数のテストから同時にアクセスされるグローバル変数だった、ということも珍しくありません。
静的解析が教えてくれたグローバル変数の危険性
私がグローバル変数の問題を強く意識するようになったきっかけは、静的解析ツールの導入でした。
静的解析とは、プログラムを実行せずにコードを解析し、バグの温床になりやすい構造や設計上の問題を検出する技術です。多くの静的解析ツールは、グローバル変数の使用を警告対象として扱います。
最初は「細かすぎる指摘だ」と思っていましたが、テストの不安定さと照らし合わせるうちに、静的解析の警告は未来の障害予告だと実感するようになりました。
グローバル変数を使わない設計がもたらすメリット
テストが安定し、自動化の信頼性が上がる
グローバル変数を排除し、必要な値を引数やインスタンスとして渡すようにしたところ、テストの成功率が劇的に安定しました。
「テストが落ちた=本当にバグがある」という状態を作れるのは、開発者にとって非常に大きな安心材料です。
コードの意図が明確になり、保守性が向上する
依存関係が明示されることで、「この処理は何に依存しているのか」が一目でわかるようになります。結果として、他人のコードを読むストレスも大きく減りました。
静的解析の指摘が減り、品質指標が改善する
グローバル変数を減らすことで、静的解析の警告数が目に見えて減りました。これはコード品質を数値で示すうえでも、大きなメリットになります。
実践編:グローバル変数を排除する具体的な方法
① 依存性注入を使う
必要なデータや設定は、関数の引数やコンストラクタで渡します。最初は面倒に感じますが、テスト時にモックやダミーを差し替えやすくなります。
② 状態を持つならインスタンスに閉じ込める
どうしても状態が必要な場合は、グローバルにせず、クラスの内部に閉じ込めます。これにより、影響範囲を限定できます。
③ テスト前後で必ず初期化する
既存コードですぐに排除できない場合は、テストのセットアップと後処理で状態をリセットします。これは応急処置ですが、効果はあります。
応用編:さらに便利になる設計とテストの考え方
一歩進んだ考え方として、「状態を持たない関数(純粋関数)」を増やすことをおすすめします。入力が同じなら出力も同じ、外部状態に依存しない関数は、単体テストとの相性が抜群です。
私の経験では、純粋関数中心の設計に切り替えたことで、テストコードの量が減り、静的解析の指摘も激減しました。
まとめ:グローバル変数を制する者がテスト自動化を制する
グローバル変数は一時的には便利ですが、単体テストの自動化や静的解析を本気で運用する段階では、大きな足かせになります。
私自身、何度も遠回りをしましたが、グローバル変数を減らすことで、テストの安定性、コードの可読性、開発チーム全体の安心感が大きく向上しました。
もし今、テストが不安定だと感じているなら、その原因を「グローバル変数」に疑いを向けてみてください。それが、品質向上への第一歩になるはずです。

コメント