CasperJSでカスタムのassertionを作る
独自のアサーションをCasperJSで使いたい。
色々調べてみては見るものの、これといった参考になるようなものがなかったので、いい機会なのでちょっと試しにやってみる。
今回やりたいことはすごく単純で、自前のAssertionクラスを作って、テスト時に読み込んで使いたいだけ。
なるべくCasperJSのTesterモジュールが最初から持っている機能に乗っかる感じにして、判定部分だけを自由に拡張できればなよいなという感じで作ってみた。
CasperJSの拡張方法は
公式ドキュメント
http://docs.casperjs.org/en/latest/writing_modules.html
ここらへんが参考になる。
あとはCasperJSのTesterモジュールを参考にしつつ
なんとなくTester.processAssertionResultにオブジェクトでオプションを渡せば行けそうな感じ。
successのキーにtrueを渡すと[PASS]でfalseだと[FAIL]になるみたい。
とりあえずやってみる。
MyAssertionというクラスを作って、必要な機能はcasper経由で利用するかたち。
MyAssertionにassertTrueっていうassertionメソッド作る。
あと、CasperJSはCoffeeScriptをそのまま実行できるので、全部CoffeeScriptでかける。
ドキュメントによると。拡張する場合は requireをpatchRequire通したやつを利用するようにと書いてあるので、patchRequire を一回通して使う。CoffeeScript使うときはドキュメンド通り global.require を渡すようにする。
```
require = patchRequire(global.require)
###*
* 俺のアサーション
###
class MyAssertion
'use strict'
utils = require 'utils'
constructor: (casper) ->
@casper = casper
###*
* assertTrue
* TrueならPASS
*
* @param {Object} subject 検証ターゲット.
* @param {String} msg メッセージ文字列.
* @param {Object} context 検証コンテキスト.
###
assertTrue: (subject, msg, context)->
result = utils.mergeObjects(
success: subject is true
type: "assert"
standard: "Trueだったよ"
message: msg
file: @currentTestFile
doThrow: true
values:
subject: utils.getPropertyPath(context, 'values.subject') || subject
)
return @casper.test.processAssertionResult(result)
exports.MyAssertion = MyAssertion
```
MyAssertion.assertTrue(検証対象, メッセージ文字列, 検証対象のコンテキスト)
という形で呼び出せる。メッセージとコンテキストはオプション。
使い方はテストの呼び出し元でrequireして、標準のassertionと同じように呼び出す。
```
casper = require('casper').create()
MyAssertion = require('./modules/myassert').MyAssertion
myAssert = new MyAssertion casper
casper.start()
casper.then (res)->
myAssert.assertTrue true, 'trueはTrue'
return
casper.then (res)->
myAssert.assertTrue false, 'falseはTrue'
return
casper.run()
```
実行結果はこんな感じ。
```
PASS trueはTrue
FAIL falseはTrue
# type: assert
# subject: false
```
やったね。自分オリジナルのアサーションかけたよ。
でも、これだけだとちょっと物足りないので、キャプチャした画像をあらかじめ用意してある画像と比較して、一致したらPASS、一致しなかったら、FAILみたいなことやりたい。
画像の比較をJavaScriptでやるなら、
resemble.jsとか
http://huddle.github.io/Resemble.js
opencv(node-opencv)とか
GraphicMagick(gm)とか
があるんだけどresemble.jsはHTML5のAPI利用するから使えない、他の2つはnodejsで実行してあげないといけない。
CapserJSがやっかいなのは、ブラウザのようでブラウザじゃなくて、node.jsのようでnode.jsじゃないところ。
opencvで書くのはしんどいので、GraphicMagickのcompareを使ってお手軽に比較してみる。
とりあえず動くものがほしいので楽をして、自前のアサーションメソッドの中でchild_processを作って、nodejs gm.compareで画像比較を実行して、その出力をCasperJSに戻して結果を得るようにした。
結果の文字列はとりあえずJSONを文字列にでstdoutに出して、それをCasperJSで拾って、JSON.parseして結果を受け取る。
コツとしては、child_processの処理が終わるまで、casper.waitForして待たせておくこと。そうしないと、CasperJSのほうが先にすすっじゃって、compareの結果が得られない。
画像比較の方のスクリプトはかなり適当。
```
###*
* Compage Image return result(json)
###
fs = require 'fs'
gm = require 'gm'
args = process.argv.splice 2
dist = args[0]
src = args[1]
res =
equal: false
equality: 0
gm.compare(
src
dist
0.1
(err, isEqual, equality, raw)->
if err
process.stdout.write "error"
return res
if isEqual
res =
equal: true
equality: equality
else
res =
equal: false
equality: equality
process.stdout.write JSON.stringify(res) + "\n"
)
```
呼び出しはこんなかんじで、画像パスを2つ渡す。
```
$ coffee sh/compare.coffee src spec
```
ここはおいおいCVとかも試して、変更しよう。
一方、画像比較のImageAssertionのクラスはこんなかんじになった。上記のcompare.coffeeをアサーションの中でchild_processとして呼び出す。
```
class ImageAssertion
'use strict'
utils = require 'utils'
exec = require('child_process').exec
spawn = require('child_process').spawn
constructor: (casper) ->
@casper = casper
###*
* assertEqual
* 画像が一致するかどうか
*
* @param {Object} subjext 検証ターゲット.
* @param {Object} src Image Spec.
* @param {String} msg メッセージ文字列.
* @param {Object} context 検証コンテキスト.
###
assertEqual: (subject, src, msg, context)->
step = false
res = ''
check = ->
return step
process = spawn 'coffee', ['sh/compare.coffee', subject, src]
process.stdout.on "data", (data)->
res = JSON.parse(data)
process.stderr.on "data", (data)->
console.log "ERROR: stderr #{data}"
process.on "exit", (code)->
#console.log 'spawn Exit'
step = true
@casper.waitFor(
->
return check()
->
result = utils.mergeObjects(
success: res.equal is true
type: "assert"
standard: "#{context} Image has diff fron src"
message: msg
file: @currentTestFile
doThrow: true
values:
subject: utils.getPropertyPath(context, 'values.subject') || subject
)
return @test.processAssertionResult(result)
)
exports.ImageAssertion = ImageAssertion
```
第二引数にspecになる画像を渡すようにした。キャプチャした画像と予め用意したspec画像がgm.compare的に一致するなら、[PASS]、一致しないなら[FAIL]になる。
実行するとこんな感じの結果が得られる。今回はwww.bbc.co.ukの英語版とフランス語版を比較した。spec画像には英語版のキャプチャを用意しておいて、どちらもこれと比較する。
つまり英語版は[PASS]でフランス語版は[FAIL]になるはず。
```
Test file: image.coffee
BBC - Homepage
PASS 画像は同じ
BBC - Learn French with free online lessons
FAIL 画像は同じ
# type: assert
# subject: "2.png"
```
やったね。うまく比較できてるみたい。