その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

chrome.storage.*.set()でInfinityやundefinedやNaNを格納出来ない問題

Chrome extensionで,extension localで横断的にlocalStorageを利用するためのAPIであるchrome.storageですが,chrome.storage.local.set()chrome.storage.sync.set()ではInfinityundefinedNaN*1 を格納することが出来ません.

以下検証コードです


localStorageではこれらの値は正しく取り扱うことができるので,chromeの仕様あるいはバグ感があったのでIssueを投げました.
Issue 380509 - chromium - chrome.storage.*.set() cannot set Infinity, undefined, and NaN - An open-source project to help move the web forward. - Google Project Hosting


使えるようになって欲しい感じです.

*1:とはいえNaNを格納したいことはあまり無いでしょうが

Released Log::Minimal::Object

皆さん,Perlのアプリケーションのログを出力したい時はLog::Minimalをご利用のことと存じます.非常に便利でグレートなモジュールなので僕も毎度使っております.


さて,そのLog::Minimalですが,ログの出力をカスタムしたい時には$Log::Minimal::COLOR$Log::Minimal::TRACE_LEVELなどといったLog::Minimalの持っている変数をいじってスコープごとにゴニョゴニョ〜〜みたいな感じにやるわけですが,ある程度複雑な条件に基いてログをカスタムしたい時 (例えばローカルからのアクセスの時だけ特殊なフォーマットでログを吐きたい,だとか) なんかは記述量が増えたりそのロジックが煩雑になったりして,ログ出力の為の記述が本質的なロジックの記述を食ってしまう場合があったりします.


そうした背景からこの度リリースしたのがLog::Minimal::Objectです.
https://github.com/moznion/Log-Minimal-Object
https://metacpan.org/pod/Log::Minimal::Object

Log::Minimal::ObjectはLog::Minimalに対するOOPインタフェースを提供する簡単なラッパーです.
使い方はドキュメントを読んでもらうとして,その特徴はインスタンスごとに独立して出力のカスタマイズ出来るという点にあります.あらかじめ個別に設定したロガーインスタンスを作っておいて,条件に基いて出力するロガーを切り替えてやる,という使い方が出来ると便利なのではないかと思ったのが主な動機です.
また,副次的な感じではありますが,ログを出力するメソッドをインスタンスごとの粒度で乗っ取る,みたいな使い方もできるのでテストの時などに便利なのではないかと思っています.


まあ,ある程度の規模感のアプリケーションだと独自でロガークラスを作ってそいつに仕事をさせる事も多いと思いますが,こうしたカジュアルに使えるモジュールがあっても良いのではないか,と思って作った次第です.ご利用下さい.

Released Parse::KeyValue::Shellish

https://metacpan.org/pod/Parse::KeyValue::Shellish
https://github.com/moznion/Parse-KeyValue-Shellish

foo=barだとかhoge=(fuga piyo)みたいな,いわゆるshell的なkey-valueを解釈する必要があったので書きました.shell的key-valueの文字列を解釈してhashrefに包んで返します.例えばfoo=barを食わせると{foo => 'bar'}が返って来て,hoge=(fuga piyo)を食わせると{hoge = ['fuga', 'piyo']}という感じでarrayrefが格納されるという寸法です.
類似のモジュールはいくつかあったんですが,ファイルにしか対応していなかったり (文字列を食わせられない) ,Yappを使っていてメッチャゴツい! みたいな感じだったりだったので,フルスクラッチで書きました.1文字ずつ読み取って正規表現でバリバリ解釈するという実装なので,依存がほぼありません *1

あくまでshellishなので完全にshellをエミュレートし切れてはいませんが,ある程度なら動くと思います.詳細はREADMEを参照して下さい.
「これが動かないとはけしからん」とかありましたら,サポートするかもわかりませんのでイシュって下さい.

以上現場でした.

*1:parentに依存しているくらいだと思う

「メソッドに対してテストをするな」という話題について

―「間違っているかもしれないので,その時はこの銃で僕を撃ってくれ,良いね?」


