Attention! Translated article might be found on my English blog.
ラベル jQuery の投稿を表示しています。 すべての投稿を表示
ラベル jQuery の投稿を表示しています。 すべての投稿を表示

2015年11月24日火曜日

mocha+jsdom+coffeescriptでクライアントサイドjsをユニットテストする

あくまで初心者の備忘録なのでご注意ください。

経緯
mochaでのcoffeescriptのテストに関してはmochaでCoffeeScriptクラスのロジックユニットテストをしてみた際のメモCoffeeScriptのユニットテストでassertからchaiに変更する書いた。
その後実装を進めていくと、jQueryやwindowにアクセスしている箇所をテストしようとするとエラーが出てテストできなかった。

jasmine, phantomjsなどを検討したが、自分の環境では今ひとつ解決できなかった。

最終的にjsdomを使い、テスト用コード実行前にwindowとjQueryオブジェクトを作ることで無事テストできるようになった。

テスト手順
以下、テスト手順ごとに必要な作業や修正箇所を解説する。
まずテスト手順自体は以下の通り。

1: cake buildで従来通りクラスごとに分割したcoffeescriptを1つのjsファイルにまとめる。
2: cake buildtestで手順1のファイルとテスト用jsファイル(windowと$を設定するためのコード)を結合してテスト用ファイルとして書き出す。
3: cake testでテストを実行する。

1: cake buildで従来通りクラスごとに分割したcoffeescriptを1つのjsファイルにまとめる。
これは過去に書いた方法と同じ。

2: cake buildtestで手順1のファイルとテスト用jsファイル(windowと$を設定するためのコード)を結合してテスト用ファイルとして書き出す。

手順2のテスト用jsファイルとして、以下のようなコードをTestWindow.jsとしてtestディレクトリに入れた。

unction SetupTestWindow() {
    var jsdom = require('jsdom').jsdom;
    var markup = '<html><body><h1 class="example">Hello World!</h1><p class="hello">Heya Big World!</body></html>';
    var doc = jsdom(markup);
    var w = doc.parentWindow;
    $ = require('jquery')(w);
    window = w;
}
SetupTestWindow();

このファイルと手順1のファイルをcatして出力するタスクをCakefileに追加した。
タスクのコードは以下のとおり。

task 'buildtest', 'compile test files', ->
    targetList = ['test/TestWindow.js', '../script.js']
    targetList = targetList.join(' ')

    cmd ="cat #{targetList} > ./test/test.js"

    exec cmd, (error, stdout, stderr) ->

        util.log(error) if error
        util.log(stdout) if stdout
        util.log(stderr) if stderr

見るとわかる通り、cake buildtestを実行するとtestディレクトリ内にtest.jsが作成される。

3: cake testでテストを実行する。
testタスクは単純にmocha --compilers coffee:coffee-script/registerするだけになっている。

手順3で実行するのは前回同様テスト用のcoffeescriptだ。
ただし、クラス定義をrequireする際にcoffeescriptファイルを直接参照するのではなく、手順2で作成したファイルを参照する。

具体的には、前回はHogeクラスを参照する際

{Hoge} = require '../Hoge.coffee'

と書いていたが、今回の方法では

{Hoge} = require './test.js'

に変更する。

雑感
しばらくはこの方法で進め、ダメだったら本格的にテストの運用方法を考える。
phamtomjsはnpmから入れられるmocha用のmocha-phantomjsに関する情報が少なかった。
phantomjsが今の所良さげだが、導入例がhtmlファイルにテストを書いてるのが気に入らないので今回は見送った。

参考
PhantomJS + MochaでクライアントサイドJavaScriptのテストをしよう - bokuweb.me - mocha+phantomjs
mochaとphantomJSとtravis-ciでフロントエンドJavaScriptのテスト - Webtech Walker - mocha+phantomjs
node.jsのテスティングフレームワークであるjasmine-nodeをインストールして使ってみる — Toroとトロは電子機械の夢を見るか - jasmine
CoffeeScriptでjasmine-node | ひげろぐ - jasmine+coffeescript
CoffeeScript Cookbook » Testing with Jasmine - jasmine+coffeescript
CoffeeScriptファイルどうやって分けるか問題 - harusamex.blog - coffeescript分割

