Template Methodパターンの事例
デザインパターンでテスティング・フレームワークを作る
昨今、ソフトウエアの商用/オープンソースを問わず、フレームワークを使用したソフトウエア開発を行う機会が増えています。
そういったフレームワークは、学ぶべき設計ノウハウやデザインパターンの適用事例の宝庫です。クラス名やメソッド名からも利用しているデザインパターンがイメージできることもあります。さらにオープンソースソフトウエア(以下OSS)フレームワークは、優秀なエンジニアが作成したソースコードを自由に入手して読むことができます。そのようなソースコードは設計思想、設計ノウハウやデザインパターンの活用方法を学ぶ良い教材となります。
本連載では、身近なフレームワークに含まれるデザインパターンの適用事例を解説します。どのような問題を解決しているのか、どのような工夫がなされているのかをひもといていきますので、デザインパターンについて効率よく理解を深めることができるでしょう。
初回は、テスティング・フレームワークの1つであるJUnit3.8.1(http://www.junit.org/)を事例に、Template Methodパターンを取り上げます。Template Methodパターンは、GoFのデサインパターンの1つで、振る舞いに分類されるパターンで、抽象クラスと具象クラスの関係をうまく活用したパターンです。
抽象クラスで処理の型(テンプレートになるメソッド)を実装し、具象クラスでその処理の型について具体的な処理を実装します。JUnitのTestCaseクラスは、Template Methodパターンが適用されています。テストケースは、必ずTestCaseクラスを継承して実装します。このTestCaseクラスに焦点を当てて、どのようにTemplate Methodパターンが適用されているのか、どのような効果があるのかを分析して理解を深めます。
なお、これ以降は説明の都合上、次の名称で説明します。
テストコード:テストケースを束ねるソースコード。JUnitではTestCaseクラスに相当。
テストケース:1テストケース。JUnitではTestCaseクラスのサブクラスで実装するテストケースメソッドに相当。
必要なフレームワークを考える
なぜテスティング・フレームワークが登場することになったのかを考えてみましょう。
JUnit等のテスティング・フレームワークが存在しなかったころは、ユニットテストを実施するために、テスト・ドライバーを作成していました。テスト・ドライバーとはテスト対象を呼び出して評価するための別のプログラムのことで、ほとんどの場合は開発者がテスト対象ごとに作成し、個別に実行されていました。
このようなテスト手法には、次のような問題がありました。
○テストコードの問題
・人によって、テストコードの実装が異なる
・テストコードの品質にバラツキがある(新人だとバグを埋め込むことも……)
・前に実装したテストコードをコピーして再利用する(似たようなコードが増える)
○実施時の問題
・テストコードを1つずつ、人の手で実行しなければいけない
・回帰テストなど、再実施に時間がかかる
○実施結果の問題
・実施したテストケースの数がわからない
・成功/失敗のときのステータスの表現が統一されていない
・実施結果から問題個所の特定が難しい
今回はこの問題の中から、JUnitのTestCaseクラスに関係の深い「テストコードの問題」を解決するためのフレームワークを作成します。
■テストコードには共通の振る舞いごとがある
テストコードの実装は、人によるところもありますが、誰が実装しても大体同じ内容になります。整理すると、次のような共通点があります。
○1テストケースにおけるテスト実行の処理パターン
パターン1. テストケース実行処理
パターン2.テスト前処理 → テストケース実行処理
パターン3. テストケース実行処理 → テスト後処理
パターン4.テスト前処理 → テストケース実行処理 → テスト後処理
○次の処理は、テストケースごとに異なる。
テスト前処理
テストケース実行処理
テスト後処理
○テスト結果を評価する処理をテストコードごとに持っている
■テストコードにはテストケースが1つ以上存在する
テストコードは、評価対象となるソースコードと対になるように作成するのが一般的です。
テストコードには、正常系と異常系のテストケースを実装する場合が多いです。よってテストコードには、1つ以上のテストケースが含まれることを想定できます。
■こんなフレームワークが必要!?
以上のことをまとめると、問題を解決するには、次の特徴を持ったフレームワークが必要になります。
1.テストケースの実行順序を統一したい
今回の場合は、 テスト前処理 → テストケース実行処理 → テスト後処理
2.具体的なテストケースの処理内容のみ実装したい
3.テスト結果を評価する共通処理をすべてのテストコードから利用できるようにしたい
4.テストコードは1つ以上のテストケースで構成される