ちょっと話題の記事

JUnitの実行結果のXMLフォーマット

2013.10.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Jenkinsでは、JUnitのテスト結果からテスト件数や実行時間などを集計することが出来ます。この時、JUnitの実行結果はXML形式のファイルとして出力され、「JUnitのXMLファイル」などと呼ばれています。ところが、このXMLのフォーマットは、JUnitの公式フォーマットではありません。JUnit自体には実行結果をXML形式に出力する機能は実装されていないため、Ant, Maven, Gradleといったビルドツールによって出力されています。恐らくはAntが出力していたJUnitの実行結果のXMLフォーマットに、Eclipseなどの他のツールが対応していき、結果としてデファクトスタンダードとなったと思われます。

Jenkinsでは、デフォルトでJUnitのXMLを集計できるため、他のテストツールを使ってテストを実行した場合にも、JUnitのXML形式に変換すれば、簡単にJenkinsで集計が可能です。今回は、このJUnitのXMLフォーマットについて簡単に解説したいと思います。

XML Schema

JUnitのXMLフォーマットには、公式な仕様というのがないようです。ですが、CIツールとしてデファクトスタンダードとなっているJenkinsのソースコードに、JUnitのXMLファイルのXML Schemaが定義されているため、この定義を正とするのが良いと思われます。

ただ、この定義は非常に汎用的なフォーマットとして定義されているためもう少しシンプルに記述できます。

サンプルXML

はじめにサンプルとなるXMLを確認してみましょう。次のXMLは、4件のテストを実行し、それぞれ成功・失敗・エラー・スキップとなった状態です。

<?xml version="1.0" ?>
<testsuite name="info.classmethod.testing.HogeTest" tests="4" errors="1" failures="1" time="0.003">
  <testcase classname="info.classmethod.testing.HogeTest" name="testCase01" time="0.001" />
  <testcase classname="info.classmethod.testing.HogeTest" name="testCase02" time="0.001">
    <failure type="java.lang.AssertionError" message="test failed" >java.lang.AssertionError: test failed
	at org.junit.Assert.fail(Assert.java:88)
	at jp.classmethod.testing.examples.core.runner.DynamicTestsRunnerExample$1.invokeTest(DynamicTestsRunnerExample.java:33)
	at jp.classmethod.testing.core.runner.DynamicTestsRunner.invokeTest(DynamicTestsRunner.java:86)
	at jp.classmethod.testing.core.runner.DynamicTestsRunner.run(DynamicTestsRunner.java:68)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)</failure>
  </testcase>
  <testcase classname="info.classmethod.testing.HogeTest" name="testCase03" time="0.001">
    <error type="java.lang.NullPointerException" message="NPE" >java.lang.NullPointerException: NPE
	at jp.classmethod.testing.examples.core.runner.DynamicTestsRunnerExample$1.invokeTest(DynamicTestsRunnerExample.java:36)
	at jp.classmethod.testing.core.runner.DynamicTestsRunner.invokeTest(DynamicTestsRunner.java:86)
	at jp.classmethod.testing.core.runner.DynamicTestsRunner.run(DynamicTestsRunner.java:68)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)</error>
  </testcase>
  <testcase classname="info.classmethod.testing.HogeTest" name="testCase04" time="0.000">
    <skipped/>
  </testcase>
  <system-out><![CDATA[sdtout!]]></system-out>
  <system-err><![CDATA[stderr!]]></system-err>
</testsuite>

スキーマ定義

サンプルを読めば何となくは理解できると思いますが、簡単に解説します。

testsuite要素

ルート要素となるのは、testsuite要素です。これは、XMLファイルをテストクラス毎に作成する場合に最も妥当な粒度となります。本来は、さらにtestsuites要素でラップすることで複数のtestsuiteを1つのファイルにまとめることも可能です。testsuite要素には、属性としてname, tests, errors, failures, timeなどを設定します。

name属性はテストスイート名になるため、テストクラス名を指定してください。Javaの場合、フルパッケージ名でテストクラス名を指定するのが標準です。Jenkinsなどで表示されるときにテストのグループ名として使われるので、重複がないようにすれば自由につけて大丈夫です。

tests属性はそのtestsuiteに含まれるテストの総件数です。

errors属性はそのtestsuiteに含まれるエラーとなったテストの総件数で、failures属性はそのtestsuiteに含まれる失敗となったテストの総件数です。JUnitではAssertionErrorとなってテストが失敗した場合はfailures、それ以外の例外が発生してテストが失敗した場合はerrorsとするのが標準です。

time属性には、testsuiteに含まれるテストの総実行時間を秒で設定します。

この他にもhostnameなどの属性がありますが、任意に設定してください。

testcase要素

testsuite要素の子要素としてtestcase要素をテストケース毎に追加します。testcase要素はテストケース、すなわちテストメソッドに対応します。testcase要素の子要素にerror要素、failure要素、skiped要素のいずれかが含まれている場合、テストは成功ではなくエラー、失敗、スキップとなります。testcase要素には、属性としてclassname, name,timeを設定します。

classname属性はそのテストケースの属するクラス名です。この値はtestsuiteのname属性と同じとするのが良いでしょう。

name属性はテストケースの名前です。JUnitのテストであればメソッド名がここに設定されます。

time属性には、そのテストケースの実行時間を秒で設定します。

failure要素

failure要素はtestcase要素の子要素として追加できます。テストが失敗した場合に追加してください。failure要素には、属性としてtype, messageを設定します。

type属性は、失敗の種類を設定しますが、標準的には例外クラス名を設定するようです。failure要素の場合は、java.lang.AssertionErrorとなるでしょう。

message属性には、例外に含まれるメッセージを設定します。

failure要素にはさらにテキストとして、失敗した時の情報を設定します。JUnitでは、例外のスタックトレース情報設定するのが標準です。

error要素

error要素はテストが失敗でなくてエラーだった場合に利用されます。それ以外はfailure要素と同様です。

skipped要素

skipped要素はテストがスキップされた時に追加されるtestcase要素の子要素です。属性などはありません。

system-out要素

system-out要素はテスト実行時に標準出力に出力された情報をCDATAとして記述します。各テストケース毎に記述する事もできますが、テストクラス(testsuite)毎に設定する方が標準的なようです。

system-err要素

標準エラー出力について記述する以外はsystem-out要素と同様です。

というわけで、あまりJUnitのXMLフォーマットについて日本語の情報がなかったので解説してみました。なお、生成したXMLファイルはEclipseで開くとJUnitの結果ビューで確認する事ができます。

junit_xml