#1 テスト自動化で達成・維持したい6つのゴール

xUnit Test Patterns ざっくり解説シリーズ

動画の方では、ゆっくり語るスタイルでお話しました。今日は改めてブログとして説明し直します。

テスト自動化のゴール理解不足の罠

テスト自動化で達成&維持したいゴールというと、作業の効率化が挙がることが多いでしょう。ですが、作業の効率化は、テスト自動化の取り組みの到達&維持したいゴールの一部しか説明していません。また、テスト自動化の活動を維持するために目指すべき状態(例えば、繰り返し実行できる状態を保つ、読んで理解できるように保つ)を理解しないまま活動をしても、躓いてしまいやがて取り組みを諦めてしまうことになりがちです。

本日は、『xUnit Test Patterns』の3章を使って、テスト自動化のゴール(到達し維持したい状態)を改めて見直します。

1. 品質改善の手助けのために

開発者であれば、おそらく仕様をまとめたときや仕様書を読んだときは気が付かなかったが、のちのテストになって初めて仕様矛盾やヌケモレなどに気づくといった経験があるのではないでしょうか。仕様矛盾やヌケモレ等に早期に気づくベストプラクティスとして、テストを最後に1回だけやるのではなく、早い段階から複数ロールの多様な視点で頻繁に取り組むことが良いと知られています。早期にテストに取り組めば、早期に仕様矛盾やヌケモレに気づき対処できるようになります。一般には、仕様のようなテスト(Tests as Specification)実行可能な仕様(Executable Specification)の言葉として知られています。テスト活動は仕様をまとめる活動とオーバラップします。

自動のリグレッションテストがCIで頻繁に実行できれば、間違いに早期に気づくことができ、バグよけ(Bug Repellent)として役立ちます。手動テストではコミットごとにAllテストの実行は実質不可能で、バグ混入させてしまったことに気づくのが遅くなってしまうでしょう。バグ混入から気づいて修復するまで時間が長くなればなるほど、コスト高になってしまうことは一般的によく知られています。

人はミスをしてしまうことは避けられません。ミスしてしまったときに、問題箇所をすぐに特定できるよう(Defect Localization)にしておくことが大切です。すべてのテストをシステムテスト(End2End)に頼ってしまうと問題箇所の特定や修復できたか否かの判定に時間がかかってしまいます。うまくユニットやコンポーネントを分割し、ユニットやコンポーネントに問題があるか否かを即座に判定できるようにしておくと、問題箇所の特定に役立ちます。

2. ユニットやコンポーネントなどの対象理解の手助けのために

開発者であれば、機能修正のために既存コード内容について周囲に聞こうとしても、前任者がすでに会社から去っており理解に苦労した人もいるのではないでしょうか? 前任者の重要なドメイン知識をどこかに明記しておく必要があります。

アジャイルな開発者であれば、DRY原則を守って、重要なドメイン知識をプロダクションコード&テストコードに集約し明記します。テストコードをドキュメントのように読める状態を保って(Test as Documentation)、コードをだれでも理解しやすく修正しやすくします。いわゆる内部品質の向上の話です。

筆者も先輩から引き継ぎ修正を続けていく際に、テストコードを読んで、ユニットやコンポーネントの理解することをよくおこなっていました。テストコードがなければプロダクションコードを理解するための貴重な情報源が1つ失われてしまいます。テスト活動は保守ドキュメントを書く活動とオーバラップします。大事なドメイン知識を伝達するため、リーンなドキュメントとしてテストに記述していきます。

3. リスクを減らすように、リスクを持ち込まないように

開発者であれば、レガシーコードと呼ばれるテストのない既存コードに恐れや不安を感じながら修正をした、バグを市場に出してヒヤッとした経験があるのではないでしょうか?

テストのセーフティネット(Test as Safety Net)があれば安心して修正し、すばやく壊れていないことを確認することができます。未知のユニットやコンポーネントに対して小さな実験として、テストケースを増やし、知見を増やすことも容易になります。

