undefined

bokuweb.me

PhantomJS + MochaでクライアントサイドJavaScriptのテストをしよう

ブラウザゲームとかを作っているとなかなかテストが難しくどうしたらいいんだろうと調べていたらPhantomJS + Mochaという組み合わせを見つけたので勉強してみました。

PhantomJSとは?

ブラウザです。JavaScriptのAPIを通じて制御できるヘッドレス(画面のない)Webkitブラウザらしいです。 まだ試せてませんがCanvasやSVGにも対応してるっぽい。

インストール

http://phantomjs.org/download.htmlよりダウンロードしPATHを通すことで使用できます。

Macでhomebrewが入って入れば以下でOK。

$ brew update && brew install phantomjs

PhantomJS準備運動

まずは以下のようなhello.jsを用意します。

・hello.js

var page = require('webpage').create();
page.open('http://www.google.co.jp', function () {
    page.render('google.png');
    phantom.exit();
});

以下のように実行します。

> phantomjs hello.js

f:id:bokuweb:20150204195448p:plain

これでGoogleのスクリーンショットがとれます。(スクリーンショットをとったのは節分です。鬼がかわいい) windowサイズもJavaScriptで変更できるし、これだけでも色々応用できそう。

Mochaとは?

node.jsやブラウザから実行できるテストフレームワークです。

インストール

npmでいれてもいいし、ブラウザで実行するなら"mocha.css"、"mocha.js"のみ持ってきたらよいようです。 今回の例ではブラウザで実行させるため、以下より"mocha.css"、"mocha.js"をダウンロードしておきます。

https://github.com/mochajs/mocha

あと、Mochaはテスティングフレームワークのみで、アサーションは別のライブラリを用いる必要があるようです。 調べたところ、今回はよさげなchaiを使用します。

http://chaijs.com/

こちらもブラウザで使用するためhttp://chaijs.com/chai.jsよりダウンロードしておきます。

Mocha準備運動

こんな感じでテストができるよーってサンプルです。 加算するadd関数をテストしています。

・test.html

<html>
    <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" href="mocha.css" />
    </head>
    <body>
        <div id="mocha"></div>
        <script src="chai.js"></script>
        <script src="mocha.js"></script>
        <script>
          var add = function (arg1, arg2) {
              return arg1 + arg2;
          }
          mocha.setup('bdd');
          expect = chai.expect;

          describe('add test', function() {
            it('should return 3 when arg1 is 1 and arg2 is 2', function() {
              expect(add(1,2)).to.be.equal(3);
            });
          });

          mocha.run();
        </script>
    </body>
</html>

test.htmlをブラウザで開くと以下のようにテスト結果が出力されます。

f:id:bokuweb:20150204195542j:plain

PhantomJS + MochaでのクライアントJavaScriptのテスト準備

ようやく本題。PhantomJS + Mochaでテストの準備をする。 で、いきなりですが、問題はMochaがPhantomJS上での動作に対応していないようです。 以下を参考にさせてもらいました。

PhantomJSを使ったスマホサイトテストの自動化(前編)|1 pixel|サイバーエージェント公式クリエイターズブログ

ここでPhantomJSで動作するよう修正したものが公開されているので使用させてもらいます。

feb0223/mocha-phantom · GitHub

テスト対象ページ

簡単に今回はこんなので試してみます。 画像を重ねておいて、クリックするとフェードインします。 バージョンはなんでもいいですが(たぶん)jqueryも使用します。

・index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <style>
      img.icon {
        position: absolute;
        top:50%;
        left:50%;
        margin:-40px 0 0 -40px;
      }
      img.icon-over {
        position: absolute;
        top:50%;
        left:50%;
        margin:-40px 0 0 -40px;
        z-index: 999;
        display:none;
      }
    </style>
</head>
<body>
    <img src="shadow.png" class="icon">
    <img src="icon.png" class="icon-over">
    <script src="jquery-1.11.2.min.js"></script>
    <script>
     jQuery(function($) {
       jQuery('img.icon').click(
          function () {
           console.log("click!!");
            jQuery('img.icon-over').fadeIn();
          }
        );
      });
   </script>
</body>
</html>

テストコード

こっからはCoffeeScriptです。 流れはこんな感じです。

  1. mocha, chaiの読み込み
  2. index.htmlをオープン、キャプチャをとり、jqueryを読み込み
  3. 画像の座標、フェードインする画像のdisplayプロパティを取得
  4. クリック前のdisplayプロパティが"none"であることをテスト
  5. 画像の座標をクリックし、1秒待つ
  6. クリック後のキャプチャを取得、クリック後のdisplayプロパティが"block"であることをテスト

これだけだけど、大分はまりました。

page = require('webpage').create()
mocha = require('./lib/mocha-phantom.js').create(reporter:'spec', timeout:1000*10)
chai = require('./lib/chai.js')
expect = chai.expect

