開発フェーズは、ソフトウェア開発ライフサイクル(SDLC)の段階であり、プログラマーがソフトウェアを構成するコードを作成します。アプリケーションのソースコードを作成するだけでなく、データベースの開発や環境のオーケストレーションなども開発フェーズに含まれます。

結局のところ、インフラストラクチャがコードである世界では、コードによるコンピューティング環境の構築は、ソフトウェア開発の他の側面と同じような考え方を必要としています。

開発段階でのテストでは、コードを書いた人がコードをテストするのが原則です。そして、書いたテストは、開発者からコードから離れた後でも自動テスト環境で繰り返し実行できるものでなければなりません。

CI/CDパイプラインの開発フェーズでテストを実施する場合、ユニットテストがスタート地点となります。

ユニットテストは、ソースコードレベルで個別の関数をテストするプロセスです。開発者は、ある関数を実行するためのテストを書きます。関数が実行されるとき、テストはその関数が返す結果に対してアサーションを行います。結果を返さない関数は、ユニットテストの対象としてふさわしくありません。そのような関数は、与えられた関数の範囲を超えてシステムの状態を変化させるからです。したがって、その関数は、インテグレーションレベルあるいはシステムレベルでテストする必要があります。ユニットテストは、うまくカプセル化され、実行時に結果を返す個別の関数を実行することです。

個別の関数をテストするという考え方は、単純で当たり前のように思えるかもしれません。しかし、悲しいことに、1つの関数に多くのアプリケーションロジックを入れすぎているコードが多く存在します。一つの領域をカバーするのではなく、関数があちこちに散らばっているのです。これはスパゲッティコードと呼ばれています。

(図1)スパゲッティコードではユニットテストが困難

A screenshot of spaghetti code showing that spaghetti code is hard to unit test.

ユニットテストは、うまくカプセル化されたコードを実行するときに最も信頼性が高くなります。

ユニットテストは、各関数が単一の領域を持っているコードを実行するときに、最も効果的です。離散的な関数は、その関数の領域外の振る舞いを別の関数に委譲します。例えば、図 1のように、うまくカプセル化された関数 UpdateUser(user_data) は、関数 ValidateUserData(user_data) を使用して、渡されたユーザーデータの妥当性を判断しています。UpdateUser(user_data)の内部では、ユーザー検証は行われません。したがって、検証のためのユニットテストを書くときは、UpdateUser(user_data) に対してではなく、ValidateUserTest(user_data) という関数に対してテストを書く必要があるのです。うまくカプセル化されたコードに対するユニットテストは、明快で制御しやすくなります。何か問題が発生したとき、悪い動作は簡単に特定できる関数に限定されます。

ユニットテストに必要なツール

ユニットテストに使われる一般的なツールの例としては、JavaコードのテストにJUnit、.NETコードのテストにNUnitMSTest、NodeJsプログラムのテストにMocha/Chai、PythonコードのテストにUNITEST、PHP用のユニットテストを書くのにPhpUnitがあります。C++は、長い間存在している言語であり、幅広い実装基盤を持っています。したがって、C++をユニットテストするときに、多くのユニットテストフレームワークから選択することができます。C++のユニットテストフレームワークの一覧はこちらで見ることができます。

テストのパス判定とコードカバレッジ:ユニットテストにおける2つの基本指標

ユニットテストを書くことをコミットすることは、CI/CDパイプラインにおけるテストのプロセスにおいて不可欠な最初のステップです。しかし、コミットはテストを書くこと以上のものです。書かれたテストは、信頼できるものでなければなりません。テストが信頼できるものであるためには、テストの結果が測定可能でなければなりません。ユニットテストによく使われるメトリクスは、"100%パス” と "コードカバレッジ" の 2 つです。

100%パスの指標を理解する

ユニットテストが実行された時の結果は、パスするか失敗するかのどちらかです。100%パスのテスト指標は、その名の通りですが、コードベースに対して実行されるすべてのユニットテストは、100%パスする必要があります。CI/CDパイプラインのどこかで失敗するテストは、特にそのテストが過去にパスしたことがある場合、直ちに対処する必要があるリスクを生み出します。

100%パスという指標をサポートすることは、ユニットテストの設計方法に直接的な影響を与えます。テストはアサーションに基づきます。実際、テストが有効であるためには、少なくともひとつのアサーションが含まれていなければなりません。しかし、テストに含まれるアサーションの数が増えてくると、厄介なことになります。