セーフティネットを使って、ユニットやコンポーネントやシステムが壊れているとわかれば、エディタやバージョン管理システムを使って、動作する地点に戻り、安全地帯から再出発できます。

セーフティネットの張り方の作戦は開発者の腕の見せ所でもあります。筆者自身も安心して機能修正を続けられるように、セーフティネットの貼り方の作戦を途中変更する工夫をしていました。ネットに穴があることを後から発見したら補強もしていました。バグを見つけたらバグを直すだけでなくネットを直すのも大切です。

またテストを書くことでリスクを持ち込まないように注意が必要です。よくあるリスクを持ち込むケースとしては、スタブやモックの誤用・乱用です。次回にテストの不吉な臭い(Test Smells)でいくつかを紹介したいと思います。

4. 簡単に繰り返し実行できるように

テストは人の介入なし動作できるように完全自動にする必要があります(Fully Automated Test)。期待結果どおりだったかの判定と報告を人のインスペクションなしに機械側で判定できる必要があります(Self-Checking Test)。また、テストは繰り返し実行できるように記述する必要があります(Repeatable Test)。

実はこの簡単に実行できて結果がすぐにわかる状態を保つことが難しい!筆者も過去に、同じテストなのにテスト実行結果がOK or NGと結果に一貫性のない事象に陥り悩まされました(テストの不吉な臭いの一種:Erratic Test)。

実行結果に一貫性のない事象は、Google の John Micco氏が語る”Flakyなテスト”の言葉が有名かもしれません。繰り返し実行の維持を妨げる事象はErratic Testだけではありません。活動を続けていれば、続けることを妨げる様々な障害物に出会います。障害物を早期発見と対処や、先回りして予防をし続ける活動が欠かせません。

5.簡単に読み書き、メンテナンスできるように

ソフトウェア開発でコードを書く活動は基本難しい作業です。難しい作業ですが、何も工夫せずに、読んで理解できないコード、書くことが難しいコード、チームメンバーと対話が難しいコードのままでは、テスト活動は維持できません。

テストコードを読みやすく、書きやすく、対話しやすくとメンテンナンスが続けられるように、テストコードをシンプルな構造を保ち(Simple Tests)、抽象度を上げてドメインの語彙を使ってテストコード読んですぐ理解できるようにし(Expressive Tests)、テスト対象がテストしやすいように設計して関心の分離(Separation of Concerns)を促進する必要があります。

3つ目は、もしかしたら皆さんも、後からユニットテストやコンポーネントテストを書こうとして、テストできない設計のまずさに気づいた経験があるのではないでしょうか?ユニットやコンポーネントがテストしやすい設計(Design For Testability)になっているか否かはテストしようとして初めてわかることがよくあります。テスト活動は設計活動とオーバラップし、設計をうまくやるためにも早期にテストに取り組む必要があります。

6.システムの進化を促進できるように

アジャイルな開発が必要な状況下では、頻繁にリリースを続けながら市場ニーズに応える、市場ニーズの変化に応えていく必要性が増します。

市場ニーズに適応してシステム進化を続けるには、既存のテストコードの変更を容易にしてメンテナンスコストをミニマムになるようにする必要があります。

油断していると、テストがシステム進化を助けるのではなく妨げる要因になってしまうので注意が必要です。もし、テストコードとプロダクションコードが密結合で絡まってしまっていると、ちょっとしたプロダクションコードの修正で多数のテストが落ちてしまい途方にくれてしまいます(Fralgie Test)。テストが重複しても、メンテナンスがコスト高になってしまいます。テストコードがじっくり読んでも理解できないのであれば、それは障害物です。スタブやモックの誤用にも注意です。

まとめ

いかがでしょうか?テスト自動化のゴール(到達・維持したい状態)のイメージは記事を読む前と変わったでしょうか?手動から機械による作業の効率化だけではないことが伝われば幸いです。次回は、テストの不吉な臭いについて解説します。

--

--