CSSのテスト 前編 何をテストするのか
CSSのテストとはいったいどのようなものでしょう。この記事では、どんな項目がCSSのテスト項目として考えられるか、またそれを実際にテストするツールの概要を紹介します。
- カテゴリー
- HTML/CSS >
- HTML/CSSの実践
発行
CSSのテストとは
JavaScriptのテストであれば、ある条件のもとに正しい値を返しているか、要素が存在するかなどのテスト項目を挙げることができるかと思います。
ではJavaScriptではなく、CSSをテストするとしたらどういったことをテストすればよいか考えたことはあるでしょうか。筆者は次のような項目を考えました。
- CSSが適切な書き方がされているか確認したい
- Sassで作成した関数(@function)が正しい値を返しているか確認したい
- ある部分が正しい見た目をしているか確認したい
それぞれの項目に関して簡単にですが、なぜそれをテストする必要があるか、またテストの概要を解説していきます。
なお、このシリーズで紹介するサンプルは次のリポジトリにまとめてあります。併せて、参照してください。
CSSのテスト
1. CSSが適切な書き方がされているか確認したい
「適切な書き方」と言っていますが、HTML ValidatorやJSLintなどのように、CSSの文法をチェックするのではなく、CSSに無駄であったり、パフォーマンスを落とす記述がないかどうかを確認するというものです。このテストを通らないものは、例えば、次のようなCSSが挙げられます。
display: inline-block;
が指定されているのにfloat: left;
が指定されている.hoge {}
のように、ルールセットを持たないセレクタがある- 全称セレクタが使われている
CSS Lintを試す
CSSの中に、これらが存在するかどうかを調べることができるのがCSS Lintというツールです。これはGruntのプラグイン(gruntjs/grunt-contrib-csslint)にもなっているので、すでにGrunt*を使用していれば導入は簡単です。
*注:Grunt
Gruntについては「現場で使えるGrunt入門」シリーズなども参照してください。
例として、grunt-contrib-csslintを使用したサンプルを作成しました。
サンプルディレクトリで、次のコマンドを実行して必要なパッケージをインストールします。
$ npm install
CSS LintはJSON形式で、さまざまなオプションの設定ができます。このサンプルでは、次のような設定ファイル(.csslintrc
)を作成しました。設定内容は、IDの使用不可、!important
の使用不可です。
{
"ids": true,
"important": true
}
このほかにもさまざまなオプションが設定できますが、それらについては以下のドキュメントを参照してください。
上記の設定のもとに、次のようなCSSをテストしてみます。
#header {
width: 100%;
height: 100px;
}
.block {
width: 100px;
margin: 0;
padding: 0;
}
.block--error {
background-color: red !important;
}
テストを実行するには、以下のコマンドを実行します。
$ grunt test
すると、以下のようなテスト結果が出力されます。
Running "csslint:all" (csslint) task
Linting ./css/style.css...ERROR
[L1:C1]
WARNING: Don't use IDs in selectors. Selectors should not contain IDs. (ids) Browsers: All
[L11:C3]
WARNING: Use of !important Be careful when using !important declaration (important) Browsers: All
>> 1 file lint free.
Done, without errors.
1行目でIDが使われていること、11行目で!important
が使われていることがエラー表示されています。
確認したいCSSの都合によっては、許容しなくてはいけないものもあるかもしれません。そういった場合はその項目を除外することもできます。
CSS Lintでチェックを行えば、ファイルサイズの削減やパフォーマンスの向上につながっていきます。
2. Sassで作成した関数(@function)が正しい値を返しているか確認したい
Sassの中では、受け取った値を加工して返したり、受け取った値がある条件に該当するかどうかを判定したりする関数(@function)を作ることができます。
その関数の記述が間違っていれば、エラーが発生しますが、その関数に渡した値が正しく加工されているかどうかといった「関数が正しいふるまいをしているかどうか」を確認することは簡単ではありません。
ここでは「関数が正しいふるまいをしているかどうか」を確認できるツールを、いくつか紹介します。
bootcampとtrueはSassの中にテストを書くことができるものです。
bootcampを試す
ここではbootcampによるテストを試すサンプルを用意しました。
簡単にですが手順を紹介します。2.bootcampディレクトリに移動してbootcampを実行できる環境を作成します。まずはBundler*を使用してSassのインストールです。
*注:Bundler
BundlerはRubyのGemを管理するツールです。インストールされていない場合は、以下のコマンドを実行する必要があります。
$ gem install bundler
$ bundle install
次に、Gruntなどを使用するのでnpmから必要なパッケージをインストールします。
$ npm install
これでテストの環境は整いました。
テストするコードを見てみましょう。たとえば、Sassで次のような関数を作成したとします。
@function parseInt($n) {
@return $n / ($n * 0 + 1);
}
@function calculateRem($size) {
$_baseFontSize: 10;
@if unit($size) != "px" {
@return false;
} @else {
$_remSize: parseInt($size) / $_baseFontSize;
@return $_remSize * 1rem;
}
}
このコードの仕様は以下のようになっています。
- parseInt関数は、単位付きの数値を受け取って、単位を削って数値にして返す。たとえば
parseInt(10px)
とすると10
を返す - calculateRem関数は、px単位の数値を受け取ったらremに変換する。たとえば
calculateRem(12px)
とすると1.2rem
を返す。もし受け取る単位がpx
でない場合はfalseを返す
短いコードですので、これらの関数は通常テストを書かなくても、ほぼ間違いはないだろうとは思います。ですが、calculateRem関数が正しいふるまいをしているかどうか、確認するためにテストを書いてみます。
テストは、parseIntやcalculateRem関数が、値を受け取ったときに、正しい値を返すか、またはエラーとなる場合は、どういうふるまいをするかということを書きます。テストの内容に関しては、こういう値が入るかもしれない、くらいのゆるい感じで書きました。
@include describe("parseIntは") {
@include it("単位を取り除いて数値を返す") {
@include should( expect( parseInt(12px) ), to( equal(12) ) );
@include should( expect( parseInt(1rem) ), to( equal(1) ) );
@include should( expect( parseInt(100%) ), to( equal(100) ) );
@include should( expect( parseInt(-12) ), to( equal(-12) ) );
}
}
@include describe("calculateRemは") {
@include it("pxをremに変換する") {
@include should( expect( calculateRem(12px) ), to( equal(1.2rem) ) );
}
@include it("px以外ならflaseを返す") {
@include should( expect( calculateRem(10) ), to( be-falsy()));
@include should( expect( calculateRem(10em) ), to( be-falsy()));
}
}
そして、次のコマンドで、テストを実行します。
grunt test
このテストコードを実行すると以下のような結果が出力されます。
parseIntは 単位を取り除いて数値を返す
✔ Expected 12 to equal 12
✔ Expected 1 to equal 1
✔ Expected 100 to equal 100
✔ Expected -12 to equal -12
Test passed
calculateRemは pxをremに変換する
✔ Expected "1.2rem" to equal "1.2rem"
Test passed
calculateRemは px以外ならflaseを返す
✔ Expected false to be "falsy"
✔ Expected false to be "falsy"
Test passed
✔ ✔ ✔
3 Tests, 7 assertions, 0 failures, 0 skipped
Done, without errors.
テストコードに問題がなければ、このような結果が出力されます。
興味がありましたら次のドキュメントなどを参照しつつ、テストを書いてみてください。
bootcampによるテストは、普通のウェブ制作で使用するというよりも、ライブラリ製作者が品質を担保するために使用するようなものかと筆者は考えます。ライブラリを安心して使ってもらうために、Sassといえどもテストを書く必要がでてくるでしょう。
3. ある部分が正しい見た目をしているか確認したい
例えば、すでにサイトがあり、サイトの見た目はそのままにCSSをリファクタリングすることになったとします。そのとき、リファクタリング後に、サイトが同じ見た目を保っているか保証するのは簡単ではありません。リファクタリング前後のページのキャプチャを取って目視確認などしていたら、日が暮れてしまいます。その面倒な作業——正しい見た目になっているかどうかの確認——をするためのツールはいくつかあります。
BackstopJSやPhantomCSS、wraithはCSSに変更があったとき、CSSの変更前と変更後のスクリーンショットを取り、画像による比較を行います。もし差分が出ている場合は、そのことをブラウザ経由やCUI経由などの何かしらの方法で教えてくれます。
BackstopJSもPhantomCSSも同じような比較方法ですが、BackstopJSのほうが比較的導入が簡単で、テスト結果画面もよくできていたので、BackstopJSを簡単にですが紹介します。
BackstopJSの概要
BackstopJSは、ある時点のページのスクリーンショットと、ある時点からCSSを変更した現在のページのスクリーンショットを比較して、差分が出ていないかどうかを調べるライブラリです。ページ全体のスクリーンショットを取ることも可能ですし、ある要素に絞ってスクリーンショットを取ることも可能です。
今回の記事では、以降で、概要のみを紹介します。導入から実行まで比較的簡単に進められるので、次回の記事では、BackstopJSを導入し、実際にテストを行ってみたいと思いますので、今回はひとまず「このようなテストを行う」といった概要を押さえてください。
BackstopJSでは、テストをするために「どのページの、どの要素を、どんな画面サイズでスクリーンショットを取るか」をJSON形式の設定ファイルに記述する必要があります。例えば、次のような設定ファイルになります。
{
"viewports" : [
{
"name": "phone",
"viewport": {"width": 320, "height": 480}
}
,{
"name": "tablet_v",
"viewport": {"width": 568, "height": 1024}
}
,{
"name": "tablet_h",
"viewport": {"width": 1024, "height": 768}
}
]
,"grabConfigs" : [
{
"testName":"http://getbootstrap.com"
,"url":"../../index.html"
,"hideSelectors": []
,"removeSelectors": []
,"selectors":[
"nav"
,".jumbotron"
,"body .col-md-4:nth-of-type(1)"
,"body .col-md-4:nth-of-type(2)"
,"body .col-md-4:nth-of-type(3)"
,"footer"
]
}
]
}
この設定ファイルでは、grabConfigs
のselectors
の配列に指定されたCSSのセレクタのスクリーンショットを、viewports
に指定された画面サイズ分だけ取得します。初回のテスト実行をしたときの画面は、次のようになっています。
selectors
に指定されたセレクタは6つで、viewports
に指定された画面サイズは3種類なので、6セレクタ×3画面サイズ分のテストが実行された結果となります。初回は比較するものがないので、Passed(18)と表示されています。
テストをパスした要素のスクリーンショットも確認することができます。次に、そのページがわざと崩れるようなスタイルを書いた後に、再度テストを実行すると以下のようになります。
Passedが15になり、Failedが3となっています。
画像に差分が発生した部分が、ピンク色になっています。わざと崩れるようなスタイルを書いたため、表示が崩れた要素はエラーとして報告されます。
見た目をテストするメリット
サイトを作っていく段階では、見た目が定まっていないので、差分があるのは当たり前で、エラーばかりになって意味がないかと思うかもしれません。
けれど、もしこれが見た目をそのままにCSSを書き換えるといったことをしている場合であれば有用です。書き換える前と書き換えた後の見た目は同じでなくてはいけないので、CSSが正しく書かれていれば前後のスクリーンショットに差分が出ることはありません。BackstopJSのテスト結果画面では前後にどれだけ差分が出ているかといったことを画像と数値(右端の部分)で教えてくれます。
BackstopJSは、ほかの画像比較によるCSSテストフレームワークと比べて、導入が簡単で、テストの結果画面がよくできていると筆者は感じます。
その他のフレームワーク
HardyやCactusというテストフレームワークは、BackstopJSやPhantomCSSなどの画像比較によるCSSテストフレームワークと比べると若干毛色が違います。例えばHardyのテストケース*は以下のような形です。
*注:Hardyのテストケース
http://hardy.io/getting-started.htmlより引用しています。
Feature: Website layout test
As a user I want visual consistency on the http://csste.st/ website
Scenario: Content layout
Given I visit "http://csste.st/"
Then "section > p" should have "color" of "rgb(68, 68, 68)"
これは、ウェブサイト(http://csste.st
)のある要素(section > p
)のcolorプロパティの値がrgba(68, 68, 68)
であるかをチェックしています。このテストを実行すると、次のような結果が出力されます。
Hardy v0.0.11
CSS Utils Steps Loaded
CSS Steps Loaded
Generic Steps Loaded
Loading browser firefox
...Shutting down browser
1 scenario (1 passed)
3 steps (3 passed)
firefox success
テストコードの書き方や結果の出力などは異なりますが、Cactusも同じようにテストケースを書いて、あるセレクタが正しい値を持っているかどうかをチェックできます。そのためには、テストコードにCSSプロパティの期待される正しい値を、書く必要があり、CSSを変えたらテストを変えて...…と、運用が面倒なことになることが予想されます。
個人的には、使いどころに悩むのですが、例えばコーポレートカラーなどで、正しい色がCSSで使われているかを機械的にチェックできるので、そういった時に使用するのがよいのかもしれません。
まとめ
本記事では、CSSのテストというJavaScriptのテストなどに比べると、まだあまり実践例がないことに触れています。
しかしCSSはちょっとしたことで簡単に壊れたり、広い範囲で意図しない影響がでることがあります。サイトの規模が大きくなってくると全ページを目視で確認することは難しくなりますし、壊れやすいCSSがどこかに影響してないかを確実に保証するためには、CSSのテストを書くということが一般的になっていくのではないかと筆者は思っています。
ただ、CSSのテストに関する技術はまだまだ発展途上のため、今後、ここで紹介したものとは、別の方法が出てくるかもしれません。ですが、今もしCSSをテストするとしたら、どういった方法あるのかを知っておくことも大事です。
次回は、画像差分による「正しい見た目になっているかどうか」をチェックできるテストフレームワークBackstopJSを導入し、実際にテストを行ってみたいと思います。