C#のテキストテンプレートを使ってみた(2)
ランタイムテキストテンプレートを使ってgoogletestのレポートをHTML形式に変換してみた

どもです。

前回は、C#のランタイムテキストテンプレートを使用してHTMLのtableタグを生成しました。
今回のエントリでは、これを少し発展させて、実際にHTML形式のファイルを生成してみます。

1.背景と目的

今回のエントリの背景と内容を、簡単ですが書きます。

1.1.背景

最近、C/C++(VC++)のコードの単体テストを真面目に実施しています。
単体テストのフレームワークは、googletestを使用しています。
この単体テストに対して、実施した単体テストの件数を知りたくなりました。
1つ1つ数えても良いのですが、それはメンドクサイ大変なので、自動で計算できる仕組みがほしくなりました。
また結果(OK/NG)についても、同時に知りたくなりました。

1.2.内容

今回のエントリでは、googletestの実行結果(XML)をC#のランタイムテキストテンプレートを用いてHTML形式に変換する方法、コードについて書きます。

2.開発環境

今回の開発環境は、以下の通りです。

OS Windows10 Pro
ver.1903
CPU i7-8700
IDE VisualStudioCommunity2019
ver.16.5.1
.Net Framework 4.7
アプリケーションの種類 コンソールアプリケーション
googletest 1.10.0

googleテストのダウンロードやビルドについては、以下のリンク先を参照してください。

3.googletestのレポート

まず、googletestのレポート生成です。
googletestでは、以下のコマンドでXML形式のレポートが生成できます。

(googletestの実行ファイル) --gtest_output=xml:(xmlファイルの出力先)

これにより、実行結果がXML形式で出力されます。

4.HTML形式の変換

次は、XMLを読み込んでHTML形式のレポートに変換する処理です。
C#では、以下の順番で処理を実行します。

  1. XMLを読み込んで、オブジェクトに変換する。
  2. ランタイムテキストテンプレートを用いて、HTML形式に変換する。

この2つの手順を、それぞれ実現していきます。

4.1.XMLを読み込んでオブジェクトに変換する

まず、XMLファイルを読み込んで、C#でのオブジェクトに変換します。
C#では、XMLファイルの読み込みに対応した「XmlSerializer」クラスが標準で用意されています。
具体的なコードは、以下!

using (var xmlReader = new StreamReader(fileInfo.FullName, Encoding.GetEncoding("UTF-8")))
{
	var serializer = new XmlSerializer(typeof(TestSuites));
	var testSuites = (TestSuites)serializer.Deserialize(xmlReader);
}

ここで「TestSuites」クラスは、googletestのXML形式のレポートの内容を保持するモデルクラスです。
具体的には、以下のような構成になっています。

この図に従って、かつXMLの各要素/タグに対応づけながらモデルクラスを実装したのが以下!

<code>[XmlRoot("testsuites")]
public class TestSuites
{
	[XmlAttribute("tests")]
	public int Tests { get; set; }

	[XmlAttribute("failures")]
	public int Failures { get; set; }

	[XmlAttribute("disabled")]
	public int Disabled { get; set; }

	[XmlAttribute("errors")]
	public int Errors { get; set; }

	[XmlAttribute("time")]
	public float Time { get; set; }

	[XmlAttribute("timestamp")]
	public DateTime TimeStamp { get; set; }

	[XmlAttribute("name")]
	public string Name { get; set; }

	[XmlElement("testsuite")]
	public List<TestSuite> TestItems { get; set; }

	public string TestName { get; set; }

	public string HtmlFileName { 
		get
		{
			return this.TestName + ".html";
		}
	}
}

[XmlRoot("testsuite")]
public class TestSuite
{
	[XmlAttribute("name")]
	public string Name { get; set; }

	[XmlAttribute("tests")]
	public int Tests { get; set; }

	[XmlAttribute("failures")]
	public int Failures { get; set; }

	[XmlAttribute("disabled")]
	public int Disabled { get; set; }

	[XmlAttribute("errors")]
	public int Errors { get; set; }

	[XmlAttribute("time")]
	public float Time { get; set; }

	[XmlAttribute("timestamp")]
	public DateTime TimeStamp { get; set; }

	[XmlElement("testcase")]
	public List<TestCase> TestCases { get; set; }
}

[XmlRoot("testcase")]
public class TestCase
{
	[XmlAttribute("name")]
	public string Name { get; set; }

	[XmlAttribute("status")]
	public string Status { get; set; }

	[XmlAttribute("result")]
	public string Result { get; set; }

	[XmlAttribute("time")]
	public float Time { get; set; }

	[XmlAttribute("timestamp")]
	public DateTime Timestamp { get; set; }

	[XmlAttribute("classname")]
	public string ClassName { get; set; }

	[XmlElement("failure")]
	public Failure Failure { get; set; }

	public string Judge
	{
		get
		{
			if (null == this.Failure)
			{
				return "OK";
			}
			else
			{
				return "NG";
			}

		}
	}
}

[XmlRoot("failure")]
public class Failure
{
	[XmlAttribute("message")]
	public string Message { get; set; }
}

