改めて自動テストを書く意義とは何か
以下では自動テストを書く意義を再考していきます。
本連載の第1回の最後でテストはバグを早期に発見するために実施するのだと紹介しました。では、自動テストと手動テストを全て実施すればバグは全て早期に発見できるのでしょうか。また、早期にバグを発見するとは一体どういうことでしょうか。
どれほど多くのテストを書いたとしても、どうしても発見できないバグはある
結論から書くと、ソフトウェアに関するあらゆるテストを実施することは不可能です。このことはJSTQB(日本ソフトウェアテスト資格認定委員会)が制定した「ソフトウェアテストの7原則」のうちの一つ、「全数テストは不可能」という原則に表されています。
ソフトウェアに対する包括的なテストを実施できないということは、ソフトウェアが含んでいる全てのバグはテストで検知できないことを意味します。そもそもテストケースを思いつかなければテストを実施することはできないため、社内メンバーの誰もが想定していないエッジケースでバグが出る可能性を否定しきれないからです。
例えば、次の複雑な操作を考えてみましょう。ブラウザのタブを2つ開いて同一サイトに同一アカウントでログインし、片方のタブではページAを表示しつつ、もう片方のタブはページBを表示して、特定の操作を行った時にだけ何らかのバグが出るといったケースがあるとします。通常の使用方法とは異なるこのようなケースはエッジケースであり、前もってテストケースとして考慮するのはかなり難しいのではないかと思います。
では、テストでソフトウェアの全てのバグを検知できないのであれば、バグに気づく方法は他にないのでしょうか。
開発フェーズで発見できなかったバグは運用フェーズで検知する──DevOpsバグフィルター
そもそも、この記事で言及している自動テストはDevOpsにおけるDev(開発)のフェーズにおけるバグの検出方法でした。
Devと対になるOps(運用)のフェーズにもバグの検知方法があります。それはAlerting(アラートを上げる)、 Monitoring(監視する)、Logging(ログを取る)です。
開発と運用を統合するというDevOpsのアイデアを引用しつつ、開発と運用でバグを検知する方法を統合したモデルにDevOpsバグフィルターがあります。このモデルは『A Practical Guide to Testing DevOps』という書籍で紹介されています。
DevOpsバグフィルターは文字通りバグを虫に、テストを物理的なフィルター(網)に例えています。
6段のフィルターのうち上半分のUnit Test、Integration Tests(結合テスト)、End to End Tests(E2E Tests)は開発(Dev)フェーズでバグを捉える一方、下半分のAlerting、Monitoring、Loggingは運用(Ops)フェーズのバグを捉えるものとされています。
各フィルターの役割を上から順番に見ていきましょう。図に描かれているようにUnit Testsは細かいバグ(虫)を通しません。しかし、フィルターのサイズが小さいため、より大きなバグ(虫)は下に落ちてしまいます。次に、Integration TestsはUnit Testsよりサイズが大きいため、より幅広く虫を捉えられるものの、大きさが十分ではないゆえに通り過ぎてしまうバグ(虫)があります。それは下のE2Eテストも同様です。
これらのテストで検知できるバグはどれも開発フェーズのものであり、この段階でバグがあることに気づければリリース前にそのバグを修正することができます。
運用フェーズでバグを検知する方法
一方、図の下半分の運用フェーズのフィルターはどれも同じサイズです。これらは開発フェーズをすり抜けてすべて本番環境で発生したバグを検知する仕組みだからです。
Alertingでは、本番環境でユーザーがバグに遭遇したときにアラートを発報してバグが起きてしまったことを開発者が知れるようにします。例えば、エラー監視ツールであるSentryを使って、エラー内容をSlackに通知することがAlertingと言えるでしょう。
Monitoringでは、インフラやアプリケーションの状態を監視します。例えば、あるコードのリリースの直後を想定します。その際、Datadogを使ってCPUの使用率が特定のタイミングで跳ね上がっていないかチェックするほか、New Relicを使ってレスポンスタイムが極端に増加していないかをチェックすることがMonitoringです。開発者が異常に気づくためにツール上で閾値を定め、その基準を超えたら電話やSlackに通知することもあります。
AlertingとMonitoringでは、リリースしたタイミングで異常が発生しておりコードに原因があるのであれば、コードの変更を即座にロールバックをすることで影響範囲を最小限に留められます。
Loggingは文字通りシステムやアプリケーションのログを収集、保存、可視化することです。ログにはシステムログやアプリケーションログなどさまざまな種類があります。バグ発見の文脈で特に重要なのはアプリケーションのエラーログです。こちらもエラーログが発生したらAlertingでSlackに通知することが一般的です。
なお、この章の冒頭で「早期にバグを発見するとはどういうことか」という疑問を投げかけました。その答えは①開発フェーズでバグを検知すること、②運用フェーズでバグが出たときに即座に気づくことです。
ユーザーにバグを出さないためには開発フェーズでより多くのバグを検知してリリース前に修正し、万が一リリース後の運用フェーズでバグが出てしまっても、スピーディにロールバックや修正ができれば影響範囲を最小限で抑えられます。バグに気づかず放置し続けることは絶対に避けるべきことです。
DevOpsの功績は、開発者が自身の書いたコードの面倒を最後まで見るように促したこと
開発者と運用者が完全に分かれていた従来のソフトウェア開発においては、開発者がコードに混ぜ込んでしまったバグを運用者が検知して修正していたそうです。
このような状況下では、開発者にとっては一度リリースしてしまえば自分が書いたコードがバグを生んだのかどうか気にする必要はないので、バグを絶対に避ける意識が芽生えづらく、反対に運用者にとっては自分が書いていないコードのバグを急いで修正せねばならないというストレスに悩まされていたそうです。
しかしDevOpsの登場により、そのような状態は健全ではないという認識が広まった結果、今では多くの人が開発者も積極的に運用にも携わるほうが良いという価値観を持っています。
以上のように、DevOpsをテストの観点から捉え直すことにより、開発におけるUnit Tests、Integration Tests、E2E Testsといった自動テストと、運用におけるAlerting、Monitoring、Loggingといった活動を、開発者が一貫して担うべき責務として考えることができるのです。
次回は自動テストを活用してソフトウェアの内部品質を高めるほか、アプリケーションの設計をする方法をご紹介します。