Javaコードの診断

単体テストと自動コード分析の連携

テストを工夫して、ツールによるコード分析を手助けする

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Javaコードの診断

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Javaコードの診断

このシリーズの続きに乞うご期待。

堅固なコードを作成するのにより有効なのは、テストか、それとも静的分析と論証か -- これは長年にわたる議論です。特にエクストリーム・プログラミングのディスカッション・フォーラムでは、プログラマーが毎日のようにそれぞれの利点について議論しています。

静的分析 (型チェックなど) 賛成派の主な主張は、静的分析の結果がプログラムの起こりうるすべての実行に対して有効であるのに対して、単体テストは、テストされたコンポーネントが (テストされたプラットフォーム上で) テストされたインプットに対してのみ有効であることを保証するにすぎないということです。

これに対して、単体テスト賛成派の主な主張は、単体テストの方がはるかに扱いやすいということです。今日の静的分析ツールの範囲をはるかに超えた、プログラムの多くの制約条件をテストできます。

ここで私はあえて、これら2つのツールが互いに相反するものであるという見方は間違っていると申し上げることにします。これらのツールはそれぞれ、より堅固なプログラムを作成するのに役立ちます。実際に、これらは非常に強力な方法で互いを補い合うことができます。

それぞれのツールには、互いを補うための特に有効で大きな長所があります。

  • 単体テストは、実行の一般的なパスを示すことができ、プログラムの振る舞いを示すことが可能です。
  • 分析ツールは、単体テストの対象範囲をチェックできます。

こうしたそれぞれの特徴について考えながら、それぞれの方法で互いの長所を生かすことができるツールについて検討することにします。

実行の一般的なパスを示す単体テスト

単体テスト・スイートでは、プログラムのコンポーネントに対するきっちりした基本使用例が提供されます。分析ツールでは、テスト実行時のプログラムの振る舞いを調べることにより、プログラム全体にわたるインバリアントであると開発者が考えていることについてヒューリスティックな推測を行うことができます (正にプログラマーが単体テスト・コードを読み進んで行くのと同じように)。

これはまた、単体テストが文書化の実行可能な形式となりうる別の方法でもあります。もっともらしいらしいインバリアントが、単体テストの実行から帰納的に推論された後、分析ツールは、インバリアントが有効であることを演繹的に検証することが可能です、すなわち、実行時に確認されることになるアサーションを使ってコードに注釈を付けることができます。

いずれの場合も、まず最初に、推論されたインバリアントのセットをユーザーに戻し、本当に意図したのはどちらであるのかをユーザーに尋ねるのがツールにとっては最善の方法です。ちなみに、そうしたツールがユーザーの意図しない多くのインバリアントを戻した場合は、単体テストに問題がある (たとえば、単体テストが十分に汎用的でない) ことが考えられます。

このような方法で単体テストに使用できるツールの1つにDaikonがあります。このツールは、MITのMike Ernst氏のプログラム分析グループが作成したフリーの試験的ツールです。Daikonは、プログラムの実行 (たとえば、単体テストの実行) を分析し、インバリアントの推論を試みた後、そうしたインバリアントを使ってユーザーにクエリーを行い、意図されたインバリアントをアサーションとしてプログラムに挿入します。

たとえば、要素を取り出すlookup メソッドと要素を後尾に追加するinsert メソッドをもつSequence インターフェースを実装したVector用のアダプターを作成するとします。lookup メソッドは、それに含まれるVectorにアクセスするために使用するインデックスi をとります。

配列の長さは、length フィールドに保存されているものとします。アダプターでその長さを維持することにより、Vector自体に通知せずに、最後から要素を削除することもできます。

それでは、このような単純なアダプターの簡単なテスト・ケースを作ってみましょう。

リスト1: Vectorコンテナーのシンプルなlookupメソッドのテスト・ケース
import junit.framework.TestCase;

public class VectorAdapterTest extends TestCase {
  public VectorAdapterTest(String name) {
    super(name);
  }
  
  public void testLookupAndInsert() {
    VectorAdapter v = new VectorAdapter();
    v.insert("this");
    v.insert("is");
    v.insert("a");
    v.insert("test");
    assertEquals("Retrieved and inserted elements don't match",
                 "a",
                 v.lookup(2));
  }
}

次に、以下のようにすれば、このテストにパスするためのアダプターを実装できます。

リスト2: Class VectorAdapter
import java.util.Vector;

public class VectorAdapter implements Sequence {
  private Vector values = new Vector();
  private int length = 0;
  
  public void insert(Object o) {
    length += 1;
    values.addElement(o);
  }
  
  public Object lookup(int i) {
    return values.elementAt(i);
  }
}