[2014-05-19T17:48:28Z 追記]
http://a-suenami.hatenablog.com/entry/2014/05/17/131326
補足してもらったので読むと良いと思います.
わかっちゃはいたけれど上手く言語化できていなかった部分,あるいはわかっていない部分について言及されていたので参考になりました.ありがとうございます.

いやーつーかさー,「『メソッドに対するテスト』っていう言葉自体がわかりにくくね?」っていうのはその文言を見たり,この文章書いている間もずっと思ってて,つまり端的に言うとそういう事を言いたかったはずなのに,今このエントリ読み返すとそうした[趣主]旨から完全にズレててメッチャ違和感あるな! ってなりました.俺のバカ!

これを仮に言い換えるとしたら「内部構造に対するテスト」とかそういうのになるんですかね? 難しいな!
[追記ここまで]


良いテストを書くための指針として「メソッドに対してテストをするな」という言葉がありますが,これは「ユニットテストに対して分かりやすい (説明的な) 名前を付けろ」ということを言っているのであって,「メソッド単体のテストをするな,ある機能を満足するシナリオについてテストをせよ」と言っている訳ではないと思います.


極端なコード例ですが (追記: ***念の為*** 書きますけどfoo()というふざけた関数名は伏線ですので回収されるまで待つこと.だから「極端」って言ってるんですねハイ),

sub foo {
    my $num = shift;
    return ++$num;
}

という,受け取った引数をインクリメントしたものを返す関数があったとして,これに対して

subtest 'test foo()' => sub {
    is foo(1), 2;
};

というテストを書いたとしても,この“test foo()”という説明からは,このテストが何についてテストをしているのかがわかりません.これがいわゆる「メソッドに対してテストをしている」状態なのだと思います.

そこでこのテストを

subtest '#foo()' => sub {
    subtest '引数がインクリメントされる' => sub {
        is foo(1), 2;
    };
};

という風に書き換えてやると,テストの対象と期待する動作が明確になってマシになった感じがします.これがいわゆる「メソッドに対するテスト」ではない状態と言えるのでは無いでしょうか.
(というか,foo()とかいうふざけたメソッド名はいったいなんだ!!! 後述!!!)


そもそも,「あるシナリオを満足しているかどうかをチェックするテスト」と「メソッドに対するユニットテスト」はそれぞれ負っている責務が異なるように感じています (前者は「振る舞いテスト」と呼ばれるかもしれません) .
振る舞いテストは「ビジネスが要求するシナリオが壊れている (満足していない) 時に教えてくれる存在」であり,これを満足しない限りはコードを成果物としてリリースが出来ないという受け入れテスト的な側面を有している一方,
ユニットテストは「どこが原因でそのシナリオが壊れているのか」あるいは「どうすればそのシナリオを満足することができるのか」といった問題を切り分ける為に役に立つ情報を提供してくれたり,コードの一部を修正した時にそのメソッドが提供する単一機能の挙動が変わってしまってはいないかをスピーディに確認出来るのでコードに対する変更を恐れずに済んだり,といったメリットを享受する為の開発者向けのテストであるように思います.ただし,ユニットテストだけでは「シナリオを満足しているかどうか」を担保することは出来ないと思います.

端的に言うと,ソフトウェアがちゃんと動いているかどうかを担保する為には振る舞いテストだけで十分という事になりますが,その開発の効率やコードの品質を向上させたい場合にはユニットテストが必要になるという事になると感じています.
たいていの場合は,開発にスピード感が無ければプロジェクトは死ぬし,コードの品質が劣っているとプロジェクトは地獄と化した末に死ぬので,結果的にユニットテストも書くことになると思います.

従って,振る舞いテストとユニットテストはどちらか一方だけを書けば良いというものではなく,つまり「メソッドに対してテストをするな」という言葉は「単一メソッドに対するユニットテストをするな」という論旨のものではない,という認識です.


ところで,メソッドの命名が適切になされていれば (つまりメソッドの名前が説明的であれば) ,いわゆる「メソッドに対するテスト」を書いたとしてもおのずと (ある程度) 説明的なテストになると思うのですが,どうか.
いわゆる「メソッドに対するテスト」を書いてそのテストの情報量が低い場合は,プロダクションコードの方にも何らかの問題があるように感じていますが,どうでしょうね.という事を上のコード例を書いている時に思いました.

