✖
node.js で Perl における Test::TCP または Ruby における Glint
Perl には Test::TCP というのがあって、テスト中、空いてるポートで何かしらのサーバーを起動して使うということができます。Ruby においては Glint というライブラリがあって、同じことができます。
node.js の場合、node-test-tcp というのがあって、node の net.Server で動くサーバに関しては簡単に同じことができます。が、memcached とか外部プロセスを起動させようとするとちょっと困るのと、done() を呼ばないと終了しないので、何かいい方法はないかなと思ったので書いてみました。
test-tcp だとカブるので glint のほうの名前を仮りています。
つかいかた
glint(
function (port) {
// ここは外部プロセスで実行される
// ただし文字列化して関数が渡されるので外のスコープの変数は使えない。
// node.js の exec, execFile はいわゆる exec ではないので1つプロセスが余分にできる。我慢するしかない。
console.log('starting memcached with port: ' + port);
require('child_process').execFile('memcached', ['-p', port]);
},
function (error, server) {
if (error) throw error;
// server は起動が確認済み
// server.port でポート番号がとれる。
// server.kill() でこのプロセスだけ殺せる
console.log(server);
var s = net.connect(server.port, function () {
s.write("version\r\n");
});
s.on('data', function (data) {
console.log(data.toString('UTF-8'));
s.end();
});
// この関数を抜けても exit されるまで server は (明示的にkillしない限り) kill されない
}
);
しくみ
他のモジュールとやってることは一緒なのですが、node.js だと面倒な点がいくつかあります
- fork / exec はいわゆる *nix の fork/exec ではない
- オブジェクトファイナライザ的な仕組みがない
- 特定のオブジェクトが破棄されるタイミングで処理を行うことができない
node の fork / exec (execFile) は *nix の fork / exec とは全く違うので、細かいプロセスの制御ができません。特に exec 単体相当はないので、現在のプロセス自体を置き換えるということができません。そんなわけで1つ別プロセスを経由している関係で、無駄に1プロセスを消費しています。
オブジェクトのファイナライザがないので、node-glint では process.on('exit') で起動したサーバを終了しています。ただし、普通に外部プロセスを起動すると、そのプロセスの終了を待つ挙動になり、exit されません。が、spawn() のオプションに detach: true を指定した上で、unref() を呼んであげることで、外部プロセスの終了に関わらず起動元の node プロセスを exit させることができます。
まとめ
一応形にはなりましたが、細かい挙動の検証がめんどうなので npm にあげてません。特に現在の実装はシグナルまわりのハンドリングがいい加減です。node マスターの皆様におかれましては、よりよい方法をご教示頂ければと思う所存です。
AngularJS のテスト
とりあえず、2種類のテストがあり、どちらも十分なサポートがされている。
基本的にangular-seedというのを元に作ればいいんだけど、e2e (end to end) テストについては protractor というのを使うのが新しいようなので、今からはじめるならそちらを使ったほうが良い。
karma での unit テスト
node で完結する、ロジックの単体テスト。主に controller とか filter をテストする。controller で DOM を直でいじっていると実行できない。
サーバサイドとかとの通信とかは全てモックにしなければならない。Angular の DI の仕組みで、モックオブジェクトを外部から注入して単体テストを完結させる。
いろいろ面倒くさいけど、これを書くようにすることで controller / directive の使いわけとかを意識せざるを得なくなるので良い気がする。
protractor による end to end テスト
selenium を使った結合テスト。
protractor は Angular JS 用の e2e テストライブラリ。簡単に selenium-standalone をセットアップするところから、テスト用のユーティリティまでのセット。ドキュメント の通りにやれば OS X では全く苦もなく selenium 環境を作りテストを開始できる。
どこが「Angular JS用」なのかというと、ページロードとか、イベント発火とかで、いちいち自分で wait() を書く必要がなく、Angular 準拠の部分は自動で処理待ちをするので、かなり楽をできる。
karma か protractor か
- karma のテストは早い
- protractor (selenium) は遅い
ので、パターンを網羅したロジックは書きたいなら karma で完結するように書いたほうがいい。
AngularJS のテストでページ側のスクリプトを実行する
protractor (webdriver) を使った場合、外から executeAsyncScript を使うと文字列でページ側で実行できる。
けど、文字列で渡すとか、シンタックスチェックもかからないし、ありえないので、定義自体は普通に書きたい。ので以下のような関数を定義する。
var PageObject = function () {
this.exec = function (func) {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = '('+ (func.toString()) + ').apply(null, arguments);';
browser.executeAsyncScript.apply(browser, args);
};
this.createEntry = function (data) {
this.exec(function (data, callback) {
angular.injector(['myApp']).invoke(function (Entry) {
var entry = new Entry();
for (var key in data) if (data.hasOwnProperty(key)) entry[key] = data[key];
entry.$save(callback);
});
}, data);
return data;
};
};
この例では、定義した exec 関数を使って、ページ側の ngResource で定義したクラスを使い、テスト用のデータを生成する createEntry メソッドを定義している。
Selenium を使ったテストでわからないこと
- テスト用のデータのロード方法がわからない
- テスト間を独立させる方法がわからない (データの初期化とか)
AngularJS でまだわからないこと
- 1つのページに複数のコントローラーを定義し、それぞれを連携させる方法がわからない
- scope はどうなる?
- スコープまわりがよくわかってない
- $on、$apply の伝搬がよくわかってない
- DI まわりがよくわかってない
- 何を service にして何を provider にするのかとか
1アマ (第1級アマチュア無線技士) 免許がきた
12月10日に受験して、12月24日の合格発表と同時に申請を出し、免許日は1月9日で、それから2日後には不在票 (書留にしたので) が入ってた。思ったより早かった。営業日的には7日ぐらいかな。
今まで持っていたやつはラミネート加工されたやつだったけど、新しい形式になっていわゆるカードサイズになったのと、英語表記が併記されるようになった (電波の性質上、国際的に使われうる免許だからかな) のと、ホログラム (富士山と桜?) っぽいものが全面に入るようになったみたい。
無線ルーターを変えた
評判の良いこれを買った。セットアップはあまり迷うところもなく終わった。
ただ、USB ディスク共有だけなんかおかしくて、smb://192.168.0.1/Hitachi-1 みたいな感じで、全部指定して接続をかけないと失敗する (Mac OS X)
NEC Aterm WR9500N で Time Capsule 的バックアップ (履歴引き継ぎ)
初回はうまくいったけど、時間が経ってからもう一度やろうとしたらうまくいかなくなった。Mac の smb 接続が不安定になったりとかいろいろした結果、以下のような状態っぽいことがわかった (推測)
- スパースバンドルのサイズが大きいとディレクトリエントリが多くなる
- ルーターがメモリサイズ的にそのサイズのエントリを扱えなくなる
- 死ぬ
なので、小さいスパースバンドルにするかなんとかしたらうまくいくかもしれないけど、もう諦めた…… 時間の無駄だった。
初回うまくいったのはよくわからない。Mac 側になんらかの形でエントリのキャッシュがあったのかもしれない。なんでもいいけど…
買ってから気付いたけど、USB ポートがついていて、USB HDD を繋いで SMB 共有ができるようになっていた。
せっかくなので、今まで有線USBでバックアップしていたのを、無線経由でバックアップするように変えた。
Time Machine をオフに
まず既存 Time Machine を OFF にする。
現状のバックアップディスクをバックアップ
HDDを新しく買ってくるならこんなことしなくていいんだけど、買いたくないので、一旦現状のバックアップディスクを、別にディスクに .dmg としてバックアップした。
バックアップディスクとなるスパースバンドルファイルをつくる
ディスクユーティリティでスパースバンドルファイルをつくる
- サイズはとりあえずデフォルトのまま
- 手元のディスク容量よりも大きいサイズには作れないため
- フォーマットは Mac OS 拡張 (ジャーナリング)
- イメージフォーマットはスパースバンドル・ディスクイメージ
作成したらアンマウントして、NASに繋げるディスクにコピーする。最初のバックアップまでは USB で直接繋いで作業する。
コピーしてからもう一度そのイメージをディスクユーティリティで開いて、「イメージのサイズを変更」を行い、2TB ほど容量を確保する。
バックアップをスパースバンドルイメージに復元
スパースバンドルファイルに対して、既存のバックアップの dmg を復元する。死ぬほど時間がかる。
NAS に繋げてバックアップ開始
ディスクをNASに繋げ、共有フォルダを開き、イメージをマウントする。
イメージをマウントして、マウント済みディレクトリに対し、以下のコマンドを実行。GUI を使わないので強制的に設定できる。
sudo tmutil setdestination /Volumes/Time\ Machine
これでバックアップを再開するとネットワーク経由でバックアップが開始される。履歴も引き継がれ、差分バックアップになる。