interface Sequence {
  public void insert(Object o);
  public Object lookup(int i);
}

Daikonがこのコードで実行されると、i が常にlength より小さいlookup メソッドを推論します。Daikonは単体テストからこれを推論し、メソッドの前提条件であるi < length を報告します。

これによりプログラマーは、Daikonが報告したインバリアントを調べて、プログラムがテストでどの程度カバーされたかをより明確に理解することができます。たとえば、Daikonが、意図されない多くのインバリアントの推論を開始した場合には、考えられる入力の代表として適切でないサブセットを使ってプログラムの単体テストを実行しているにすぎないということになります。

DaikonはJava言語で作成されていますが、C++で作成されたフロントエンドが必要なため、移植性が低くなります。それにもかかわらず、多くの主要なプラットフォーム用のフロントエンドのビルドがオンラインで入手できます。Daikonチームはさらに、要望に応じて他のプラットフォームで必要なビルドの追加も行います。

(Daikonのダウンロード情報と詳細については、参考文献のセクションを参照。)

単体テストの対象範囲をチェックできる分析ツール

分析ツールは、プログラマーが強力な単体テスト・スイートを作成する際に役立ちます。これまでは、主に次の2つの方法によってそれが行われました。

  • 静的分析を使用して、単体テスト・スイートの自動生成を試みる
  • 静的分析を使用して、プログラムの機能に対する単体テスト・スイートの対象範囲を判別する

現在、コードから単体テストを自動的に生成しようとする無料のツールはいくつかありますが、そうした目的に取り組んでいる無料ツールのほとんどはまだアルファ・テストの段階にあります。比較的有望なものには、JUnitDocletやJUB (「JUnit test case Builder」の頭文字) などがあり、SourceForgeで利用できます (参考文献のセクションにリンクがあります)。

こうした種類のツールに関して注意すべき点は、これらのツールが、テストによるレガシー・コードの改良時に最も適合するということです。これらのツールは、新しいプロジェクトを作成する場合にはそれほど有効ではありません。

なぜでしょうか。その理由は、新しいプロジェクトは、それらに対する単体テストと連携して作成する必要があるからです。単体テストの開発は、設計を行うための有効な方法です。コンポーネントに対するAPIは、APIのテストが作成されるに伴って暗黙のうちに設計されます。さらに重要なのは、このように設計することにより、設計者に直ちにフィードバックがなされるという点です。設計が不適切であれば、テストの作成は困難になります。また、どのような分析ツールでも、プログラム用にどのようなテストを作成するか決定する設計者と同様、悪戦苦闘させられます。

2番目の種類の分析ツールは、プログラムとその単体テストを分析し、それらのテストがどの程度プログラムをカバーしているかを判別します。前述の1つめのツールと異なり、これらのツールは両方の種類のプロジェクトに対して有効です。実際に、エクストリーム・プログラミング・チームは、そのようなツールをコード・コミット・プロセスに統合することを検討する可能性があります。その場合、これらのツールでは、コードがすべてのテストにパスしない限りコードをコミットさせないだけでなく、コード全体に対するテストが行われない場合にもコードをコミットさせません。テスト対象は、怠慢だけでなく誤りによっても不十分なものとなる可能性があることから、こうした拡張はあらゆるレベルのスキル (および、誠実さ) のプログラマーにとって有効なものとなります。

このような分析を実行できる新しい、特に優れたツールはCloverです。Cloverは、make に代わる、Javaのみで作成された人気のある、Ant用のプラグインです。Cloverは市販のツールですが、オープン・ソースのプロジェクトには無料で利用できます。

Cloverは2段階のプロセスで機能します。まず、コンパイル時にコード生成が行われます。そして、テスト時にテストの実行に関する情報をデータベースに書き込み、レポート(GUI、Webページ、またはコンソールを介してなされる)の生成に使用します。

Antを使用する既存のプロジェクトへのCloverの統合は非常に簡単です。プロジェクト用のbuild.xmlファイルを調整して、コンパイル時のコード生成、テストのログ、レポートの生成のための2、3のターゲットを追加します。たとえば、ビルドとコンパイルのターゲットを持つbuild.xmlファイルがあるとします。やらなければならないことは、Cloverの JARファイルをAntライブラリー・ディレクトリーに置き、以下のようにbuild.xmlファイルを拡張するだけです (これらの、また同様のAntターゲットに関する情報は、Cloverユーザー・ガイドに記載されています。それを、便宜上、ここに含めました)。

リスト3. Cloverを使用するAnt build.xmlファイルの拡張
<property name="clover.initstring" value="/tmp/mycoverage.db"/>