「Kyoto.なんか」でgithub-commit-comment.vimの話をしました

Kyoto.なんか」というイベントで,最近書いたgithub-commit-comment.vimというVimプラグインの話をしてきました.お招きいただきありがとうございます.

github-commit-comment.vim

https://github.com/moznion/github-commit-comment.vim


Vim上からGitHubのコミットコメントの投稿と参照が出来るというプラグインです.
GitHubのコミットコメントには,

  • 行単位のコメント
  • ファイル単位のコメント
  • コミット単位のコメント

と,3種類のコミットコメントが存在しており,これらすべてに対するコメントが可能となっています.
詳しくは上記URLのREADMEを読むと良いと思います.Gifアニメを大量に貼っているので,なんとなく雰囲気がつかめるでしょう.
なお,READMEには書いていないのですが,このプラグインはcommit commentの内容をすべてquickfixに流し込んでいるので,
:cwindowコマンドを発行してやると,そのファイルに対するコメント一覧を見ることができます.地味に便利だ.


開発のモチベーションとしては,コードレビューをする時 (というかコードを読む時) に任意のエディタでやりたいという欲求があって,僕の場合はVimでそれをやっているんですけれども,
Vimでコード読みながら「コミットコメントを付けたいなーー」って時にGitHubが開いているブラウザとそのエディタとの間を行ったり来たりするのは大変に面倒なわけで,
そこら辺を上手く解決したい!!!!! という事でグワーッと書いたという感じです.


ひとまず便利は便利なのですが,色々と問題点というかわからない部分があって,それは例えば

  • コメントは,HEADのリビジョンではなく,そのファイルが最後に変更されたリビジョンに反映されるのはOK?
  • そもそもgitのoriginに決め打ちなのはどうなのか
  • 行単位のコメントをした時に,そこが変更点ではない *1 場合に微妙 (とはいえexpandすれば見れる)
  • 実際Pull-Requestとかでも使いたいよねー

などなど色々あり,ここら辺は使っていって追々機能を追加したり修正したりしていく感じかなあと思っています.


類似のプラグインvim-github-commentというものがあるのですが,
こちらは1行しかコメントを追加できなかったり,Vimからcommit commentを参照出来なかったりしてちょっと不便,
というか思想が違うのかな,と思ってフルスクラッチで書いた次第です.


フィードバック等頂けると嬉しいです.

*1:つまりGitHubのdiffとして現れない

Ukigumo入門 ― 2014年スタイル

とりあえずデモサイトを示しますので適当に見て下さい.
http://ukigumo.moznion.net/

さて今回はゆるふわCIシステムであるところのUkigumoのナウいスタイルについて説明しようと思います.
ここ最近ではUkigumo::AgentというAgentサーバが存在しており,これを使うとまあ便利なんですけれども,ドキュメントが少ない為か *1 あまり利用されている事例を見かけませんので,それらも踏まえて解説したい感じです.

まずUkigumoとは何か

CIシステム.Perl製.
Perl製だが,もちろん他言語のプロジェクトでも使える.

Travisのようにサービスとして提供されている感じではなく,自前でインストールして使う.
多分,感覚としてはJenkinsに近いと思うが,そこまで複雑ではなく,シンプル.
基本的に,「テストの実行及びその結果の取得」と「テスト結果の保存」が主な仕事.詳細は後述.

Ukigumoの登場人物

Ukigumo::Server

Clientから送られてくるテスト結果を保存する為のサーバ.
保存しているテスト結果をブラウザから閲覧するためのウェブアプリケーションもサポートしている.
端的にいうとCPAN Testers Reportsみたいな存在.

Ukigumo::Client

実際にテストを走らせて,Serverにその結果を送る存在.
例えば,Ukigumo::Clientのインスタンスを作って,$client->run()などと実行してやると,テスト対象のリポジトリをclone or pullしてきて,実際にテストを走らせる.そしてその結果をServerに対してPOSTしてその儚いライフサイクルを終える.

