皆さん、ユニットテスト書いてますか? 書いてますよね? 書いててくださいね!
という謎のテンションから始めましたが、どんな言語であれUnitTestは開発の生産性を上げるための効果的なテクニックです。
ただ、いわゆるテストが無いレガシーコードというものもこの世にはたくさんあります。そして、それのメンテをしないといけない機会も。
どうするかと言えば、数多の条件分岐を確認し自分でカバレッジを満たす自動テストを組み上げるか、他の部分は見なかったことにして直したところだけを修正して無事バグが出ないかを天に祈るしかありません。
もし、良い感じにカバレッジを満たすテストデータが自動生成されたら? 何度それを夢見たことでしょう。そんなときに役立つかもしれないのがEvoSuite。
こいつはJavaのユニットテストツールであるJUnitのテストケースを「テストカバレッジを満たす状態」で自動生成します。
EvoSuite - Automatic Test Suite Generation for Java
http://www.evosuite.org/evosuite/
Mavenで使ってみる
私は普段NetBeansで開発しているのでMavenを利用した利用例を記載します。なお、サンプルプロジェクトはkoduki/example-evosuiteより入手できます。
まずはpomにリポジトリを追加します。
<repositories>
<repository>
<id>EvoSuite</id>
<name>EvoSuite Repository</name>
<url>http://www.evosuite.org/m2</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>EvoSuite</id>
<name>EvoSuite Repository</name>
<url>http://www.evosuite.org/m2</url>
</pluginRepository>
</pluginRepositories>
つづいてプラグインを追加
<pluginManagement>
<plugins>
<plugin>
<groupId>org.evosuite.plugins</groupId>
<artifactId>evosuite-maven-plugin</artifactId>
<version>${evosuiteVersion}</version>
<executions>
<execution>
<goals>
<goal> prepare </goal>
</goals>
<phase> process-test-classes </phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>${customFolder}</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<configuration>
<properties>
<property>
<name>listener</name>
<value>org.evosuite.runtime.InitializingListener</value>
</property>
</properties>
</configuration>
</plugin>
</plugins>
</pluginManagement>
最後に依存を追加します。
<dependency>
<groupId>org.evosuite</groupId>
<artifactId>evosuite-standalone-runtime</artifactId>
<version>${evosuiteVersion}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
これで準備は完了です。
テストケースを作成してみる
まずはテスト対象のコードを書きます。シンプルに以下で。
public class FizBuzz {
public static String apply(int num) {
if (num % 15 == 0) {
return "FizzBuzz";
} else if (num % 5 == 0) {
return "Fizz";
} else if (num % 3 == 0) {
return "Buzz";
} else {
return String.valueOf(num);
}
}
}
つづいてテストケースの生成をします。
$ mvn clean evosuite:generate evosuite:export
generateだけだと.evosuiteにメタ情報を作り、実際の生成はexportでやってるっぽいです。
生成されるテストケースはこちら
@RunWith(EvoRunner.class) @EvoRunnerParameters(mockJVMNonDeterminism = true, useVFS = true, useVNET = true, resetStaticState = true, separateClassLoader = true, useJEE = true)
public class FizBuzz_ESTest extends FizBuzz_ESTest_scaffolding {
@Test(timeout = 4000)
public void test0() throws Throwable {
String string0 = FizBuzz.apply((-74));
assertEquals("-74", string0);
}
@Test(timeout = 4000)
public void test1() throws Throwable {
String string0 = FizBuzz.apply((-9));
assertEquals("Buzz", string0);
}
@Test(timeout = 4000)
public void test2() throws Throwable {
String string0 = FizBuzz.apply((-1570));
assertEquals("Fizz", string0);
}
@Test(timeout = 4000)
public void test3() throws Throwable {
String string0 = FizBuzz.apply(660);
assertEquals("FizzBuzz", string0);
}
@Test(timeout = 4000)
public void test4() throws Throwable {
String string0 = FizBuzz.apply(1114);
assertEquals("1114", string0);
}
@Test(timeout = 4000)
public void test5() throws Throwable {
FizBuzz fizBuzz0 = new FizBuzz();
}
}
実行してみると適切に結果が生成されました。
Tips
そのまま事項するとJaCoCO等で適切にカバレッジが取れないことがあります。
その場合は@EvoRunnerParameters
のseparateClassLoader
をfalseにすることでJaCoCOでカバレッジ計測ができるようになりました。
まとめ
まだ、自分の触ってる業務プロダクトには試してないのでどのくらい実用性があるかは分かりませんが、レガシーコードと戦うためのきわめて強力な武器になりそうです。
公式サイト等の情報を見るとOSSのライブラリへ適用して動作検証とかもされてるので今回のサンプルのような超シンプルケースしか使えないという分けでは無さそう。
ただ、気を付けないといけないのはあくまでカバレッジを満たしたテストケースというだけで、適切なテストケースとは限らないので、その点だけは忘れずに使うことが必要ですね。
それでは来年もHappy Hacking!