このページの本文へ

やばっ!CSSの先祖返りで消耗したデザイナーにはビジュアル自動化テストがおすすめ

2016年08月15日 04時00分更新

文●Pavels Jelisejevs

  • この記事をはてなブックマークに追加
本文印刷
CSSを修正したらデザインがいつの間にか先祖返りしていたり、別の部分に影響して崩れてしまったり…そんなトラブルを防ぐのが、ビジュアルリグレッション(回帰)テスト。変更したらテスト。これからの常識になりそうです。

重要な開発プロジェクトに携わるときに、あなたはテストの重要性に気づくようになるでしょう。経験の度合いによって突然、もしくは時間をかけて徐々にその重要性に気づくかもしれません。テストにはさまざまな方法があり、独立したコードをテストするときのユニットテストから、システムの各パーツがどのように同時に動作するかを確かめるための結合テストや機能テストまであります。

この記事では一般的なテストの概要ではなく、やや特殊で比較的新しい取り組みである「ビジュアルリグレッション(回帰)テスト」について説明します。

ビジュアルリグレッションテストは、Webページをテストする方法の1つです。ある要素やテキストの値がDOMに存在しているかどうかを確認するのではなく、ページを実際に開いて、ある特定のブロックが希望どおりの見た目になっているかどうかをチェックします。1つ例を挙げてみましょう。Webサイトの閲覧者に、次のようなコードで親しみのあるメッセージを表示するとします。

<div>Hello, %username%!</div>

きちんと機能しているか確認するため、正しい名称が挿入されたかチェックし、メッセージを表示するコードのユニットテストをします(すべきです)。また、要素が実際に正しいテキストのページにあるか確認するための、SeleniumやProtractorを利用した機能テストもできます。

しかし、これでは不十分です。このテストでは、テキストが正しく作成されDOMに表示されているかだけでなく、要素全体が正常に見えるかどうかを確認する必要があります。たとえば、要素がdisplay: noneで隠されていないか、誰かが間違えてテキストのカラーを上書きしていないかも確認しましょう。さまざまなツールがありますが、この記事ではPhantomCSSを取り上げます。

PhantomCSSとはなにか

PhantomCSSとは、ビジュアルリグレッションテストを実行するためのNode.jsベースのツールです。Huddleによって開発され、オープンソースで公開されています。PhantomCSSを使うと、ヘッドレスブラウザーを立ち上げたり、ページを開いたり、ページ全体やある特定要素のスクリーンショットを撮ったりできます。スクリーンショットはあとで照合するための基準となる画像として格納されます。Webサイト上に変更を加えるときにはいつでもPhantomCSSを使えます。スクリーンショットが別にもう1枚撮影され、オリジナル画像と比較されます。相違点が見つからなければ、テストに合格したことになります。

一方、スクリーンショットがマッチしない場合は不合格となり、相違点を示した新規イメージがレビューとして作成されます。この方式のおかげで、PhantomCSSはCSSにおける変更のテストに最適なのです。

PhantomCSSはいくつかの主要コンポーネントの上に成り立っています。

  • CasperJS – PhantomCSSやSlimerJSブラウザーと相互作用するツール。ページを開いたり、ボタンをクリックしたり値を入力したりと、ユーザーによる操作が可能。また、CasperJSは独自のテストフレームワークを持ち、ページのスクリーンショットを撮影できる
  • PhantomJS 2またはSlimerJS – それぞれ異なるヘッドレスブラウザーで、どちらかをPhantomCSSとともに使用できる。ヘッドレスブラウザーとは、通常のブラウザーでユーザーインターフェイスがないもの
  • Resemble.js – イメージを比較するためのライブラリー

PhantomCSSはPhantomJSやSlimerJSとともに使用できますが、この記事ではPhantomJSを扱います。

試してみよう

