Hatena::ブログ(Diary)

?D of K

2012-06-26

簡易テストツールを公開しておきます

今年はブログを書きたいと述べて二ヶ月以上経ってしまいました。

最近、大規模だったり高速だったりという需要に応えるJavaScriptを書く道具がいくつも出てきています。一方で、小規模でもっと手軽にという道具がイマイチ少ない気がします(主観)。jQueryでもうちょっと届かないところを埋めるそういう道具を少しずつ準備していこうかなと思います。

まずは昔作ったユニットテストを改良したので公開しておきます。

utestは最小限のユニットテストを提供します。昔作ったのでIE5.5でも動きますが、そういう目的にはあんまり使うつもりは無いです。

方針としては容易に書けるテストを目指しました。自分はテスト書くのがたるくて手が止まるというのがありがちなので……。

例えば、以下に示すsample.jsをテストすることを考えます。

var sample = {
  valid: true, // trueであるかテストしたい
  empty: [], // nullではなく空の配列であることをテストしたい
  ok: function () {
    return 1; // ok()を実行したら1が返り、ng()は実行できないことをテストしたい
  },
  done: false, // delayを実行後、遅れてtrueになるかテストしたい
  delay: function () {
    setTimeout(function () { sample.done = true; }, 0);
  },
  // 画像読み込みをテストしたい
  image: function (src, fn, err) {
    var img = new Image;
    img.src = src;
    img.onload = fn;
    img.onerror = err;
    return img;
  }
};

QUnitでは以下のように書くと思います。

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>QUnit Test Suite</title>
<link rel="stylesheet" href="../qunit/qunit.css">
<script src="../qunit/qunit.js"></script>
<script src="sample.js"></script>
<script src="test.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">test markup</div>
</body>
</html>

test.js

test("true test", function() {
  expect(1);
  ok(sample.valid);
});

test("function test", function() {
  expect(2);
  same(sample.ok(), 1);
  raises(function () { sample.ng(); });
});

test("array test", function() {
  expect(2);
  equal(QUnit.equiv(sample.empty, []), true);
  equal(QUnit.equiv(sample.empty, null), false);
});

test("async test", function() {
  expect(2);
  sample.delay();
  ok(!sample.status);
  stop();
  setTimeout(function() {
    ok(sample.status);
    start();
  }, 1000);
});

testAsync("image test", function() {
  expect(1);
  sample.image('sample.png', function () {
    ok(true);
    start();
  }, function () {
    ok(false);
    start();
  });
});

testAsync("image test (2)", function() {
  expect(1);
  sample.image('notfound.png', function () {
    ok(false);
    start();
  }, function () {
    ok(true);
    start();
  });
});

非同期テストがもう少し簡単に書ければ嬉しいな、と感じます。うまく書くやり方を知っている方は教えてください。

一方でutestは以下のように書きます。

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Utest Test Suite</title>
<script src="utest.js"></script>
</head>
<body>
<script src="test.js"></script>
</body>
</html>

test.js

utest("true test", [ sample.valid ]);

utest("array test", [
  [ sample.empty, [] ],
  [ sample.empty, '!==' null ],
]);

utest("function test", [
  function () { return [ sample.ok(), 1 ]; },
  utest.raise(function () { sample.ng(); })
]);

utest("async test", [
  function(test) {
    sample.delay();
    test(!sample.status);
    test(sample.status, 1000);
  }
]);

utest("image test", [
  function(test) {
    var ok_test = test(),
        ng_test = test();
    sample.image('sample.png', function () {
      ok_test(true);
    }, function () {
      ok_test(false);
    });
    sample.image('notfound.png', function () {
      ng_test(false);
    }, function () {
      ng_test(true);
    });
  }
]);

全体的にズボラな感じに仕上がっています。QUnitはよくできているのですが、もう少し手軽で気楽に書ければと思って作ってみました。今までテスト書かずに書き捨てていたコードにも少しはテスト書くようになりたいです。

まとめ

  • utestはQUnitを用いるのもちょっとかったるい時に使う手軽な道具
  • 世間のちゃんとしたユニットテストの代替じゃなくて、ユニットテスト使わずに書いていた(自分の)ところ向け
  • IE5.5で動く
    • けど、もうそういう古いの向けのコードは今後は書かないよ

