ユニットテストの自動作成ツールを調べてみた(2019年末版)
こんにちは。もうすぐ2019年も終わりですね。この記事はソフトウェアテスト Advent Calendar 2019 の25日目の記事です。
前日の記事は、同僚の@ozhiro sanのあなたが自動テストを行う目的は何ですか?でした。感動した!
さて、今日はユニットテストの自動作成ツールの現状について調べてみました。
なぜ、調べようと思ったのか?
ユニットテストって大切ですよね?
ソフトウェアを日々開発、修正していく中で、ユニットテストは開発したソフトウェアの品質を確保するテストツールです。また、テスト実行は自動化できるので、テスト工数も削減できます。
しかし、ユニットテストを実行するためにはテストコードの開発が必要で、それなりに開発工数を必要とします。より網羅性などを求めると、テスト対象のソフトウェア開発と同じくらい時間がかかったりする場合もあります。
そのような背景もあり、開発工数や期間的が足りないからユニットテストは作成せず人手によるマニュアルテストのみって状況は割とよくあります。(今は違いますが、前職、前々職のプロジェクトはそんな感じでした。)また、既に膨大な量のソースコードがあって、今からユニットテストを作成したいけれども、どこから手をつければよいのかわからないって状況も少なくないと思います。
そこで思いました。現在、AIやRPA(Robotic Process Automation)がブームしているこんな世の中だから、ソースコードを解析してユニットテストを自動作成してくれるツールが既にこの世界には存在しているのではないかと。
2020年は、こんな感じになっているのではないか...? と。
どんなツールがあるか探してみる
早速、どんなツールがあるかインターネットで探してみます。検索条件は「generate unit test automatically」で、あと、私はJavaを主に使用しているエンジニアなので、「Java」も条件に加えます。
意外に検索結果は少なく古い記事がちらほら見られるので、一抹の不安...(´・ω・`)も感じましたが、よさげなものを4つほどピックアップしてみました。
- TestMe
- Squaretest
- EvoSuite
- Randoop
概要を調べるだけでは使い勝手などわからないので、実際のソースコードからテストコードを作成して評価しました。ソースコードとして、以下の2種類を用意しました。
1のメソッドにはいくつか分岐処理を追加して、テストのカバレッジ(網羅)率も評価に加えたいと思いました。
2はDI(Dependency Injection)されたインスタンスを持つクラスです。
DIされたインスタンスはテストコード上はMockitoのモックなどに置き換えてもらいたい。という思いがあります。
具体的なソースコードは作成したテストコードと一緒に、1プロジェクトとしてGitHub上に用意しました。所々、GitHubへのリンクを張っていますので、詳しくはそちらをご覧ください。
ツールの紹介と評価
それでは、それぞれのツールの紹介と生成されたテストコードを評価したいと思います。
TestMe
- https://plugins.jetbrains.com/plugin/9471-testme/
- IntelliJのプラグイン。
- テンプレート型で、ソースコードから決まったルールでテストコードを自動生成。
- テストコードのフレームワークはJUnit4、5などから選択可能。
生成されたテストコード(一部)は以下です。
DIされるインスタンスはMockitoを使ってモックされています。いいですね!
テストコードはテンプレートに従って生成されているだけなのでカバレッジ率はほぼなく、生成されたテストコードを元に別途開発する必要があります。ただ、自動生成のスピードは数秒ですし、一からテストコード作成と比較すると悪くない印象でした。
評価:
スピード:★★★★★ (数秒)
カバレッジ:
カスタマイズ:★★ (テストフレームワークの選択が可能)
class ZipCodeServiceTest {
@Mock
ZipCodeApi zipCodeApi;
@InjectMocks
ZipCodeService zipCodeService;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
void testSearch() throws IOException {
when(zipCodeApi.search(anyString())).thenReturn(null);
List<ZipCodeData> result = zipCodeService.search("zipCode");
Assertions.assertEquals(
Arrays.<ZipCodeData>asList(
new ZipCodeData("zipcode", "prefcode", "address1", "address2", "address3", "kana1",
"kana2", "kana3")), result);
}
}
Squaretest
- https://squaretest.com/
- IntelliJのプラグイン。
- TestMeと同じくテンプレート型で、ソースコードから決まったルールでテストコードを自動生成。
- テストコードのフレームワークはJUnit4、5などから選択可能。
- 有償ライセンス。ただし、30日間のトライアルが使用可能。
TestMeとほぼ同じかな。って印象ですが、設定画面でテンプレートを編集できます。
アサーションにはAssertJやHamcrestなど別ライブラリを使いたかったりするので、このあたりは使い勝手が良いですね。
__有償ライセンス__でなければ、使いたい感じです。
評価:
スピード:★★★★★ (数秒)
カバレッジ:
カスタマイズ:★★★★ (テストフレームワークの選択、テンプレートの編集が可能)
class ZipCodeServiceTest {
@Mock
private ZipCodeApi mockZipCodeApi;
private ZipCodeService zipCodeServiceUnderTest;
@BeforeEach
void setUp() {
initMocks(this);
zipCodeServiceUnderTest = new ZipCodeService(mockZipCodeApi);
}
@Test
void testSearch() throws Exception {
// Setup
final List<ZipCodeData> expectedResult = Arrays.asList(
new ZipCodeData("zipcode", "prefcode", "address1", "address2", "address3", "kana1", "kana2",
"kana3"));
// Run the test
final List<ZipCodeData> result = zipCodeServiceUnderTest.search("zipCode");
// Verify the results
assertEquals(expectedResult, result);
}
}
EvoSuite
- http://www.evosuite.org/
- IntelliJのプラグイン。コマンドライン実行でもテストコードの自動生成が可能。
- ただし、テストコードの生成には、別途Jarファイルが必要。
- また、テストコード実行にも別途ランタイムJarが必要。
- テンプレート型ではなく、カバレッジ率が高くなるよう、実際にソフトウェアを実行してテストコードを自動生成。
- テストコードのフレームワークはJUnit4。
2から作成したテストコードがいまいちだったので、1のテストコード(一部)を以下に記述します。
テストコードの生成には3分かかります。ただ、この辺りは生成時の引数で変更できます。
テストコードのカバレッジ率をみてみると... 100% すごい!
ただ、生成されたテストコードは可読性が悪く、後々人手でメンテナンスする気にはなれません。
よくわからないアノテーションも多く付与されていて、EvoSuiteの基底クラスを継承している点も少しいまいちです。
モックに関しても、1のテストコードを見てみましたがうまく設定されていませんでした。このIssueには、テストコード生成時に引数で調整可能と書かれているのですが、実行時にエラーが発生し、結局モックありのテストコードを生成することはできませんでした。(途中断念)
ただ、カバレッジ率は非常に良いので、なにかステートレスでPublicなAPIを作るときに、そのバグ発見用に活用するのが良いかな。って感じです。
評価:
スピード:★★ (3分)
カバレッジ:★★★★★ (100%!)
カスタマイズ:★★ (いくつかテストコード生成時に引数で指定可能)
@RunWith(EvoRunner.class) @EvoRunnerParameters(mockJVMNonDeterminism = true, useVFS = true, useVNET = true, resetStaticState = true, separateClassLoader = true, useJEE = true)
public class NumberUtils_ESTest extends NumberUtils_ESTest_scaffolding {
@Test(timeout = 4000)
public void test00() throws Throwable {
boolean boolean0 = NumberUtils.isOddNumberBetween1And50(50);
assertFalse(boolean0);
}
// omit other testcases.
Randoop
- https://randoop.github.io/randoop/
- Javaのライブラリ。コマンドラインで実行し、テストコードを生成。
- EvoSuiteと同じく、カバレッジ率が高くなるよう、実際にソフトウェアを実行してテストコードを自動生成。
- テストコードのフレームワークはJUnit4。
できることはEvosuiteとほぼ同じですね。カバレッジは同様に100%でした。よきよき。
テストコード生成のためにコマンドライン実行しかない分、使い勝手は悪いですが、生成されたテストコード実行にランタイムJarが必要ない点は良いかなと思いました。
評価:
スピード:★★ (数分)
カバレッジ:★★★★★ (100%!)
カスタマイズ:★★ (いくつかテストコード生成時に引数で指定可能)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class NumberUtilsTest {
public static boolean debug = false;
@Test
public void test01() throws Throwable {
if (debug)
System.out.format("%n%s%n", "RegressionTest0.test01");
// The following exception was thrown during execution in test generation
try {
boolean boolean1 = test.NumberUtils.isEvenNumberBetween1And10WithBadCode((-1));
org.junit.Assert.fail("Expected exception of type java.lang.IllegalArgumentException; message: input should be between 1 and 10.");
} catch (java.lang.IllegalArgumentException e) {
// Expected exception.
}
}
まとめ
以上、4つのユニットテスト自動作成ツールの調査と評価を行いました。正直、期待していたイメージとはほど遠いような気がしますが、現状を知る意味では良かったと思います。
この調査で使用したソースコード、テストコード、ライブラリ一式およびHowToのReadMeは、以下のGitHub上に保存しています。もし、これらのツールを使ってテストコードを作成してみたいなどあれば、参考にしていただければ幸いです。
Investigation of auto generation unit tests with Java
さて、長かったアドベントカレンダーも今日でおしまいです。読む方も書く方も、皆様お疲れさまでした!
それでは、良いお年を!