mocha.setup('bdd')

describe 'client javascript sample', ->
  # 画面のサイズを設定
  page.viewportSize =
    width: 800
    height: 600

  before (done)->
    # 指定したページをオープン
    page.open 'index.html', ->
      # ページ読み込み後のキャプチャを取得
      page.render('before_click.png')
      # jqueryのインクルード
      page.includeJs "jquery-1.11.2.min.js", ->
        console.log "includeJS"
        done()
      page.onConsoleMessage = (msg, lineNum, sourceId)-> console.log msg
    return

  describe 'image click test', ->
    it 'fadein image when click image', ()->

      # imageの座標を取得する
      position = page.evaluate -> $('img.icon').position()

      # fadeinする画像のdisplayプロパティを取得
      displayVal = page.evaluate -> $('img.icon-over').css('display')

      # クリック前のdisplayプロパティが"none"であることをテスト
      it 'should fade in image property is none before click', -> expect(displayVal).to.be.equal("none")

      # imageの座標をクリック
      page.sendEvent('click', position.left+1, position.top+1)

      # fadeIn時間を待つ
      setTimeout ()->
        # クリック後のキャプチャを取得
        page.render('after_click.png')

        # fadeinした画像のdisplayプロパティを取得
        displayVal = page.evaluate ()-> $('img.icon-over').css('display')

        # クリック後のdisplayプロパティが"block"であることをテスト
        it 'should fade in image property is none before click', -> expect(displayVal).to.be.equal("block")
        phantom.exit()
      , 1000
      return
    return

# テストの実行
mocha.run()

実行

> phantomjs test.js

f:id:bokuweb:20150204211236p:plain

テスト後に"before_click.png"と"after_click.png"が生成されているかと思います。

・before_click.png

f:id:bokuweb:20150204195700p:plain

・after_click.png

f:id:bokuweb:20150204195722p:plain

はまった点

page.evaluateを理解してなかった

Evaluates the given function in the context of the web page. The execution is sandboxed, the web page has no access to the phantom object and it can't probe its own setting.

evaluateメソッドはWeb Pageのコンテキストで評価されます。この実行はサンドボックス化されているため、PhantomJSと直接やりとりできません。

http://murayama.hatenablog.com/entry/2013/06/24/065633より

例えば、page.evaluate内でpage.evaluate ->console.log $('img.icon').position()ってしてもWeb Pageのコンテキストで評価されてしまい、PhantomJS側には何も表示されません。これはonConsoleMessageを使うことで対処できるようです。具体的にはページオープン時にpage.onConsoleMessage = (msg, lineNum, sourceId)-> console.log msgとしておけばPhantomJS側のコンソールに吐かれます。

こちらも上記参考サイトに詳細が書かれていますが、PhantomJS側とWeb Page側とで値をやり取りするには、evaluateメソッド呼び出しの引数や戻り値を使えばいいようです。 position = page.evaluate -> $('img.icon').position()でWeb Page側の画像座標を取得したり、displayVal = page.evaluate -> $('img.icon-over').css('display')でCSSプロパティを取得したりしています。 ただし

受け渡し可能なデータはJSONにシリアライズ可能なオブジェクトに限定されます。クロージャや関数オブジェクトを使ってやりとりすることはできません。

らしいです。