記法の備忘録

まとめのあとに続けます。

上記のutestの記法は一番省略を効かせています。上記の例とは異なりますが、省略を行わずに書いた例を示します。

utest("sample", function (regist) {
  //  :
  // setup
  //  :
  regist([
    "sync1", function (test) {
      var test1 = test();
      test1(tmp.flag);
    },
    "sync2", function (test) {
      var test1 = test(), test2 = test();
      test1(tmp.bool);
      test2([ tmp.num, '==', '1' ]);
    },
    "async", function (test) {
      var test1 = test(), test2 = test();
      setTimeout(function () {
        test1(tmp.delay_bool);
        setTimeout(function () { test2([ tmp.delay_num, '==', '1' ]); }, 2000);
      }, 1000);
    }
  ], function () {
    //  :
    // teardown
    //  :
  });
});

setupとteardownが不要な場合があると思います。その場合、returnで書くこともできます。

utest("sample", function (regist) {
  return [ ... ]; // regist([ ... ]);
});

また、テストごとに関数で囲むのであれば、全体の関数も不要かもしれません。

utest("sample", [
  "sync1", function () { ... },
  "sync2", function () { ... },
  "async", function () { ... }
]);

さらにテスト名はもっとルーズに書ける方が良いでしょう。

// 関数に名前をつけた例
utest("sample", [
  function sync1() { ... },
  function sync2() { ... },
  function async() { ... }
]);
// オブジェクトを使った例
utest("sample", {
  sync1: function () { ... },
  sync2: function () { ... },
  async: function () { ... }
});

このように配列やオブジェクトで記述できます。なお、さらにルーズにテスト名を書かずに連番による自動命名に任せることもできます。

sync1は同期するテストですが、test()は関数を生成し、一次変数に格納しています。

function sync1(test) {
  var test1 = test();
  test1(tmp.flag);
}

これは以下のように書けます。

function sync1(test) {
  test()(tmp.flag);
}

一つ目のカッコが鬱陶しいのと、テストなら第一引数にbooleanかarrayかobjectしか入らないので、その場合は、カッコを省略できるようにしました(それ以外が入る場合があれば、自動命名が働くので注意する必要があります)。

function sync1(test) {
  test(tmp.flag);
}

テストセットの登録と同じようにtestの数が一つならreturnで返せるようにします。

function sync1() {
  return tmp.flag;
}

ものによっては関数スコープも不要でしょう。

tmp.flag

sync2も同期テストです。なお、二つ目のテストはreturnで置換可能です。

function sync2(test) {
  test(tmp.bool);
  return [ tmp.num, '==', '1' ]; // == test([ tmp.num, '==', '1' ]);
}

両方returnで記述することもできます。配列を使わないのはテストで配列を利用するためです。

function sync2() {
  return {
    bool_test: tmp.bool,
    num_test: [ tmp.num, '==', '1' ]
  };
}

名前も単なる連番でも構いません。

function sync2() {
  return {
    0: tmp.bool,
    1: [ tmp.num, '==', '1' ]
  };
}

自分で名前をつけるのが億劫なら引数に与えたものを上記の連番で名前をつけたオブジェクトに変換する関数もあります。

function sync2() {
  return utest.and(tmp.bool, [ tmp.num, '==', '1' ]);
}

この形になれば関数スコープもいりませんね:-)

utest.and(tmp.bool, [ tmp.num, '==', '1' ]);

非同期テストを見ていきます。

function async(test) {
  var test1 = test(), test2 = test();
  setTimeout(function () {
    test1(tmp.delay_bool);
    setTimeout(function () { test2([ tmp.delay_num, '==', '1' ]); }, 2000);
  }, 1000);
}

このテストも一見すると、以下のように書けるかもしれません。ですが、setTimeoutの関数が実行されなかった場合、テストがいくつあるかわからないため、実行漏れが起きてしまいます。そのため、非同期テストの場合はtest()でテスト実行関数の生成を必ず行う必要があります。