おまけ
TestWindow.jsはnode.js - Basic error when using jquery with nodejs - Stack Overflowを参考にしたが、そのままでは動かなかった。
不要なvar、セミコロン付け忘れ、windowへの代入タイミングといった修正が必要だった。


2015年11月21日土曜日

CoffeeScriptのユニットテストでassertからchaiに変更する


$ npm install chai
chai@3.4.1 ../../../../../node_modules/chai
├── assertion-error@1.0.1
├── type-detect@1.0.0

└── deep-eql@0.1.3 (type-detect@0.1.1)

前回のchai無し版を以下のように修正。

chai = require 'chai'
chai.should()
{Hoge} = require '../Hoge.coffee'

describe 'Hoge instance', ->
    range1 = range2 = null
    it 'should make correct Hoge instance', ->
        range1 = Hoge.makeHoge 5, 10
        range1.location.should.equal 5

        range1.length.should.equal 10

テスト実行は前回と同じく

$ mocha --compilers coffee:coffee-script/register

でOK。

2015年11月20日金曜日

mochaでCoffeeScriptクラスのロジックユニットテストをしてみた際のメモ

ログ代わりなので読みづらいと思います。

いちいちブラウザでHTMLを開くのが辛くなってきたのでユニットテストを書くことにしました。
テストフレームワークにはnode.jsモジュールのmochaを使いました。
ひとまずchaiは使わず、mochaでやれるところまで書いてみます。

なお、環境はMac OS X 10.11, node.js v0.12.4, CoffeeScript 1.9.1です。

インストローリング

$ npm install mocha
mocha@2.3.4 ../../../../node_modules/mocha
├── escape-string-regexp@1.0.2
├── supports-color@1.2.0
├── growl@1.8.1
├── commander@2.3.0
├── diff@1.4.0
├── debug@2.2.0 (ms@0.7.1)
├── mkdirp@0.5.0 (minimist@0.0.8)
├── jade@0.26.3 (commander@0.6.1, mkdirp@0.3.0)
└── glob@3.2.3 (inherits@2.0.1, graceful-fs@2.0.3, minimatch@0.2.14)

セットアッピング

mochaコマンドへのパス
mochaへのパスが通ってなかったので~/.bash_profileに以下の行を追加し、source ~/.bash_profileした。

# ~/.bash_profile
export PATH=$PATH:$HOME/node_modules/mocha/bin

Better CoffeeScript Testing With Mocha - Envato Tuts+ Code Tutorialを参考にテストコードを作成。

テスト用ファイルの場所
mochaコマンド実行時はtestというディレクトリ内にテスト用coffeeファイルが必要な模様。
testディレクトリが無いと以下のようにエラーが出てしまった。

$ mocha
/path/to/node_modules/mocha/lib/utils.js:626
        throw new Error("cannot resolve path (or pattern) '" + path + "'");
              ^
Error: cannot resolve path (or pattern) 'test'

多分ディレクトリは指定できると思われるがひとまずtest内にファイルを置くことにした。

mochaコマンドオプション
上記サイトを参考にテスト用coffeeファイル内でrequireを使ったところ、