やってみて

  • PhantomJS上でMochaを使うかどうかはおいといて、PhantomJSだけでも使いこなせればめちゃめちゃ便利な気がする
  • アプリケーションによってはキャプチャの差分を取って合致しているかどうかの判定処理まで入れてしまえば完全な自動化もできる?(canvasで取り込んでpixelベースの比較をしてやるとか?わかんないけど)
  • PhantomJSはwebkitなので、他のブラウザはseleniumとか使わなきゃ?
  • スマホページでこそ真価を発揮するかも
  • ただし、参考サイト(http://ameblo.jp/ca-1pixel/entry-11549761391.html)にあるように「JSでDomを追加してイベントを設定した要素に対し、イベントの発火ができない」とか不便な点もあるよう
  • なかなかおもしろかった

リポジトリ

めんどくさいので全部ごっちゃだけどあげときます。

bokuweb/mocha-phantomjs-test · GitHub

参考にしたサイト

http://ameblo.jp/ca-1pixel/entry-11549761391.html http://murayama.hatenablog.com/entry/2013/06/24/065633 http://webtech-walker.com/archive/2012/10/mocha_phantomjs_travisci.html

【組み込み】ハードウェアに近い組み込み開発でもテストの自動化を行いたい - 環境構築編 -

普段、業務ではRenesasのRL78やRX、ARMのCoretexM0〜CoretexM3辺りを触ることが多いんですが、このあたりのハードウェアに近いレイヤーの開発はテストの自動化の浸透が遅れている領域の一つかと思っています。(自分の周りだけかもしれませんが・・。) Renesas製のマイコンとなるとホストもwindowsになる場合が多く、それがテスト自動化の導入のしきいを上げる要因のひとつになっているのでは?と考えています。

今回はwindows上でCygwin/Eclipse/googleTestを使用し、テストを行えるところまでのメモを記載します。 基本的には以下の書籍を参考にしていますが、書籍ではVirtualBox+Ubuntu上に環境を構築しています。 諸事情によりVirtualBox+Ubuntuが使用できない方の参考になればと思います。

参考書籍

タイトルにはありませんが”組み込み向け”を意識した内容になっています。(・・と思っています。)

Cygwinのインストール

  1. setupのダウンロード http://cygwin.com/install.htmlよりsetup.exeをダウンロードする

  2. setup.exeの実行 Root Directoryを聞かれますが、自分はC:\Windows\cygwinとしました (なぜかC直下だとGCCのインストールでエラーとなったため)

  3. パッケージの選択 「Deval」を必ず「install」にする
  4. PATHを通す PATHにC:\Windows\cygwinを追加する
  5. gccの確認
アクセスが拒否されました
>gcc -4
アクセスが拒否されました

上記のように「アクセスが拒否されました」となる場合、Cygwin\binのgcc.exe,g++.exeをリネームし、gcc-3(-4).exe、g++-3(-4).exeをgcc.exe,g++.exeにリネームする その後、gcc,g++でno input filesとなればOK

gcc: no input files
>g++
g++: no input files
  1. エラー対策 *** multiple target patterns. Stop.というエラーが発生する場合、Cygwin/binのmake.exeを以下のものに差し替える http://www.cmake.org/files/cygwin/make.exe

以上でCygwinのインストールは完了

Eclipseのインストール

  1. Eclipseのダウンロード Pleadesのプロジェクトページダウンロードする 環境に合わせC/C++と書いてある列のものを選択すること
  2. Eclipseのインストール ダウンロード後、任意のディレクトリに解凍する eclipse.exeを実行するとeclipseが立ち上がる

以上でEclipseのインストールは完了


ここからは「モダンC言語プログラミング」p147~とほぼ同じ内容となります 詳細はそちらを参照願います

Google Testのインストール

  1. Google Testのインストール http://code.google.com/p/googletest/downloads/listからダウンロードする ダウンロード後任意の場所に展開する

  2. ライブラリの作成

>cd gtest-1.7.0
>g++ -I.include -I. -c ./src/gtest-all.cc
>ar -rv libgtest.a gtest-all.o

これでlibgtest.aというライブラリファイルが生成されます g++のバージョンが変更になったら再度ライブラリを生成してください (ライブラリを再生成し忘れ原因不明のエラーで半日つぶしました)

Eclipseの設定

  1. プロジェクトの作成 [ファイル] - [新規] - [C++プロジェクト]を選ぶ [ツールチェーン]では[Cygwin GCC]を選択する
  2. GoogleTestの設定 プロジェクトのプロパティ―から[C/C++一般] - [パスおよびシンボル]を選ぶ [インクルード]タブを選択し、[追加] - [ファイルシステム]から(gtest-1.7.0/include)を選択 [ライブラリー・パス]タブを選択し、[追加] - [ファイルシステム]から(gtest-1.7.0)を選択 [ライブラリー]タブを選択し[追加]を押して、ファイル名に「gtest」を追加して[OK]、もう一度[追加]を押して、ファイル名に「pthread」を追加して[OK]

テストの実行

  1. 以下のファイルをプロジェクトに追加 add.cがテスト対象、add_test.ccがテストコードです

・add_test.cc

#include "gtest/gtest.h"
#include "add.h"

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

TEST(AddTest, onePlusTwoGivesThree) {
    EXPECT_EQ(3, add(1, 2));
}

・add.c

#include "add.h"

int add(int i1, int i2)
{
    return i1 + i2;
}

・add.h

#ifndef ADD_H_
#define ADD_H_
#ifdef __cplusplus
extern "C" {
#endif

int add(int i1, int i2);

#ifdef __cplusplus
}
#endif

#endif /* ADD_H_ */
  1. ビルド・実行 ビルドに成功するとadd.exeが生成される add.exeを実行することでテストが開始される

テスト結果

たとえばadd_test.ccを以下のようにすると

#include "gtest/gtest.h"
#include "add.h"

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

TEST(AddTest, onePlusTwoGivesThree) {
    EXPECT_EQ(2, add(1, 2));
}

失敗する

f:id:bokuweb:20141227214136j:plain

おまけ

「Grep Console」をeclipseの追加すると特定の文字を含むメッセージに色を付けることができる この機能を使うと[OK]、[PASSED]の時は緑色、[NG]、[FAILED]の時は赤色を表示するなどのカスタマイズが可能となる 些細なことかもしれませんが、個人的には結構重要かと思います オールグリーンのときは気持ちよくモチベーションあがりますからね