function async(test) {
  // 全部でいくつのテストをするの?
  setTimeout(function () {
    test(tmp.delay_bool);
    setTimeout(function () { test([ tmp.delay_num, '==', '1' ]); }, 2000);
  }, 1000);
}

ですが、setTimeoutを組み合わせてテストを書くことは多々ありそうです。そこでテスト関数の第二引数に遅延時間、第三引数に終了後実行関数を設定できるようにしました。

function async(test) {
  var test1 = test(), test2 = test();
  test1([ tmp.delay_bool ], 1000, function () {
    test2([ tmp.delay_num, '==', '1' ], 2000);
  });
}

ここでテストの意味は変わってしまいますが、このtest2はtest1が終わって2秒後ではなく、テスト開始からtest1の遅延を含めた3秒後に開始と読み替えます。すると、以下のように書けます。

function async(test) {
  var test1 = test(), test2 = test();
  test1(tmp.delay_bool, 1000);
  test2([ tmp.delay_num, '==', '1' ], 3000);
}

これなら、テストの初回の関数取得を省略して、こう書けます。

function async(test) {
  test(tmp.delay_bool, 1000);
  test([ tmp.delay_num, '==', '1' ], 3000);
}

そんなわけで、短くすると(色々副作用がある場合があるので場合によりけりですが)こう書けます。

utest("sample", [
  sync1: tmp.flag,
  sync2: utest.and(tmp.bool, [ tmp.num, '==', '1' ]),
  async: function (test) {
    test(tmp.delay_bool, 1000);
    test([ tmp.delay_num, '==', '1' ], 3000);
  }
]);

以上のように簡潔にサッとテストを書き残すことができます。

2012-04-02

入社しました

昨日付けで株式会社プリファードインフラストラクチャーに入社しました。新卒です。

今日に至るまで紆余曲折がありましたが、これから頑張っていこうと思います。今年度はブログをもう少し書きたいです。

2012-04-01

MacBook Airを買ったのでセットアップをします

入社するので、新しいマシンを買いました。4年近く使ったLetsnote(CF-W7)はスペック的にももう厳しそうだったので。最近ではディスプレイのバックライトにガタが来たのか画面が突然消えたりするので、そろそろ寿命という説もあります。ただWindows機はあって困らないので安いやつを今度探すつもりです。

そういうわけで、先日購入したMacBook Air 13インチ(Core i5、メモリ4G、ストレージ256GB)のセットアップをします。初めてのMacでワクワク。

開封後

マシンと電源ケーブルと小さいマニュアルだけ。mjd、と思ってしまう。リカバリ用のディスクとかUSBメモリとか無いのか―、と驚く。

電源オン後

  1. 言語選択
    • 軟弱なので日本語を選択
  2. キーボード入力環境
    • USキーボードだけど、「ことえり」でおk。そうじゃないと設定で日本語が入力できない。記号の配列がJISになったりするかと思ったけど、そんなことなかった。
  3. その他
    • タイムゾーンとかネットワークとか適当に設定。

起動後

  • iCloud
    • 勧められたのでアカウントを作ってみる。
  • カスペルスキー2012
    • セキュリティソフト。二年期限なんだけど一年期限のシリアルナンバーが二つ入っているので、どっち使ったかはメモ。
  • ソフトウェア・アップデート
    • 思い出したかのようにやっておく。結構時間がかかる。

再起動後

システム環境設定
  1. セキュリティとプライバシー
    • 「ファイヤーウォール」を入
  2. キーボード
    • 「キーリピート」を一番速く。
    • 「リピート入力認識までの時間」を一番短く。
    • 「F1、F2などのすべてのキーを標準のファンクションキーとして使用」をオン。
  3. トラックパッド
    • 「タップでクリック」をオン。
  4. サウンド
    • とりあえず「消音」。
  5. 共有
    • コンピュータ名を変更。
