概要

reftest-runnerというブラウザで描画内容やレイアウトといった表示結果をテストするためのライブラリを作りました。

要素技術としてはブラウザ、WebDriver API、レンダリングキャプチャ、画像Diffという感じです。

時間が無い人向け

以下のスライドに簡単にreftest-runnerやreftestとはなにか、どういうユースケースがあるのかが書いてあります。

reftestとは

reftest(Referrence Test)とは、2つのHTMLの表示結果(スクリーンショット)を比較することで表示結果が意図したものかをテストする方法です。

用意するHTMLとして以下の2種類を1セットとして用意して利用します。

  • テスト用HTML
    • テストしたい機能を使って実装したHTML
  • リファレンス用HTML
    • テスト用HTMLとは別の機能で実装したHTML

この2つのHTMLをブラウザで表示し、スクリーンショットを取って画像として比較することで、2つのHTMLの表示が一致する OR 一致しないことを自動的にテストするためのツールが今回作ったreftest-runnerです。

ref-test

詳しくは以下でも書かれていますが、FirefoxやWebkit、Chormeといったブラウザで使われていて、こういうのをもっと普通のウェブ開発レベルでも使いたくなったのでこれをインスパイアしています(完全に一致というわけでもないかも…)

reftestは、画像のキャプチャを保存しておいて比較するのと違って、HTMLがテストファイルとなっています。 そのため、reftest向けに作ったHTMLは自動テストだけじゃなくて、普通にHTMLを開いて目視のテストや機能のデバッグなどにも使えるので多少面倒ですが意外と使い道があったりします。

また画像自体を保存して画像比較するテストは管理が大変なので、reftestのような形式のほうが柔軟性がある感じです。

画像比較ベースのテストは変化に弱く保守が大変なので、 一年ほど前に Reftests と呼ばれる新しいタイプのテストがサポートされた – 炭坑の庭師 - steps to phantasien

reftest-runnerのできること

  • HTMLの描画結果の比較テスト
    • いわゆるreftestができます
    • 非同期テストも対応しています
  • クロスブラウザの描画比較テスト
    • 通常は同じブラウザ同士で別HTMLの表示比較をしますが、同じHTMLを別ブラウザで表示比較が可能
    • WebDriverサポートしてるブラウザ - IE、Firefox、Chrome、Safari、Opera、PhantomJSなどで動作します
  • 比較結果のDiff画像を生成
  • テスト結果をTAP形式で出力
  • テスト用のローカルサーバを立てる
  • reftest.list という形式でテスト対象を記述

reftest-runnerはrunnerという名前になってて一応コマンドラインツールとしても動かせますが、本質的にはNodeモジュールとして使えるような作りになっています

Diff画像とは以下の様なテスト対象とリファレンスのキャプチャ画像を比較して差分を出す画像で、yahoo/blink-diffを使っています。

diff

この画像の差が一定のしきい値(delta、設定可能)を超えると一致してないと判定されます。

表示結果さえあれば何でもテストできるので、テストが難しいCanvas APIのテスト等もできます。


インストール

npm install reftest-runner -g

でインストールできます。インストールするとreftest-runnerというコマンドが追加されます。

動かし方

リポジトリにサンプルが入ってるので、このreftestを動かしてみたいと思います。

先ほどでてきたreftest.listというテキストファイルに、比較するHTMLと比較演算子(== or !=)を並べるのが簡単な使い方です。

サンプルのreftest.listでは以下のようになっています。

# implementation is difference, but visual is the same
== ./equal/smile-canvas.html ./equal/smile-img.html
# ◀ != ▶
!= ./non-equal/canvas-left.html ./non-equal/canvas-right.html
# async test support
== ./async/reftest-async-xhr.html ./async/reftest-sync.html

例えば、

== ./equal/smile-canvas.html ./equal/smile-img.html

は”./equal/smile-canvas.html./equal/smile-img.htmlの描画結果が一致することを期待する” 事を示しています。