$ mocha --compilers coffee:coffee-script
/path/to/node_modules/coffee-script/lib/coffee-script/coffee-script.js:210
          throw new Error("Use CoffeeScript.register() or require the coffee-s
                ^

Error: Use CoffeeScript.register() or require the coffee-script/register module to require .coffee.md files.

てな感じでエラーが出てしまったので、実行時には

$ mocha --compilers coffee:coffee-script/register

とすることでエラーを回避できた。
参考: Fixed Mocha for CoffeeScript 1.7+ and Travis CI errors by MrSaints · Pull Request #6 · github/generator-hubot · GitHub

クラスファイル側の修正
別のjsファイルからも使えるように、Hoge.coffeeには

window.Hoge = Hoge

と記述していたのだが、このwindowがユニットテスト時には存在しないようで

$ mocha --compilers coffee:coffee-script/register
/path/to/project/coffee/Hoge.coffee:68
  window.Hoge = Hoge;
  ^
ReferenceError: window is not defined

というエラーが出てしまった。
windowの代わりにthisとすることでエラーを回避し、ユニットテストとブラウザの両方で利用できるようになった。
参考: FreePender: JavaScript Unit Testing with Mocha

テストライティング

HogeクラスのクラスメソッドmakeHogeをテストするコードだけ作ってみた。
ひとまずtest/HogeTest.coffeeを以下のように書くことでユニットテストを正常に実行することができた。

assert = require 'assert'
{Hoge} = require '../Hoge.coffee'

describe 'Hoge instance', ->
    range1 = range2 = null
    it 'should make correct Hoge instance', ->
        range1 = Hoge.makeHoge 5, 10
        assert(range1.location == 5)

        assert(range1.length == 10)

assertの代わりにchaiのshouldなどを使うとベターらしい。今後の課題とする。

エグゼキューティング

$ mocha --compilers coffee:coffee-script/register


  Range instance
    ✓ should make correct Range instance



  1 passing (12ms)

ユニットテストを実行し、メソッドが意図通り動いていることを確認できたので今回はここまで。
最終的にファイル配置は以下のような感じ。srcディレクトリを切ったほうがいいかも。
project
|- index.html
|- script.js
|- jquery.js
|- coffee
   |- Cakefile
   |- Hoge.coffee
   |- App.coffee
   |- test
      |- HogeTest.coffee

今後は
・テストファイル分割時の挙動
・クラスが別のクラスに依存性している場合のテスト
・cakeでの自動化
などを試してみたいと思います。

2015年11月4日水曜日

JS, jQuery, CoffeeScript覚書2015/11/04分

NaN判定
ひとまずisNaN()を利用することにした。ただし、意図しない動作をする場合が多々あるようなので要注意。
参考: JavaScript - NaNの判定について - Qiita

文字列から数値へ変換
こちらはparseInt()を使うことにした。他にも方法があるので用途に応じて使い分けるのが良さそう。
参考: JavaScriptにおける数値⇔文字列の型変換あれこれ - console.lealog();

jQueryオブジェクトの子ノードを全て削除する
.empty()というメソッドがあるのでこれを使うと楽。
参考: .empty() | jQuery API Documentation

CoffeeScriptのループ
古典的なforループの場合(ループ回数はx+1回なので注意): for i in [0..x]
一般的な配列の場合: for value, index in array
連想配列の場合: for key, value of dic
参考: CoffeeScript入門メモ - Qiita

jQueryオブジェクトのi番目の子ノード
.children().eq(i)
参考: jQuery: .children()[index] not allow function invocation - Stack Overflow
ちなみにtableタグのi番目のtrを取得したい場合、tbodyを考慮してtable.eq(0).children('tr').eq(i)とする必要がある模様。

2015年7月29日水曜日

jQuery, CSSメモ

jQuery:

jQuery要素にclassを追加・削除する:
addClass(), removeClass()を使う
参考: jQuery: 要素にスタイルクラスを適用/除外するには?(addClass/removeClass/toggleClass/hasClass) - Build Insider

jQuery要素にstyleを追加する:
css()を使う
参考: jQuery – style属性のプロパティの値を取得・追加『 css() 』 | 1:n – DETELU Blog

jQuery要素がクリックされたときのアクションを追加する:
click()を使う。再指定時はclick()の前にunbind('click')したほうがよさそう。
参考: .click() | jQuery API Documentation


CSS:

CSSでクラス名を指定する:
.クラス名で指定可能。
参考: スタイルシート[CSS]/CSSの基本/クラス名を使った指定 - TAG index Webサイト

CSS内でコメントアウトする:
/* */ で括る。
参考: CSSのコメントアウト - ホームページ作成F


2015年7月19日日曜日

jQueryのloadハンドラが複数回呼ばれるときはunbindする

画像ロード時にスピン画像を表示させる処理を実装していたのですが、
imgのsrcを変更するたびにloadハンドラが呼ばれる回数が増えていくことに気づきました。
コードは以下のような感じです。

    var src = $('#viewer').attr('src', url);
    src.load(function() {
        // do some stuff
    });
javascript - jQuery event firing twice after .load() - Stack Overflow によると、
逐一loadハンドラをunbindする必要があるようです。
修正後のコードは以下の通り。
    var src = $('#viewer').attr('src', url);
    src.unbind('load'); // loadが複数呼ばれないようにするのに必要
    src.load(function() {
        // do some stuff
    });
これで複数回実行されることは無くなりました。

ていうか、初期化処理時に1度だけload()を実装し、
画像変更時にはsrcの変更のみ行うっていう方法で良いのかもしれません。

2015年5月29日金曜日