アプリケーションのインストール
  1. Google Chrome
    • デフォルトブラウザにする。
  2. Google 日本語入力
    • 言語とテキストの入力ソースからことえりとGoogle日本語入力のカタカナ・半角カナ・全角英数を無効化。
  3. Firefox
  4. Dropbox
  5. CotEditor
    • 少しはまともなエディタ。
  6. Skype
  7. Office mac
  8. VMWare FUSION 4
    • あとでWindows入れる。
  9. Emacs
    • これから慣れる
  10. Hoster
App Storeからのインストール
  1. Alfred
  2. Evernote
  3. 夜フクロウ
  4. MPlayerX
  5. ATOKPad

他に何か必要か探しています。

2011-09-05

jQueryのプラグインを三つ作りました。

細々とした奴を作ったので置いておきます。

keychar.js

キーコードを文字列に変換します。keyIdentifierが使えるようになるまでのつなぎ的なやつです。

$(window).keydown(function (evt) {
  if (evt.keyChar() === 'A') { //< ココ
    alert('shift + a');
  }
});

keydownでもkeyupでもkeypressでも変な記号でも割と動きます。

jquery.touchable.js

mousedown、mousemove、mouseupをtouchstart、touchmove、touchendに読み替えるライブラリです。普通にmousedown等を使っているだけのコードがtouchイベントで動きます。

まだ、mouseupのtouchendへの読み替えが手抜きなので、後々改善したいです。三つとも使うドラッグとかは綺麗に書けます。これでマウス用のざっくり書いたイベントがタッチで動くようになりました。

改善しました。

jquery.loaded.js

完全に状況を掌握した画像の遅延読み込みの実現 - latest log JavaScript で、画像本来のサイズ(幅, 高さ)を取得する方法 - latest logをjQueryプラグインとして実装したものです。

HTML中に書かれているimgタグに対しても利用したかったので、読み込みが終了している場合も既に読み込んでいるという扱いで着火します。

$('img').loaded().success(function () {
  alert('loaded: ' + this.src);
}).error(function () {
  alert('error: ' + this.src);
});

もちろん、新規作成時のonloadとしても使えます。

あと、iframeとかscriptの読み込みの監視もできるようになっています。同じドメインのiframeを操作するためにもjQuery.sub(iframe.contentWindow)とかできるようになってほしいですね。

2011-06-09

Googleギターを自動演奏しよう!(Firefoxのみ)

最近のGoogleロゴは遊べるものが出ていて、中々非常に面白いです。さっそく2chではキー入力による演奏例が登場し、力作が投稿されています。しかし、これらの力作を正確に手元で再現するのは中々難しいです。特にタイピングが苦手な僕にとっては難易度が高いです。というわけで、投稿作を自動演奏するブックマークレットをサクサクッと作ってみました。

javascript:(function(D){function cn(t){return D.createElement(t)}var dv=cn('div'),sc=cn('input'),tm=cn('input'),bn=cn('button'),lga=D.getElementById('lga');sc.size=30;tm.size=10;tm.value=100;bn.appendChild(D.createTextNode('play'));dv.appendChild(sc);dv.appendChild(tm);dv.appendChild(bn);lga.parentNode.insertBefore(dv,lga.nextSibling);bn.addEventListener('click',pl,false);function pl(){bn.disabled=true;var scs=sc.value.replace(/^\s+|\s+$/g,'').toLowerCase().split(''),ms=parseInt(tm.value,10);(function lp(){if(scs.length){var cc=scs.shift().charCodeAt(0),kc=48<=cc&&cc<=57?cc:97<=cc&&cc<=122?cc-32:0;if(kc){var e=D.createEvent('KeyboardEvent');e.initKeyEvent('keydown',true,true,null,false,false,false,false,kc,kc);D.dispatchEvent(e);}setTimeout(lp,ms);}else{bn.disabled=false;}}())}}(document))

FirefoxでGoogleのギターを開いて、上記のブックマークレットを実行して下さい。演奏コードの入力枠、音間隔の入力枠、演奏実行ボタンがロゴの下に表示されます。2chまとめブログなどに掲載している奴をコピペしてやると、イイ感じに鳴ってくれます。

なお、Chromeでは動きません。initKeyboardEventでkeyCodeがうまく設定できないからです。多分、まだサポートされていないのだと思います。動かし方があれば、誰か教えてください。