Ukigumo::Agent

Agentサーバ.テストランナーサーバとも言える.
何らかのイベントが発火して,Agentにタスクをenqueueする (AgentサーバのAPIにPOSTする) とAgentが適宜Clientのインスタンスを作って,そのClientのインスタンスにタスクを振って処理を行わせる,という事が出来る.
中にジョブキュー的な機構を持っているので,「次から次へとClientに処理を実行させた結果、ハングして処理を詰まらせる」みたいな悲劇を防ぐことが可能.
実際の例を挙げると,GitHubのPushのWebHooksにAgentサーバのAPIのアドレス (GitHub WebHooksだと/api/github_hook)を登録しておくと,リポジトリにpushされる度にイベントが発火してWebHooksでリポジトリの情報がAgentサーバのAPIにPOSTされる (つまりenqueueされる) ので,リポジトリにpushされる都度テストを走らせる,みたいなことが出来るようになる.

構成の今昔

かつての構成

かつてはAgentサーバを使わず,下図のようにServerとClientのみでまかなう構成が多かった.
Cron等で定期的に実行するという素朴な感じ.

f:id:moznion:20140503134026p:plain

ここ最近の構成

Agentサーバを使って,Agentが何らかのイベントを受け取ったらClientを立ち上げてジョブを走らせるようにする.
例えばリポジトリにpushされたらそのイベントをAgentに投げて,その都度テストを回してやるという感じ.

f:id:moznion:20140502180827p:plain

最近のインストールの方法

最近ではUkigumoシリーズはCPANにアップされているので,それを使うと楽.今回はこの方法を紹介する.
GitHubに上がっているもの *2 をcloneしてcarton等で実行するのでも良い.そういうことしたい人は説明しなくても出来ると思うので説明略!

なお今回の説明では,簡単化の為にUkigumo::ServerとUkigumo::Agentを同じマシン上で立ち上げるものとする.もちろんそれぞれが別のマシンに存在していて良い.

インストールは至って簡単,以下のコマンドを叩くだけで良い.

$ cpanm Ukigumo::Server Ukigumo::Client Ukigumo::Agent

最近の構成で実際に使ってみる

1. サーバ群を立ち上げる

なにはなくともServerとAgentを立ち上げる必要があるので立ち上げる.

Server
$ ukigumo-server --port=2828 --host=127.0.0.1 --max-workers=4 --config=/path/to/config.pl

--port--hostはそれぞれサーバを立ち上げるポートとホスト.
--max-workersは動かすワーカーの最大数.--configについては後述.
詳細はukigumo-server --helpを参照のこと.
とりあえずこうすると,localhost:2828にUkigumo::Serverが立ち上がる.

Agent
$ ukigumo-agent --port=2829 --server_url=http://127.0.0.1:2828 --work_dir=/tmp/

--portはAgentサーバを立ち上げるポート番号.
--server_urlはテスト結果をPostするサーバのアドレス (つまり上で立ち上げたUkigumo::Serverのアドレス) .
--work_dirはClientがリポジトリから実際にコードをcloneしてきてテストを走らせる為に割り当ててあげるワークスペースのディレクトリ.
こういう塩梅で実行するとUkigumo::Agentも立ち上がる.

2. テストしたいリポジトリのルートに.ukigumo.ymlを置く

.ukigumo.ymlというのは,.travis.ymlのようなもので,Ukigumo::Clientはプロジェクトルートに置かれているこのファイルを見てよしなにもろもろ設定をしてくれる.
以下が例:

before_install:
  - "cpanm -L $HOME/.ukigumo/ukigumo-server/extlib --installdeps --with-develop -n ."
install: "ln -s $HOME/.ukigumo/ukigumo-server/extlib ./extlib"
script: prove -lrv -Iextlib/lib/perl5 t

before_installと言うのはinstallの前段階で走るコマンドで,installはその名の通りインストール時に走るコマンド.scriptでは実際にテストを実行するコマンドを指定する.
あけすけに言うと,だいたい.travis.ymlでできることが出来る.現状セット出来るオプションは以下のとおり.実行される時系列順に示している.

  • before_install
  • install
  • before_script
  • script
  • after_script