PhantomJSの実際の利用法を知るため、ちょっとしたテストプロジェクトを立ち上げます。テストプロジェクトのためには、ページを開くためのテスト用WebページとCasperJSが使えるシンプルなNode.jsのWebサーバーが必要です。

テストプロジェクトの設定

サンプルコンテンツを利用してindex.htmlファイルを1つ作成します。

<!doctype html>
<html>
  <head>
    <style>
      .tag {
        color: #fff;
        font-size: 30px;
        border-radius: 10px;
        padding: 10px;
        margin: 10px;
        width: 500px;
      }

      .tag-first {
        background: lightcoral;
      }

      .tag-second {
        background: lightskyblue;
      }
    </style>
  </head>

  <body>
    <div class="tag tag-first">The moving finger writes, and having written moves on.</div>
    <div class="tag tag-second">Nor all thy piety nor all thy wit, can cancel half a line of it.</div>
  </body>
</html>

Webサーバーをインストールするため、npmプロジェクトを初期化し、http-serverパッケージをインストールします。

npm init
npm install http-server --save-dev

Jsonサーバーを起動するため、簡単なnpmスクリプトを定義します。package.jsonに次のscriptsセクションを追加します。

"scripts": {
  "start": "http-server"
},

プロジェクトフォルダーからnpm startを起動できるようになり、インデックスのページはデフォルトアドレスhttp://127.0.0.1:8080でアクセスできるようになりました。サーバーを起動し、しばらくの間そのままにしておきます。

PhantomCSSのインストール

PhantomCSSのインストールは簡単で、プロジェクトに依存オブジェクトをいくつか追加するだけでよいのです。

npm install phantomcss casperjs phantomjs-prebuilt --save-dev

テストスイートの作成

最初のテストスイートを設定するのに必要なものがすべて揃いました。PhantomCSSテストスイートはNode.jsスクリプトで作られており、好きなWebサイトのページを開き、スクリーンショットを撮ってそのイメージを前のものと比較するものです。それでは、PhantomCSSのデモに基づく簡単なテストケースから始めます。

var phantomcss = require('phantomcss');

// start a casper test
casper.test.begin('Tags', function(test) {

  phantomcss.init({
    rebase: casper.cli.get('rebase')
  });

  // open page
  casper.start('http://127.0.0.1:8080/');

  // set your preferred view port size
  casper.viewport(1024, 768);

  casper.then(function() {
      // take the screenshot of the whole body element and save it under "body.png". The first parameter is actually a CSS selector
      phantomcss.screenshot('body', 'body');
  });

  casper.then(function now_check_the_screenshots() {
    // compare screenshots
    phantomcss.compareAll();
  });

  // run tests
  casper.run(function() {
    console.log('\nTHE END.');
    casper.test.done();
  });
});

テストではhttp://127.0.0.1:8080/が開き、body要素のスクリーンショットを撮り、screenshots/body.pngの中に保存します。

テストの準備ができたら、あとはスクリプトを定義してテストを実行するだけです。次のスクリプトを、startの隣にあるpackage.jsonに追加します。

"test": "casperjs test test.js"

次のコマンドを実行してテストします。

npm test

次のようにアウトプットされます。

Test file: test.js                                                              
# Tags
PASS Tags (NaN test)

New screenshot at ./screenshots/body_0.png

Must be your first time?
Some screenshots have been generated in the directory ./screenshots
This is your 'baseline', check the images manually. If they're wrong, delete the images.
The next time you run these tests, new screenshots will be taken.  These screenshots will be compared to the original.
If they are different, PhantomCSS will report a failure.

THE END.
WARN Looks like you didn't run any tests.                                       
npm ERR! Test failed.  See above for more details.

はじめてのテストだったので、新しい基準となるスクリーンショットだけが作成され、比較するものはありません。screenshotsフォルダーの中を見てみると、次のような画像が見つかるはずです。

Screenshot of the whole body

これが(テストケースにおける)Webサイトの適切な基準となる表示結果あり、後のテストではこの画像に対する結果が比較されます。

