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

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

最近、大規模だったり高速だったりという需要に応える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);
  }
]);

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

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 日本語入力
  3. Firefox
  4. Dropbox
  5. CotEditor
    • 少しはまともなエディタ。
  6. Skype
  7. Office mac
  8. VMWare FUSION 4
  9. Emacs
    • これから慣れる
  10. Hoster
App Storeからのインストール
  1. Alfred
  2. Evernote
  3. 夜フクロウ
  4. MPlayerX
  5. ATOKPad

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

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 logJavaScript で、画像本来のサイズ(幅, 高さ)を取得する方法 - latest logjQueryプラグインとして実装したものです。

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

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

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

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

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))

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

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

ア行は本当に多いのか?

昨日このような話題がありました。確かに自分の大学の名簿もア行が1/3ぐらいいたような気がします。
日本の苗字7000傑 1-1000位のデータを元に、日本の苗字のトップ1000に一般的な訓読みを付与して、どの五十音の行が多いか、人口比を出してみました。

ア行 18.1%
カ行 15.7%
サ行 13.7%
タ行 10.6%
ナ行 8.68%
ハ行 10.3%
マ行 10.9%
ヤ行 9.25%
ラ行 0.67%
ワ行 2.01%

なんと人口比でア行は一番多いことがわかりました。

という話もあるので、トップ1000の苗字を五十音の行ごとに分類してみました。

ア行 209種類 20.9%
カ行 166種類 16.6%
サ行 114種類 11.4%
タ行 117種類 11.7%
ナ行 84種類 8.4%
ハ行 122種類 12.2%
マ行 115種類 11.5%
ヤ行 62種類 6.2%
ラ行 3種類 0.3%
ワ行 8種類 0.8%

やっぱり、ア行がすごく多いです。

しかし、三割を超えると何か偏っているのかもしれません。統計的な誤差ってどこまで入るんでしょうかね……。

結論

これは幻想なんですね……。

どっちかというと、記事よりもtableのスタイルを書くのに時間がかかったような。

ここのブログとサイトのリデザインをしました

自分のブログなのに少し読みにくくて、メモとしても活用できなかったので、リデザインしました。ついでに、0fk.orgのリデザインも行ってみました。

0fk.orgでやったことは文字列が切り替わるJavaScriptを書きなおしたことぐらいです。ついにIE5をサポートブラウザから切りました。というか、今までIE5で動いていたことに僕もびっくりです。これからはメンテコストを押さえるところはjQueryYUI CSSを活用していきたいですね。

ということで、そのどちらの恩恵もあずかれないはてなブログのデザインのハマりどころをまとめておきます。

@importがダウンロードされたデータなので困る

Google Web Font APIで少し困るという話です。

はてなは危ないCSSプロパティ(behaviorとか)使わせないために、@importを書くとはてな側がダウンロードしてくるデータを貼りつけてきます。普通はあまり困らないのですが、Google Web Font APIの場合はユーザーエージェントでダウンロードさせるCSSを振り分けてきます(その仕組も正直どうかと思っていますが)。

というわけで、手元にUA変えて落として、woff、ttf、eotのフォントのURLを入手して、振り分けてやるコードを書くとみんなハッピーになれると思います。まぁ、はてなに任せたらttfしかダウンロードされないCSSを貼りつけてくるので、IE以外のブラウザでは表示されると思いますけどねw

http://fonts.googleapis.com/css?~を@importで読み込む場合は、はてな側で展開しないとかという仕様になってくれると嬉しいのだけど、まぁ、ならないでしょうね……。

普通の記事にクラスが振られていないので、その場で編集モードのデザイン崩れてちょっと困った

タイトル通りです。

普通の記事はdiv.body>div.section 本文の要素だけど、その場で編集モードはdiv.body>div.section>form 編集関連の要素なので、本文中のimgタグだけ色々やろうとdiv.body>div.section imgと書くとその場で編集モードでぐちゃぐちゃになって泣けます。

記事であるものだけのクラスがあればいいのだけど、そんなものはないのでinheritでポチポチ打ち消す必要があります。

以外とタグが少なくて焦る

細いヘッダー部分を簡単に表すと以下のようになります。

<div id="simple-header">
  <!-- ここから -->
  <a><img id="logo-hatena" /></a>
  <a><img id="logo-diary" /></a>
  <!-- ここまでdivなりpなりで挟みたい -->
  <form>
    <input type="text" class="search-word" />
    <input type="submit" class="search-button" />
    <input type="submit" class="search-button" />
  </form>
  <ul class="menu">
    <li><a>...</a></li>
    ...
  </ul>
</div>

コメント部分も同様に示すと以下の感じになります。

<div class="comment"> 
  <div class="caption" />
  <div class="commentshort">
    <p class="commentdelete">スパムコメントの報告</p>
    <p><!-- これもクラスが欲しい -->
      <input type="checkbox" />
      <span class="commentator">
        <a href=".." class="hatena-id-icon"><img src=".." class="hatena-id-icon">..</a>
      </span> 
      <span class="timestamp"><a>YYYY/MM/DD HH:MM</a></span>
      <span class="commentbody">..</span>
    </p>
    <p class="commentform">コメント投稿フォーム</p>
  </div>
</div>

全体的にクラスとかもうちょっと欲しいんだけどなーって思えるようなHTML構成でした。

SVG背景に貼れなかった

background-image: url('data:image/svg+xml;base64,..');という部分がbackground-image: url('');base64,..');ってなった。Opera残念でしたーって感じですね。単純に不正なURLを削る処理でしょうけど。

他、頑張ったところ

IE対応は適当にやりました。なぜか、position:absolute;効かなくて諦めた。

まとめ

楽しかった。けど、当分はてな向けにCSSとか書きたくないやw