この作成したreftest.listをCLIで以下のように指定すれば、reftestが実行できます。

$ reftest-runner --list path/to/reftest.list

もちろん、reftest.listなしでもできて、以下のようにテスト用ファイルとリファレンスファイルをそれぞれ指定することでも実行できます。

$ reftest-runner --browser "firefox" --targetA path/to/fileA.html --targetB path/to/fileB.html

--browserで実行ブラウザも指定できて、IE/Firefox/Chrome/phantomjs/iPhone/AndoirdのようなSelenium WebDriverがあるものをサポートしてます。 (SeleniumHQ/selenium に依存してます)

実行すると、logsディレクトリにスクリーンショットのdiff画像が生成されます。

実行してる動画

非同期のテスト

何か非同期の処理が終わってからその描画テストしたい場合は非同期テストにする必要があります。

reftest-runner/example/async at master · azu/reftest-runnerに非同期テストのサンプルがあります。

対象のHTMLでhtmlのクラス属性にreftest-waitが含まれている場合、このHTMLは非同期テストの対象として認識します。

<html class="reftest-wait">

非同期処理が終わったらこのクラスを削除したら、reftest-runnerは描画完了したと判断してその時点の描画内容を比較に使用します。

// html classの中身を空にする = 非同期処理完了
document.documentElement.className = "";

これは既存のブラウザが使ってるreftestツールがこういう形式を取っていたのでそれに合わせた形です。

reftestの使いドコロ

リグレッションテスト

典型的な使いドコロとしてはリグレッションテストがあると思います。

あるUIライブラリの構造を多く変更しようとした時に、APIのインターフェースも変わる場合ユニットテストだとやりにくいケースが出てきます。 そこで、以前のバージョンを使って書いたUIをリファレンスとして、作りなおしたライブラリで同じUIが再現できているかをチェックするテストを書くという使い道があります。

Canvasのテスト

Canvasは描画されている内容がピクセルでしか取れないので、ある描画上手くできてるのかのテストが難しいです。

Canvasの描画内容とimgタグでの画像の表示を比較したりすることでテストが出来ます。

Canvasのライブラリを書いている場合は、そのライブラリを使って書いたものをテスト対象として、素のCanvas APIで書いたリファレンス対象として用意して比較したりできます。

クロスブラウザの表示一致テスト

reftest-runnerはモジュールとして使えば、FirefoxとChromeで同じHTMLの表示結果を比較できるので、ブラウザの差を吸収しているライブラリがほぼ同じ表示になるかのチェックできます。

blinkDiffdeltaオプションでどれくらいの差を許容するかを決定できます。

var ReftestEngine = require("reftest-runner").Engine;
var testEngine = new ReftestEngine({
    server: {
        port: 8989
    },
    rootDir: __dirname,
    blinkDiff: {
        delta: 20 // どのくらいの差を許容するか
    }
});

おわりに

reftest-runner はこういうことができたらもっといろんな角度からテストできるのではと思って、既存のものを組み合わせて作った感じです。

3時間後 ->

実際にCanvasライブラリを書き直す時に、互換性を検証するための自動テストに組み込んで使ってみた感じ、テストファイルがただのHTMLであるというのがかなり有用だと思いました。

そのテスト用HTMLは別にテストのためだけではなく、ある機能を使って動かすサンプルコードともなるので、妙な動きをした場合はそのHTMLを開いてデバッガで確認するという単純なことができるのがいい気がします。 テストファイルのテストが簡単にできるというのはユニットテストとは少し違う所なのかもしれません。

reftest-runner はrunnerとなっていますがNodeモジュールとして使った方ができることは多くて、実際APIの方を意識して実装しています。(CLIは単純なことしかできない)

そのためmocha integrateができたりするともっと使いやすくなったりすんじゃないかと思うので、何かあったらIssues · azu/reftest-runnerへお願いします。