リグレッションの導入

同じテストコマンドを再び実行すると、すべてのテストに合格したという結果が表示されます。

Test file: test.js                                                              
# Tags
PASS Tags (NaN test)


PASS No changes found for screenshot ./screenshots/body_0.png

PhantomCSS found 1 tests, None of them failed. Which is good right?

If you want to make them fail, change some CSS.

THE END.
PASS 1 test executed in 0.827s, 1 passed, 0 failed, 0 dubious, 0 skipped.

Webサイト上になにも変更を加えていないので、これは予想どおりの結果です。ここで少し変化を加え、テストを再度実行します。たとえばブロックサイズを400pxに縮小するなどして、index.htmlのスタイルを少し変更します。ここでテストを再度実行し、なにが起こるかテストしてみましょう。

Test file: test.js                                                              
# Tags
PASS Tags (NaN test)
Failure! Saved to ./failures/body_0.fail.png


FAIL Visual change found for screenshot ./screenshots/body_0.png (11.41% mismatch)
#    type: fail
#    file: test.js
#    subject: false

PhantomCSS found 1 tests, 1 of them failed.

PhantomCSS has created some images that try to show the difference (in the directory ./failures). Fuchsia colored pixels indicate a difference between the new and old screenshots.

THE END.
FAIL 1 test executed in 1.082s, 0 passed, 1 failed, 0 dubious, 0 skipped.       

Details for the 1 failed test:

In test.js
  Tags
    fail: Visual change found for screenshot ./screenshots/body_0.png (11.41% mismatch)
npm ERR! Test failed.  See above for more details.

ここで、重要なことがいくつか起こりました。まず、PhantomCSSが「スクリーンショットbody_0.pngの不一致のためテストは失敗した」といった趣旨の結果を表示しました。不一致率は11.41%でした。次に、現バージョンと前バージョンの相違点はfailuresフォルダーに保存されました。それを開けば、次のようなスクリーンショットが見られます。

Screenshot of an error

スクリーンショットでは、変化した部分にハイライトがかかっているので、相違点が分かりやすくなっています。

変更を許可する

相違点がハイライトされましたが、その変化を許可するにはどうしたら良いでしょうか。縮小したブロック幅を受け入れ、現在の見栄えを新たな基準として取り入れるという内容をツールに伝えるのが良いでしょう。そのためには-- --rebase引数を追加したテストコマンドを実行します。

npm test -- --rebase

2つのダブルダッシュに注意してください。npmでは、パラメーターをアンダーラインが引かれたコマンドに送るときこのように表記します。次のコマンドはcasperjs test test.js --rebaseとなります。変更を許可したので、比較の基準となる画像は新しいものに代わりました。

応用

基本が分かったので、このツールを自分のワークフローに取り入れてみてください。実際にはプロジェクトによって変わるので、ここではその詳細には触れませんが、考慮すべきポイントを次に挙げておきます。

  • 実際のWebサイトに対してテストを実行するのか、それともスタイルガイドのようなものに対してか? 分離したUI要素はどこにあるのか?
  • サイトに動的コンテンツがあるか? ある場合、コンテンツの変化によりテストが失敗するのを回避するには、静的コンテンツを使ったWebサイトの別バージョンを作成しテストする必要がある
  • バージョンコントロールにスクリーンショットを追加するか? (追加するのがおすすめ)
  • スクリーンショットはページ全体なのか、個々の要素に対してなのか?

説明してきたツールを利用すれば、自動テストでWebサイトの見た目を把握できます。ユニットテストと機能テストを合わせた新たな戦略により、最先端のテスト技術に近づけます。テスト初心者の人も始めてみる良い機会です!

(原文:Visual Regression Testing with PhantomCSS

[翻訳:Noriko O. Romano]
[編集:Livit

Web Professionalトップへ

WebProfessional 新着記事