<target name="with.clover">
    <property name="build.compiler"
            value="org.apache.tools.ant.taskdefs.CloverCompilerAdapter"/>
</target>

<path id="clover.classpath">
  <pathelement path="<CLOVER_HOME>/lib/clover.jar"/>
  <pathelement path="<CLOVER_HOME>/lib/velocity.jar"/>
</path>

 <target name="clover.report">
  <java classname="com.cortexeb.tools.clover.reporters.html.HtmlReporter">
   <arg line="--outputdir /tmp/clover_html --showSrc --initstring 
     $\{clover.initstring\} --title 'My Project'"/>
   <classpath refid="clover.classpath"/>
  </java>
</target>

clover.initstring プロパティーは、Clover対象データを書き込むファイルを指定します。ターゲットwith.clover は、他のターゲットの実行時 (compiletest など) にCloverをオンにするために使用されます。ターゲットclover.report は、累積された対象データを使用してレポートを生成するために使用されます。

上記のコードでは、HTMLレポートが生成されます。また、テキスト・レポート (テスト対象が受け入れ可能であるかどうかを判別するためのスクリプトに送る際に役立つ) やSwingベースのレポートも生成できます。

レポート生成プログラムの対象が、必要なすべてのクラスがある場所を認識するために、clover.classpath の設定が必要です。ただし、クラスパスにある2番目のJARファイル (velocity.jar) は、HTMLレポートの生成にのみ必要です。これが行われると、以下のコマンドによってCloverレポートが生成されます。

$ ant with.clover compile test
$ ant clover.report

このように簡単です。いくつかのサンプルの出力については、人気のあるコーディング・ツールであるJBossとAnt用のオンラインのCloverレポートでご確認ください (これらのダウンロード情報と詳細については、参考文献のセクションを参照)。

2つを組み合わせる

この記事で説明したツールは、プログラム分析と単体テストを個々に実行するよりも、連携して使用することで、より強力なインバリアント検出を実現できる有効な方法のいくつかに重点を置いています。しかし、こうしたテクニックは、可能な方法の氷山の一角にすぎません。

今後は、新たなツールが、さらに強力な単体テストの活用を可能にします。たとえば、型推論エンジンと最適化コンパイラーが、既存の単体テストからヒントを推論できるようになったり、UML生成ツールがテストから (クラス・ダイアグラムだけでなく) さまざまなダイアグラムを作成できるようになります。これらの手法を組み合わせた創造的な開発と実験には、より優れたコードの作成とトラブルシューティングができるようになる大きな可能性があります。

ここで、それぞれの方法の長所である次のような特徴を思い出してください。

  • 単体テストは、特定の実行の間にプログラムがどのように振る舞うのかを示すことができます。また、実行の一般的なパスを示すこともできます。
  • 分析ツールは、プログラムの特定の箇所について考えられるすべての実行をチェックできます。

それぞれの長所を使用して、互いの方法の潜在的な短所を補い合うことができます。

次回は、単体テストの拡張に関するもう1つの方法について取り上げ、GUIに対する単体テストの開発に利用できる最新ツールのいくつかについて検討します。


ダウンロード可能なリソース


関連トピック

  • Daikonについて理解を深めてください。Daikonは、C/C++ とJavaのフロントエンドによる動的なインバリアント検出のプロトタイプ実装です。Daikon Webサイトからダウンロードできます。
  • 実行中でないコードのセクションを発見し、テストが適切にコードを実行していない箇所を判別するツールの詳細については、Cloverユーザー・ガイドをご確認ください。
  • CloverはAnt (Javaベースのビルド・ツール) に緊密に統合されています。コピーをお持ちでない場合は、ダウンロードしてください。
  • JUnitDoclet は、XDocletと連携するオープン・ソースの単体テスト生成ツールです。
  • 単体テストは、エクストリーム・プログラミングの主要なプラクティスです。エクストリーム・プログラミングの実践については、developerWorks のRoy Miller氏のコラム「エクストリーム・プログラミングの神秘を解く」をご覧ください。
  • もう1つのオープン・ソースの生成ツールは、SourceForgeで利用できるJUB (JUnit test case Builder) です。
  • DrJava をダウンロードしてください。これは、統合されたread-eval-printループ、デバッガー、JUnitサポートを備えた、軽量のオープン・ソースの無料Java IDEです。
  • Eric Allen氏による、「Javaコードの診断」のコラムをすべてお読みください。
  • developerWorksJava technologyゾーンのJavaテクノロジーに関する他の記事やチュートリアルをご覧ください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=224129
ArticleTitle=Javaコードの診断: 単体テストと自動コード分析の連携
publish-date=10012002