なお、各プロパティに対して、「XmlElement」属性を設定しています。
これにより、Deserializeにより、各プロパティに対応するXML要素の値が格納されます。
属性の使い方については、属性を使用した XML シリアル化の制御が参考になるかと思います。

4.2.ランタイムテキストテンプレートを用いてHTML形式に変換する

XMLの内容の取得ができたので、実際にHTML形式に変換します。

4.2.1.ページの構成

今回生成されるHTML形式のレポートは、以下のページ構成にします。

4.2.2.index.htmlのテンプレート

トップページになるindex.htmlのテンプレートは、以下にします。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8"/>
		<meta http-equiv="X-UA-Compatible" content="IE=EDGE,chrome=1" />
		<title>Test report - overview and content</title>
		<link rel="stylesheet" type="text/css" href="report.css" />
	</head>
	<body>
		<div>
			<h1>Test report</h1>
		</div>
		<div>
			<table>
				<caption>Overview</caption>
				<tbody>
					<tr>
						<th>Tests</th>
						<th>Failures</th>
						<th>Disables</th>
						<th>Errors</th>
						<th>Time</th>
					</tr>
					<tr>
						<td><#= TestNum #></td>
						<td><#= FailureNum #></td>
						<td><#= DisableNum #></td>
						<td><#= ErrorNum #></td>
						<td><#= TimeNum #></td>
					</tr>
				</tbody>
			</table>
		</div>
		<div>
			<table>
				<caption>Content</caption>
				<tbody>
					<tr>
						<th>テスト名</th>
						<th>Tests</th>
						<th>Failure</th>
						<th>Disables</th>
						<th>Errors</th>
						<th>Time</th>
						<th>Timestamp
					</tr>
					<# foreach (var testSuiteItem in TestSuitesList) { #>
					<tr>
						<td><a href="<#= testSuiteItem.HtmlFileName #>"><#= testSuiteItem.TestName #></td>
						<td><#= testSuiteItem.Tests  #></td>
						<td><#= testSuiteItem.Failures #></td>
						<td><#= testSuiteItem.Disabled #></td>
						<td><#= testSuiteItem.Errors #></td>
						<td><#= testSuiteItem.Time #></td>
						<td><#= testSuiteItem.TimeStamp #></td>
					</tr>
					<# } #>
				</tbody>
			</table>
		</div>
	</body>
</html>

前半部分、「Overview」テーブルで、実行したテストの合計情報を表示します。
次の「Content」テーブルで、テスト毎の合計情報を表示します。
更に「Content」テーブルの名前から各テストの詳細ページに移動できるよう、リンクを設定しています。

4.2.3.test_suite_name.htmlのテンプレート

次に、各テストケースのページのテンプレートは、以下にします。

<!DOCTYPE html>
	<html>
	<head>
		<meta charset="utf-8"/>
		<meta http-equiv="X-UA-Compatible" content="IE=EDGE,chrome=1" />
		<title>Test report<#= TestSuites.Name #></title>
		<link rel="stylesheet" type="text/css" href="report.css" />
	</head>
	<body>
		<div>
			<div>
				<h1>Test report - detail</h1>
			</div>
			<div>
				<a href="index.html">TOP</a>
			</div>
			<div>
			<# foreach (var testSuite in TestSuites.TestItems) { #>
				<table>
					<caption><#= testSuite.Name #></caption>
					<tbody>
						<tr>
							<th>test name</th>
							<th>State</th>
							<th>Result</th>
							<th>Judge</th>
							<th>Timestamp</th>
							<th>Time</th>
						</tr>
						<# foreach (var testCase in testSuite.TestCases) { #>
						<tr>
							<td><#= testCase.Name #></td>
							<td><#= testCase.Status #></td>
							<td><#= testCase.Result #></td>
							<td><#= testCase.Judge #></td>
							<td><#= testCase.Timestamp #></td>
							<td><#= testCase.Time #></td>
						</tr>
						<# } #>
					</tbody>
				</table>
			<# } #>
			</div>
		</div>
	</body>
</html>

このページは、ひたすらテストの結果の出力を繰り返すだけです。

以上で、HTML形式の変換は完了です。

5.出力結果

これまで紹介したコードを実装、実行した結果を示します。
まずはトップ画面!

次に、各テストケースに詳細画面!

今回示した例ではNGの項目のエラー情報へのリンクは設定できていませんが、最も基本的な内容である実行件数とOK/NGの結果は確認できます。
また、CSSは一切設定していないので、ホントに文字だけの画面になってします。
画面のデザインは、index.htmlと同じフォルダに「report.css」という名前のCSSファイルを読み込むようになっています。
なので、各々の環境に合せて設定してください。

6.まとめ

今回は、ランタイムテキストテンプレートを使用して、googletestのXML形式のレポートをHTML形式に変換する内容について書きました。
googletestの内容を、特に加工何もせずにそのまま出力するだけですが、簡単にできました。
ランタイムテキストテンプレートを少し変更することで、情報を絞ったり、もっと見易い表示にできるのではないかと思います。

ではっ!

ex.公開しています

今回紹介したツールの全コードを、GitHubで公開しています。
エントリ中では紹介しきれなかった内容は、コチラを参照してください。
さらに言えば、使てみていただけると幸いです。