PySpec3について
そろそろberryMQも実装が落ち着いてきているので(マルチスレッドのロックとか、アマアマな部分はたくさんあると思うけど)、pySpec3も考え始めようかな、と思っているところです。
berryMQはもともとPySpecのリライトのためのバックエンドとして作り始めたのだけど、その後に知って、現在絶賛猛プッシュ中のSphinxに出会ってからはちょっと別路線を考え中。Cucumberみたいなのも面白そうだしね。あと、pyspecは記号が多くて、シフトキーで指が疲れるという問題もあるので。ということでSphinx拡張&ビルダーとして実装する方向で検討中。TDD→BDDはドキュメント化の流れなので、いっそのこと、ドキュメントに埋め込んでしまう方が潔いはず。とちぎRuby会議02では「文芸的テスト」と言っていたけど。
Sphinx拡張は、htmlやlatex、pdfの場合には整形してテスト仕様書として出力し、pyspecという名前のビルダーだったら、構築後のイベントを拾って、中のテストを全部たどって実行・・・という流れかな。普通にUnitTest的に実装していたpyspec2までは、before, specは関数として個別に実行していたけど、くっつけたコードを作ってevalしてやればいいか。エラーの見せ方とかは工夫が必要かな。
BDD的なサンプル
.. pyspec:: .. before:: 空のスタック empty_stack = Stack() .. spec:: 空かどうか判断できる empty_stack.empty() should be True
cucumber的なサンプル
.. feature:: ログインページ :when: ログインページを表示して :and: フォームのユーザ名に"test", パスワードに"testpass"と入れて送信ボタンを押すと :then: ログインに成功する
最近悩んでいるのが、テストコードがシンプルになるように強制するかどうか。たとえば、ユニットテストのフレームワーク上で、
ちょっとの行数しか書きたくなくならないような、文法にしてもいいけど、あまり強制しすぎて、テストのためのアダプターコードが増え過ぎちゃうと、逆に仕様変更に弱いコードになりそうな気もするし。適度に無駄は必要なんだろうな、と思っているところ。
berryMQ作業メモ 9/27版
http://bitbucket.org/shibu/berrymq/
http://berrymq.shibu.jp/
プロセス間通信の実装メモ
- JSON-RPCをバックエンドに利用
- Connectionクラスを作って、そいつがJSON-RPCの次に低レベルな部分を担当
- Connectionクラスは基底クラスと、Style02のプロセス間通信のクライアント/サーバ
- ConnectionQueueクラスはStyle03のプロセス間通信のサーバ側
- ConnectionQueueClientクラスはStyle03のプロセス間通信のクライアント側
- ConnectionInteractiveクラスは、Style01のプロセス間通信のクライアント/サーバ
- JSON-RPCに公開するのは、ExportedFunctionクラスのオブジェクト
- こいつはフォワードするだけで、中身はほぼ空。矢面に立つクラスは仕事しない方がいいと思われるので。主にセキュリティ的な意味で。
- 仕事するのはConnectionPointクラス
- 他のノードとのコネクション情報などを管理する。
- berryMQのローカル通信で来たメッセージを外部に中継する
- 外部から来たメッセージをローカルに中継する。また、他のノードにも中継する
ネットワークのプログラムの開発手順はいつも悩む。今回はJSON-RPCの直接アクセス部分と、高水準なAPIの2段階に分けて行った。今回はやらなかったけど、JSON-RPCやXML-RPCであれば、Excelか何かに応答手順を書いておいて、クライアント、サーバの両方のモックを用意してテストする、というのもできるかもしれない。以前はMockSocketなんぞを作ってみたけど、あまり低水準すぎてもテストはしにくいので。
berryMQ作業メモ 9/24版
http://bitbucket.org/shibu/berrymq/
http://berrymq.shibu.jp/
こちらに書くのは久しぶり。プロセス間通信の実装中。
berryMQ作業メモ 8/1版
http://bitbucket.org/shibu/berrymq/
ちょこっとずつ実装してます。
前回から完了した項目
これから実装、移植、テストを行う機能
今回の一番大きかった作業はJSON RPCかな。表面的な部分はほとんど変わってないですが。まずはRubyのマルチスレッド対応からかな。現状のpush型のAPIだけだと、メッセージが来たらほとんど即時でクライアント側で呼び出されるので、優先順位は入れてもほとんど無意味だけど、pull型のAPIができたらちょっとは意味があるっぽい。今はtwitter()というメソッド名でメッセージを送るけど、優先順位はtwitter_louder(), twitter_loudest()の順番に高くなる、というAPIにする予定です。よく分からない数値よりも分かりやすいでしょ?
id:Voluntas とチャットしながら仕様を決めているけど、意見交換するといいね。
berryMQ実装メモ
http://bitbucket.org/shibu/berrymq/
今まではmqasとかいう、タイプしていても手がつりそうな名前でしたが、 id:voluntas とチャットしていて、berryMQという名前になりました。オンメモリで永続化などはしない、簡易軽量なMessage Queueのライブラリ。ライブラリの目標としては、簡単にプログラミングできることが一番重要な目標。小さいとか、そういうイメージが伝えられるような名前に。そんでもって、親しみやすい名前に。ということで、実装メモなどはこちらに書いていきます。上記のところには日本語は書きません。
実装済み機能
これから実装、移植、テストを行う機能
これから実装方法を決める機能
- priority queue
- 同期送信
- 同期送信で送信エラーがあったときのコールバック
Erlang実装パターン2
id:voluntas からskypeで教わった話も一緒に入れています。
EDocスタイルの型宣言
コメントで@spec, @typeというのを使って型の宣言をする。ドキュメントジェネレータ、静的プログラムチェックツールで使用される。これを言語仕様として拡張して入れてしまおうというEEP-8(http://articles.shibu.jp/article/30833304.html)もある。ただし、使っているのはRabbitMQぐらいらしい。EUnitはEDoc。
@typeは型の宣言。@specは関数の入出力の宣言に使うみたい。typeは関数宣言部でも@specの補助として使えるっぽい。@throwsを使うと、例外が投げられる可能性についても書ける。
%% @type moduleName() = atom() %% @type functionName() = atom() %% @throws {bad_test, term()} %% | {generator_failed, exception()} %% @spec (testIterator()) -> [integer()] iter_id(#iter{pos = N, parent = Ns}) -> lists:reverse(Ns, [N]).
配列の操作。
nextが空か、そうじゃないかで関数を分けている。典型的なパターン。
# python def iter_next(I): if len(I.next) == 0: 空のときの処理 else: そうじゃないときの処理
関数直下のif文はこのように分解する。もちろん、caseも使えるはず。
%% Erlang %% @spec (testIterator()) -> none | {testItem(), testIterator()} iter_next(I = #iter{next = []}) -> nextが空の時の処理; iter_next(I = #iter{next = [T | Ts]}) -> nextが空じゃない時の処理.
if文はcaseに置き換える
Pythonの場合はこう。
if is_string(S): true時の処理 else: そうではない時の処理
Erlangだとこうかく。
case is_string(S) of true -> true時の処理; false -> そうではない時の処理; end
例外処理
try 正常時の文 catch 例外の型 -> キャッチしたときの処理 end
throw({型のアトム, 追加情報})
で例外を投げる。
文字列の結合
"test: " ++ S
と結合できる。
try ofという書き方もある
もちろん、 ->をたくさん書けば返り値でパターンマッチで処理の振り分けもできる。ただし、こういうコードは今のところお目にかかったことがない。プログラミングErlangの本の中にもない。 id:voluntas によると、これをやるぐらいなら、tryの返り値に対して、case ofを使った方がいいよ、とのこと。僕もそう思う。
try M:module_info(exports) of Es -> Fs = get_module_tests_1(M, Es), W = ?DEFAULT_MODULE_WRAPPER_NAME, case lists:member({W,1}, Es) of false -> Fs; true -> {generator, fun () -> M:W(Fs) end} end catch error:undef -> throw({module_not_found, M}) end.
宣言的なコメント
将来はこうしたいけど、今はうまくいかないからこっちを使っている、というコメント。テスティングフレームワークみたいに、自分で自分のテストなど、ドッグフード系のシステム開発にはこういうのは不可欠かも。Erlangに限った話ではないけど。
%%{application, eunit}. % this currently causes a loop %% We use the below until loop detection is implemented
タプルをそのまま画面に出力できない
io:format(atom).
はOKだけど
io:format({atom, "sample test"}).
はエラーで落ちる。ちなみに、formatは引数を一つだけとれる。一つしか取れない。
whenはパターンマッチ後に使える
whenを指定すると、パターンマッチの情報を詳細に指定できる。whenにはオリジナルの関数はつっこめないけど、マクロはOK。