<追記>
すっかり忘れていた.
.ukigumo.ymlではこれ以外にもnotificationsという項目が設定できて,これが便利.
テストの結果を任意のNotifierに渡して,通知を飛ばすということができる.
例えば,以下のように書いてやると,GitHubのStatuses API *3 を使ってテスト結果をGitHubのcommitに反映させる,みたいなことが可能.

notifications:
  guthub_statuses:
    - api_endpoint: https://api.github.com
      access_token: __ACCESS_TOKEN__

他にも色々な通知 (とは言え限定的なのだけれど……) に対応している.Ukigumo::Client::Notify::*というモジュールがそうした通知に対応している *4.<追記ここまで>


ここらへん,現状公式のドキュメントが無いので書く必要を感じている.

とにかく,こういうものを置くと,Ukigumo::Clientは上手く処理してくれる.

3. (例えば) GitHubのWebHooksに登録してやる

リポジトリにpushされたらその都度テストが走る,みたいな事をさせたいのでGitHubのWebHooksを設定する.

リポジトリの設定のWebHooksのところに,Ukigumo::AgentのサーバのAPIに対するアドレスを突っ込んでやる.
例: http://your.ukigumo-agent.net/api/github_hook

push以外のPayloadが来てもしょうがないので,Push Onlyで問題ない.

(今回はGitHubを例に採っているが,別のものでも構わない.その場合,登録するアドレスは`http://your.ukigumo-agent.net//api/v0/enqueue`のようになると思う *5.しかしここ最近のUkigumoはGitHubに寄せていて,「GitHubを使っていると更に便利!」という風になっているという旨を記す)

4. リポジトリにpushする

こうすると,GitHubリポジトリにpushされるや否やAgentサーバがそのHookを受け取り,Clientを立ち上げてテストを実行し,その結果をServerに投げるという一連の処理を実現できる.

Ukigumo関連の設定の勘所

Ukigumo::Server

サーバを立ち上げる時のオプションである--config=/path/to/config.plに食わせるコンフィグファイルの話.
コンフィグファイルは以下のような感じになる.

+{
    'DBI' => [
        'dbi:SQLite:dbname=deployment.db',
        '',
        '',
        +{
            sqlite_unicode => 1,
        }
    ],

    max_num_of_reports => 5000,
    max_num_of_reports_by_branch => 1000,
    enable_compression => 1,
};

DBIはもちろんDBの設定で,Ukigumo::ServerはMySQLSQLiteに対応している.たいていの場合はSQLiteで問題がない (と思う).
max_num_of_reportsmax_num_of_reports_by_branchはそれぞれ「保存するレポートの総個数の上限」と「ブランチごとの保存するレポートの個数の上限」を表している.これを超えると,古いものから順に自動的に消去される.
enable_compressionは,レポートの内容をgzip圧縮するかどうかの設定で,たいていの場合はenable (つまり1) にすると良いと思う.

Ukigumo::Client

特になし..ukigumo.ymlを使えば良いという位.

Ukigumo::Agent

特になし.強いて言えばukigumo-agentコマンドには--timeoutというコマンドがあって,テストの実行に指定した以上の時間がかかると強制的に処理を終了させる,ということが出来る (その場合はTIMEOUTという風になり,失敗扱いになる).

そんな感じです

かなり駆け足でしたが,こんなふうにすると使えますよ,という感じでした.
割と簡単に導入できることがお分かりいただけたと思います!!
「単純にテストを走らせて,その結果だけとれればいいワ」みたいな用途だとハマるのではないでしょうか.

ああ,次は公式のドキュメントだ……

突然ですが職質テックトークの12回目やります

唐突ですが職質テックトークの12回目を今日の18:00位からやります.
ゲストは以前にも1回お越しいただいた[@uzulla:twitter]さんです.

Future PHPやAlt PHPの話などをする予定です.
以下のURLで配信しますのでよろしくお願いします.

http://www.ustream.tv/channel/shock-sheets-tech