現在JavaScriptの設定が無効になっています。このページを利用するにはJavaScriptの設定を有効にしてください。エンジニアの小沼俊治です。開発者の皆さんの中には、CIにユニットテストの自動化を導入して、日々開発されるプロダクトの品質維持に務めている方々も多いのではないでしょうか?私の担当サービスでもxUnitを使ってプロダクトコードにテストコードを作成して、CIツールのジョブで定期的にユニットテストを実行して品質維持に活用しています。言う観点が重要になってきます。その観点の達成状況を確認する指標の1つとして、ユニットテストを実行した時に行われるコードカバレッジ解析を利用する手があります。これはテストコードが実装コードで実行したステップとその割合を示すものですが、闇雲にテストコードを数だけ用意したからといって単純にコードカバレッジが上がっていく訳ではありません。プロダクトコードの実装面からも、テストコードと親和性の高いプロダクトコードの作りが理解できているかどうかで、得られる結果に大きな差がでてきます。何事も高スコアを達成できると気持ちのいいものですし、引き続きユニットテスト環境を整備・維持していこうとするモチベーションにもつながると思っています。よる小刻みで軽快な開発を阻害してしまうことは望んでいません。皆さんが「こんなテストケースを書きたい」と思いついたときに、思い描いたテストコードを自由自在に書くことができるスキルの習得を目指す際の、成長度を確認する定量的な指標の1つを提案させていただいていると言うことで、ご理解いただきたいと思います。コードカバレッジの向上に役立つ実装事例を主に以下の観点で紹介させていただこうと思います。接続、または、ファイルアクセスなど、外部のコンポーネントやリソースに依存する実装は一般的にもあると思います。それらのプロダクトコードにユニットテストを実行するには、事前に外部のコンポーネント(および、リソース)と接続する設定を行い、テストケースに見合ったテストデータを外部のコンポーネントに用意しておく必要があるため、気軽にどんな環境でもユニットテストが実行できず、せっかく作ったテストコードも実行される機会が少なくなり、品質維持につながらなくなります。ユニットテストの実行条件が外部のコンポーネントに依存してしまう割合が高いため、正常系、異常系のあらゆるケースを立て続けに実行することも非常に困難で、その分、テストケースの網羅性に支障をきたします。ユニットテストを実行できる条件が環境に左右されず、また、気軽に実行してもらえるようにするには、外部のコンポーネントに接続するオブジェクトなど、ユニットテストの実行条件を上げてしまっている依存オブジェクトをテストダブルに置き換えられると、環境に依存する実行条件が無くなり、気軽にどんな環境でもユニットテストが実行できるので、品質維持に活用されるようになります。ここでテストダブルと言う用語が出ましたが、もしかするとスタブやモックなどと言う方がなじみ深い方も多いかと思いますが、これら依存するオブジェクトの代役としての総称を指しています。テストダブルの定義については、「テストダブルで検索」していただくとより詳しく解説されているサイトがありますので、そちらをご参照ください。依存するオブジェクトをテストダブルで置き換えるとはどう言うことかを具体的に説明していきましょう。ここで示した図は、実行環境でプロダクトコードを実行した際の、あるクラス構成を表しています。クラスを依存してプログラムを実行します。データベースに接続する必要があるため、このクラスを実行するにはデータベースへの接続設定などの条件が整っている必要があります。これらのプロダクトコードに対してユニットテストを実行する際は、どうすればよいでしょう。次の図で表してみました。クラスのインスタンスではなく、テストダブルのインスタンスに置き換えて実行します。クラスのユニットテストが実行できるようになります。クラスのテストダブルを用意して、テストダブルのインスタンスに置き換えて実行します。クラスから安易に全てのテストケースを実行できるためテストダブルに置き換えていません。この様に、実行する事が困難だったりする依存オブジェクトを、テストダブルに置き換えることが出来るようなプロダクトコードの実装になっていれば、気軽にどんな環境でもユニットテストが実行できるようになります。含めた細かい条件でテストが実行できるので、コードカバレッジの向上につながってきます。次にソースコードを用いてテストダブルに置き換える実装例を説明させていただきます。)が使われているため、ビジネスロジックの動作確認としてユニットテストを実行したい場合でも、KVSに接続できる環境が整っているロジックが通過できるようなデータがKVSに用意されているなどの実行条件をクリアする必要があり、テストコードを作成した当初はそれら条件がそろっていても、時間の経過とともにデータの欠落が生じたり、プロダクトコードの維持と並行して永続的に条件を維持することも難しくなります。CIツールによるビルド実行時にユニットテストの実行を組み込むには、環境依存の実行条件を排除しておく方が組込みやすくなります。これらの理由により、外部のコンポーネントへ直接的に依存している実装をビジネスロジックから分離してみます。}}ラッピングしたクラスはビジネスロジックと完全に切り離されているので、このクラスの役割としては、ビジネスロジックの事は全く考えずに、外部のコンポーネントと接続する機能の動作を保証することに専念できます。外部のコンポーネントへ接続する実装を、ラッピングクラスを経由した間接的な接続方法に改良します。ラッピングクラスのインスタンス生成をコンストラクタのパラメータ化を用いて実装することにより、テストダブルが組み込みやすいプロダクトコードにします。クラスのオブジェクトを生成してビジネスロジックが実行されます。コンストラクターの引数でテストダブルのオブジェクトを投入するとテストダブルでビジネスロジックが実行されます。後者がユニットテストにおける動作に該当し、外部のコンポーネントへ接続できた振りをしたテストダブルを投入すれば、実際に外部のコンポーネントに接続できていなくてもビジネスロジックのユニットテストが実行できるようになります。この様に外部のコンポーネントに直接的につながっていなければ、外部のコンポーネントの環境に直接影響しないので、このクラスはビジネスロジックの動作を保証することに専念できるようになります。コンストラクタの引数にテストダブルを投入しないので、実際にKVSに接続してビジネスロジックが実行されます。生成したモックオブジェクトをテストダブルとしてテスト対象のコンストラクタに投入することで、外部コンポーネントの接続状況などに左右されず、ビジネスロジックを単体でテストすることが可能になります。}}ラッピングクラスにユニットテストでは、ビジネスロジックから分離させた、外部コンポーネントへの接続をラッピングしたラッピングクラスにユニットテストを実行する場合、外部コンポーネントの接続ライブラリがインストールされている環境であればそのままユニットテストが実行できます。確認できれば最低限としてのユニットテストは達成されます。CIツールからユニットテストを実行する時のように、外部コンポーネントの接続ライブラリがインストールされていない環境で実行させる必要がある場合には、名前空間に実装した関数はグローバル関数より優先して実行されるというPHP言語仕様を活用することにより、同名の関数をスタブとして用意できるので、ユニットテストではそれらを利用することも可能になります。スタブとして実装しました。参照することでスタブ関数を利用してユニットテストを実行できます。}}プロダクトコードにオブジェクト指向型APIを利用している場合ここまではKVSへの接続を手続き型API(関数)で実装したプロダクトコードで説明してきましたが、オブジェクト指向型APIを使っていた場合には、外部のコンポーネントへ接続するためのラッピングクラスは用意することなく、テストダブルを組み込みやすいプロダクトコードが実装できます。コンストラクターのパラメーター化で、引数にオブジェクト指向型APIのインスタンスを指定することで、コンストラクタ内で生成するか、コンストラクタ生成元から投入するかを選択できれば、プロダクトの実行環境、および、ユニットテストのいずれにおいても実行しやいクラスになります。(以降の実装は割愛)}ユニットテスト実行時には、Memcacheクラスのインスタンスの代わりにMemcacheクラスを模したのモックオブジェクトを生成してコンストラクタで投入します。アクセス権を付与してメソッドを実装すると思います。メソッドしかコールできません。メソッドに対して直接ユニットテストを実行したいと思うことはあるはずです。してしまうと言うことは、本末転倒であります。ご紹介したいと思います。呼ばれるので全てのメソッドをテストコードから実行することは可能です。内部で3つの条件分岐が実装されているので最低3回はテストを実行する必要があります。生成された乱数が元になっているので、3回テストを実行したからといって必ずしも全てのコードをカバレッジできるとは限りません。全てをカバレッジするには運次第になりますね。テストコードから直接呼び出せれば確実性のあるユニットテストが実施可能だと、誰しもが考えることだと思います。メソッドのアクセス権を変更できるので、それらのメソッドにユニットテストを直接実行することが可能になります。クラスを利用することで、プロダクトコードのアクセス権に対する設計思想を変更することなく、ユニットテストが実行可能となりました。メソッドを使うと抽象メソッドをインスタンス化することが出来るため、わざわざ継承実装した具象クラスを用意することなく、抽象クラスのユニットテストが実行可能になります。メソッドで抽象クラスのモックオブジェクトを生成できます。そのモックオブジェクトに対して具象メソッドを実行できるので、ユニットテストが実行可能になります。メソッドにもユニットテストが実行できたりもします。さいごに今回は、まず始めにプロダクトコードがある事が前提で、後からテストコードを用意する流れで説明をしてきましたが、紹介した実装事例が理解できているとプロダクトコードを実装する前に、プロダクトコードのインターフェースからテストコードを実装することも可能になってきます。これができると、テスト開発駆動(TDD)を導入することへの敷居も下がり、その結果、プロダクトコードが実装されると既にテストコードもペアで必ず存在することになるので、ユニットテストフェーズが意味を成す状態となり、理想的なCIライフサイクルに近づけることも可能だと考えています。それでは、ご紹介させていただいた事例が、少しでも皆さんのクリエイティブな活動にお役に立てていただければ幸いです。JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。JAPANのサービス開発を支える技術やデザイン、文化を発信しています。Reserved.このページの先頭へ。