通常、大量のアサーションを持つテストはスパゲッティ・テストになる危険性があります。たとえば、validateUserDataTest という名前のテストに 10 個のアサーションがあるとしましょう。多くのアサーションがあるということは、そのテストが関数の振る舞いのさまざまな側面を検証していることを意味します。どれかひとつのアサーションが失敗すると、そのテストはすべて失敗となります。しかし、テストに含まれるアサーションの数が多いため、どの検証で失敗するのかを正確に把握するのは困難です。

ユニットテストのもうひとつのアプローチとして、合理的と判断される場合には様々なアサーションを別々のテストに分割することがあります。開発会社の中には、1 つのユニットテストにつき 1 つのアサーションという方針をとっている会社もあります。また、もっと柔軟に対応する会社もあります。しかし、CI/CDパイプラインで包括的なテストを行うほぼすべての開発会社は、アサーションを適切に分割し、関数の動作の1つの側面のみを検証するように制限することを要求しています。(以下、図2を参照)

A screenshot showing the approach to unit testing that is to segment various assertions into separate tests when reasonable.

分割されたアサーションは、より信頼性の高いテストを可能にします。

100%パスという指標をサポートすることは、高品質のコードの開発を保証するために大きな役割を果たします。しかし、100%パスだけでは問題は解決しません。50 個の関数を持つコードベースを想像してみてください。開発者が書いた100%パスのテストでは、40の機能しか実行されないとしたら、明らかにリスクがあります。しかし、書かれたコードがすべてテストされたわけではないことを、定量的にどうやって知ることができるでしょうか。そこで、コードカバレッジレポートの出番となります。

コードカバレッジレポートの理解

コードカバレッジレポートは、記述されたすべてのコードがテストを通じて実行されたことを知るための方法です。最近のユニットテストのフレームワークのほとんどがコードカバレッジレポートを提供しています。以下の図3は、Android Studioでのユニットテストによるコードカバレッジ機能を示しています。

A screenshot showing the code coverage capabilities of unit testing under Android Studio.

Android Studioは、ユニットテストフレームワークの一部として、コードカバレッジレポートを提供します。

以下の図 4 は、典型的なコードカバレッジレポートを HTML 形式で示したものです。ユニットテストによって実行されたコード行は、緑色でハイライトされています。実行されなかったコード行は、赤で強調表示されます。

A screenshot showing a typical code coverage report in HTML format.

ほとんどのコードカバレッジレポートツールは、ユニットテストによって実行されるコードの行を視覚的に判断する方法を提供します。

コードカバレッジレポートの価値は、ユニットテストの有効性を正確に測定する方法を提供することです。前述したように、100 個の単体テストが 100% パスしていても、実際に実行されたのはコードベースのほんの一部だけということは十分にありえます。コードカバレッジレポートは、開発者のユニットテストの全体的な有効性を判断するために必要な情報を提供します。

ユニットテストでカバーすべきラインの許容割合は、企業によって異なります。100%のカバレッジを要求する厳しいアプローチを取る企業もあります。しかし、多くの企業では、それほど厳密ではありません。多くの企業では、オブジェクトのコンストラクタのようなデータの初期化関数と同様に、すべてのビジネスルールとアルゴリズムコードがカバーされていることを確認することが、許容できるレベルのカバレッジとなります。

単純なデータオブジェクトを作成する場合、自動コード生成は多くの企業で一般的に行われています。そのため、データオブジェクトのセッターやゲッターのテストは、コードの他の部分で検証されることを信じて見送る企業もあります。

最も重要な2つの項目は、企業が合理的で測定に基づいたコードカバレッジポリシーを実施することと、開発者がCI/CDプロセスにさらされるリポジトリブランチにコードをチェックする前にポリシーを支持することです。

すべてを統合する

テストがQA部門の唯一の責任であった時代は終わりを告げました。CI/CDパイプラインを通過する必要のある複雑なコードが多すぎるのです。開発者は、エラーを修正するために効率的に対応することができる最も優れた役割を担っています。テストが開発者から離れれば離れるほど、テスト活動のコストが高くなることは過去の研究によって度々示されています。mablの社内ルールは、「書いたら、テストする」です。mablのモットーは、素早く頻繁なテストを実行するために、テストを自動化することです。

自動化されたテストは、CI/CDパイプラインの基礎となるものです。開発フェーズは、CI/CDデプロイメントプロセスの最初のステップです。しかし、それが唯一のステップではありません。CI/CDプロセスの開発フェーズの次のテストフェーズは、ローカルUIテストになりますが、これについては次回に詳しく解説します。

CI/CDパイプライン全体を通してテストを自動化するために、mablがどのように役立つかをご覧ください。今すぐ無料トライアルを開始しましょう