以前の記事はFindBugsバグパターンの実装方法を試行錯誤している最中に作成したものでしたが、今回はその結果どのような開発手法に行き着いたかをまとめます。Mavenベースです。
前提
findbugs.xml
をsrc/main/resources
に置いてしまうと単体テスト実行時にエラーが発生してしまう(前回記事参照)- テストが面倒
- Mavenプラグイン、Antタスク、SonarQubeなど様々な利用方法があり得る
拡張すべきクラスについては前回記事を参照してください。
ビルドのTips
メタファイルをどうpackageするか
findbugs.xml
をsrc/main/resources
に置いてしまうことによるエラーを回避するために、メタファイルをsrc/main/meta
ファイルに保存してprepare-packageフェーズにリソースに追加するという手法を取ります。プロファイルを利用することもなくシンプルな解決になります。
Mavenを実行するたびにcleanを実行しなければならないという制約は付きますが、Detector用プロジェクトは基本的に小さいものになるはずですので問題にはならないでしょう。プラグイン設定は以下のようになります。
<plugin> <!-- Copy meta files to default outputDirectory at "prepare-package" phase. Because of findbugs specification, an edu.umd.cs.findbugs.PluginDoesntContainMetadataException instance would be thrown at "test" phase if we put these meta files on '/src/main/resources'. We have to copy these meta files after testing. See "How to build FindBugs plugin with Maven" thread in FindBugs mailing list (findbugs-discuss@cs.umd.edu) to get detail and other solution. Note: You should execute "clean" phase before you execute "test" phase. --> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <phase>prepare-package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${project.build.outputDirectory}</outputDirectory> <resources> <resource> <directory>src/main/meta</directory> </resource> </resources> </configuration> </execution> </executions> </plugin>
依存ライブラリをパッケージする
findbugs-maven-pluginだけでなくSonarQubeなどでの利用も検討している場合、依存ライブラリも同梱してしまうと便利です。私はmaven-shade-plugin
でこれを実現しています。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> </configuration> </execution> </executions> </plugin>
テストのTips
テストには大きく分けて2通りの方法を採用しています。FindBugs本体が提供する機構はドキュメントが無いため現時点では使わず、外部ライブラリに頼った手法のみを利用しています。
test-driven-detectors4findbugs による単体テスト
youdevise/test-driven-detectors4findbugs @ GitHubはDetectorをtest-drivenな開発手法で作成できる優れたライブラリです。FindBugs 2.0.3にはまだ対応できていませんが、多くの場合問題にはならないでしょう。
使い方は非常にシンプルで、テスト用のBugReporterをDetectorに渡してからassertXxx
メソッドでバグが発見されるかどうかを調べるだけです。
// quoted from https://github.com/WorksApplications/findbugs-plugin/blob/1c1930cacd0fe2aed9b41633b9b72d6a9b50e07e/src/test/java/jp/co/worksap/oss/findbugs/jsr305/BrokenImmutableClassDetectorTest.java public class BrokenImmutableClassDetectorTest { private BrokenImmutableClassDetector detector; private BugReporter bugReporter; @Before public void setup() { bugReporter = bugReporterForTesting(); detector = new BrokenImmutableClassDetector(bugReporter); } @Test public void testEnumIsImmutable() throws Exception { assertNoBugsReported(When.class, detector, bugReporter); } @Test public void testMutableClass() throws Exception { assertBugReported(MutableClass.class, detector, bugReporter, ofType("IMMUTABLE_CLASS_SHOULD_BE_FINAL")); assertBugReported(MutableClass.class, detector, bugReporter, ofType("BROKEN_IMMUTABILITY")); } }
この方法は簡単かつ充分なテストが行えるため、まず最初に導入したいところです。ただしこのライブラリはfindbugs.xml
のようなメタファイルは参照しないため、メタファイルと実装の不整合を見つけることはできません。実際に実行を試せる環境を別途用意するか、次に述べる統合テストを併用する必要があります。
なお依存関係は以下のようになります。
<dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>findbugs</artifactId> <version>2.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>1.3</version> <scope>test</scope> </dependency> <dependency> <groupId>com.youdevise</groupId> <artifactId>test-driven-detectors4findbugs</artifactId> <version>0.2.1</version> <scope>test</scope> </dependency>
実際にFindBugsを実行した結果を解析
eller86/findbugs-slf4jで利用している方法です。Mavenのマルチモジュールを利用したやや複雑な構成です。
まずはDetector実装を含むサブモジュールを作成します。次にDetectorによる解析を行うためのクラスファイルをsrc/main/java
に持つサブモジュールを作成し、pre-integration-testフェーズでfindbugs-maven-pluginを実行することで、その結果をintegration-testフェーズで解析・照合することが可能になります。
解析用サブモジュールをDetectorサブモジュールにtestスコープで依存させることで、mvn clean install
実行時にfindbugs-maven-pluginが常に新しい実装を利用することを保証できます。
この手法は実際にfindbugs-maven-pluginを利用するため、FindBugsプラグインが期待通りに動作すること、すなわちメタファイルの整合性も含めた動作保証が可能になります。XMLパーサを用意するなど実施コストは高くなりますが、この動作保証を手動で行う手間を考えるとペイすると言えるでしょう。
プラグイン設定は以下のようになります。src/test/java
にあるテストケースをintegration-testフェーズで実行するためにmaven-surefire-pluginに2つのexecutionを設定しています。
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <version>2.5.2</version> <executions> <execution> <phase>pre-integration-test</phase> <goals> <goal>findbugs</goal> </goals> </execution> </executions> <configuration> <threshold>Low</threshold> <plugins> <plugin> <groupId>my.groupId</groupId> <artifactId>my.detector</artifactId> <version>0.0.1-SNAPSHOT</version> </plugin> </plugins> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.12</version> <executions> <execution> <id>default-test</id> <phase>test</phase> <configuration> <skip>true</skip><!-- all test case should run at integration-test phase --> </configuration> </execution> <execution> <id>default-integration-test</id> <phase>integration-test</phase> <goals><goal>test</goal></goals> </execution> </executions> </plugin>