このアメブロはピコピコFrontrend Advent Calendar 2013ピコピコ 24 日目の記事です。







F.Y.I. 今日はクリスマス・イブだよ。サンタさん









おばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけ

CasperJSでカスタムのassertionを作るアップ


おばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけおばけ


独自のアサーションをCasperJSおばけで使いたい。


色々調べてみては見るものの、これといった参考になるようなものがなかったので、いい機会なのでちょっと試しにやってみる。



今回やりたいことはすごく単純で、自前のAssertionクラスを作って、テスト時に読み込んで使いたいだけ。


なるべくCasperJSおばけのTesterモジュールが最初から持っている機能に乗っかる感じにして、判定部分だけを自由に拡張できればなよいなという感じで作ってみた。

CasperJSおばけの拡張方法は



公式ドキュメント


http://docs.casperjs.org/en/latest/writing_modules.html溜め息


ここらへんが参考になる。


あとはCasperJSおばけのTesterモジュールを参考にしつつ


n1k0/casperjs溜め息


なんとなく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)とか


OpenCV溜め息


peterbraden/node-opencv溜め息


GraphicMagick(gm)とか


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"

```





やったね。うまく比較できてるみたい。バンザイ




sei