XSLTをユニットテストする(XSpec)
「Extensible Stylesheet Language (XSL)関連」といったら、こんな話もありなのかな?
ということで、15日目として参加してみます。XSLTでもユニットテストしたい
プログラム開発を続けているうちに、ユニットテストが必要になる日がやって来ます。言語がXSLTでも、それは同じです。
そんな日の役に立つ、XSpecというフレームワークを紹介します。
テストの書きかたとかまで説明しだすとキリがないので、ここでは、テストの実行や得られる結果の、ほんのさわりの紹介だけにとどめます。
XSpecとは
XSLTをユニットテストするためのフレームワークです。
『Beginning XSLT』などの著作もあるJeni Tennisonさんが2005年に原型を開発し、改良して2008年に名を改めてリリースしたのが、始まりのようです。
メンテナーが替わりながら、2020年12月現在も、改良やリリースは続いています。
ここでは例として取りあげませんが、XSLTだけでなく、XQueryやSchematronもテストできます。
XSpecの特長
依存関係が少ない
XSpec自体がXSLTで実装されているので、Saxon(Java版)さえあれば動きます。
シンプルな記法
テストシナリオの記法は、入力に対して期待する出力結果を書いていく、シンプルなものです。
いま現在XSLTを書けている人なら、難しくないと思います。
シナリオ例
<x:scenario label="h1要素をcapitalizeモードで変換すると">
<x:context mode="capitalize">
<h1>hello</h1>
</x:context>
<x:expect label="先頭が大文字になるはず">
<h1>Hello</h1>
</x:expect>
</x:scenario>
ここでは、x:context
の中が、テスト用の入力です。
その入力に対して期待される出力結果が、x:expect
の中に書いてあります。
XSLT 3.0対応
mapやarrayの入出力や、xsl:package
のテストもできます。
特定の入力に対してエラーの発生を期待するシナリオも書けます。
エラー発生を期待するシナリオの例
<x:scenario label="my:func()の引数が負のとき"
catch="true">
<x:call function="my:func">
<x:param select="-1" />
</x:call>
<x:expect label="エラーXTTE0570になるはず"
test="?err?code"
select="xs:QName('err:XTTE0570')" />
</x:scenario>
XSpecのインストール
-
Javaをインストールしておきます。
Saxonを動かすためなので、バージョンはJava 8、もしくはそれ以降。
JREだけで可。JDKは不要。 -
Saxon(Java版)をダウンロードして、
.jar
ファイルをどこかに置きます。
2020年12月現在、いちばん無難なバージョンは9.9.1.8です。
とにかく試してみたいだけなら、https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/9.9.1-8/Saxon-HE-9.9.1-8.jar
をダウンロードするだけ。 -
XSpec本体は、レポジトリをクローンするだけでよいです。
要は、https://github.com/xspec/xspec/archive/master.zip
をダウンロードして、.zip
ファイルの中身をどこかに展開します。
これで、実行準備は完了です。
とりあえずテストを実行してみる
何はともあれ、XSpecレポジトリにあるサンプルを使って、テストを実行してみましょう。
-
Windowsならコマンドプロンプト、*nixならターミナルを開きます。
-
環境変数
SAXON_CP
に、Saxonの.jar
ファイルのパスをセットします。例
-
Windows(コマンドプロンプト)
set SAXON_CP=C:\path\to\saxon9he.jar
-
Windows(PowerShell)
$env:SAXON_CP = "C:\path\to\saxon9he.jar"
-
*nix
export SAXON_CP=/path/to/saxon9he.jar
-
-
XSpecレポジトリの
bin
フォルダの、xspec.bat
(Windows)またはxspec.sh
(*nix)を実行します。
引数に、テストシナリオのファイル(.xspec
ファイル)を指定します。ここでは、XSpecレポジトリに存在する
tutorial/escape-for-regex.xspec
を指定してみます。例
-
Windows
bin\xspec.bat tutorial\escape-for-regex.xspec
-
*nix
bin/xspec.sh tutorial/escape-for-regex.xspec
-
-
テストシナリオがコンパイルされ、実行されます。
例
C:\xspec>set SAXON_CP=C:\Saxon\saxon9he.jar C:\xspec>bin\xspec.bat tutorial\escape-for-regex.xspec Creating XSpec Directory at C:\xspec\tutorial\xspec... Creating Test Stylesheet... Running Tests... Testing with SAXON HE 9.9.1.8 No escaping Must not be escaped at all Test simple patterns ..When encountering parentheses escape them. ..When encountering a whitespace character class escape the backslash result should have one more character than source When processing a list of phrases All phrase elements should remain Strings should be escaped and status attributes should be added FAILED Formatting Report... passed: 5 / pending: 0 / failed: 1 / total: 6 Report available at C:\xspec\tutorial\xspec\escape-for-regex-result.html Done.
最後のテストシナリオ(
Strings should be escaped and status attributes should be added
)でFAILED
と出ます。そのシナリオでテストが失敗したということです。
テスト結果レポート
テスト結果のレポートを見てみましょう。
テスト実行時にReport available at C:\xspec\tutorial\xspec\escape-for-regex-result.html
のように表示されていたとおり、そこにHTMLファイル形式のレポートが作成されています。
Webブラウザで開いて、失敗したシナリオの部分に注目すると、こんな感じです:
テスト結果レポート(の一部)
-
左側(Result)が、テストシナリオで設定した入力に対して、XSLTから出力された結果。
-
右側(Expected Result)が、期待していた出力です。
濃いピンクの部分が、結果と期待とが異なっているところです。
テストしたXSLTのどこかで、@status
属性の値設定をまちがえてる、ということですね。
テストカバレッジ
テストカバレッジをざっくり見ることもできます。
カバレッジ取得の実行例
コマンドラインだと-c
オプションです:
C:\xspec>bin\xspec.bat -c tutorial\coverage\demo.xspec
Creating XSpec Directory at C:\xspec\tutorial\coverage\xspec...
Creating Test Stylesheet...
Running Tests...
Collecting test coverage data...
****************************************
controller=net.sf.saxon.trans.XsltController@3c46e67a
Testing with SAXON HE 9.9.1.8
'iron' element
is transformed to 'shield' element
Formatting Report...
passed: 1 / pending: 0 / failed: 0 / total: 1
Formatting Coverage Report...
Report available at C:\xspec\tutorial\coverage\xspec\demo-coverage.html
Done.
テストカバレッジのレポート
レポートには、テスト対象のXSLT(xsl:include
やxsl:import
されたものも含む)の全行が、色分けされて表示されています。
赤い行が、テストシナリオの実行中に、一度も実行されなかった行です。
少なくとも、これらの行が実行されるようなテストを追加したほうがよい、ということですね。
使いどころ
開発のベースとなるXSLT/XQueryライブラリにユニットテストを書いておくと、特に有用です。リファクタリングや、依存関係を変更するときの、効率と安全性が格段に違います。
ドキュメントの形式的構造は別のスキーマでチェックして、内容寄りのチェックはSchematronスキーマで行う、というケースも多いと思います。テスト無しの状態でSchematronにエンバグすると気付きにくいので、そこでもユニットテストが有用です。
以上、XSLT(やXQueryやSchematron)にもユニットテストのフレームワークがあるという話でした。
Discussion