株式会社クリアコード > ククログ

ククログ

クリアコードはフリーソフトウェア開発で培った技術力を提供しています。特にMozilla製品(Mozilla FirefoxとMozilla Thunderbird)Rubyに関連した開発を得意としています。

Ohloh profile for kou
最新
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|
タグ:

Ruby on Rails Technical Night: Railsで作るActive Directoryと連携した社内システム

先日開催された〜Ruby on Rails Technical Night〜 Ruby on RailsセミナーでActive Directoryと連携したRailsアプリケーションの作り方について話しました。

Railsで作るActive Directoryと連携した社内システム

概要

ActiveLdapという社内システムをRailsを使って実現するときに便利なライブラリをデモを交えながら紹介しました。

社内向けのシステムをWebアプリケーションとして実現することは驚くことではなくなりました。Webアプリケーションなので、もちろんRailsを使っても実現することができます。

そのときに避けて通れないのが既存の社内情報との連携です。社内向けのシステムなので、社内情報と密接に連携し、より便利に使えるものであるべきです。多くの組織では社内情報をActive Directoryを用いて一元管理しています。

Railsアプリケーションとして社内システムを実現する場合も、必要に応じてActive Directory内にある情報を利用します。それを助けてくれるライブラリがActiveLdapです。

内容

Active Directoryをあまり知らない方も参加されるかたでもついてこられるように*1、前半でActive DirectoryとLDAPの基本的なところを説明しました。今回は「図での説明 + まとめ」というように、感覚的にわかってもらった後(なんとなくわかってもらった後)に要点を確認するという流れにしました。参加された方に感想を聞けなかったのですが、いかがだったでしょうか。

その後、実際にデモを行いながら、ActiveLdapがActiveRecordと同じようにActive Directory上の情報を操作できることを説明しました。ActiveRecordと同じように操作できるということは、いつもと同じようにコントローラ部分を書けるということです。つまり、今までのRailsアプリケーション開発の知識を活かしながらActive Directoryを操作するRailsアプリケーションを開発することができるということです。

今回は「コードがバンバン出るような内容を」ということで声をかけてもらったので、ここがメインの内容になっています。残念ながら公開されている資料ではデモを表現することができないので、デモで実行したコマンドなどを載せています。

最後に、実際にアプリケーションを開発するときにぶつかることが多い問題点についてふれました。Active Directoryとの接続の仕方、テストの仕方などです。

まとめ

先日開催されたRailsセミナーでの内容を紹介しました。

ActiveLdapに興味をもたれた方はるびまのActiveLdap を使ってみよう(前編)ActiveLdapのチュートリアルも読んでみてください。次号のるびまでは後編が公開される予定です。そちらも楽しみですね。

今回の内容はActive Directoryに特化した内容もありますが、OpenLDAPなどLDAPサーバ一般に通用する内容も多いです。LDAPサーバと連携するRailsアプリケーションを開発している方も参考にしてみてください。

Active Directory関連のことだけではなくて、コードを書くことについても伝えたかったのですが、欲張りすぎました。話の中ではうまく伝えられませんでしたが、プログラマーの方であれば、グラデーションで繋がる世界: 札幌Ruby会議02に行ってみて初心に帰ったもぜひ読んでみてください。

*1  多くの方はご存知のようでしたが。。。

つづき: 2009-12-21
Tags: Ruby | このエントリの del.icio.us history 5 users | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | Permalink
2009-12-15

札幌Ruby会議02: レシピに書かれていないこと

先週末開催された札幌Ruby会議02でライブコーディングしてきました。本州枠の1つで話す機会を作ってくれた実行委員長のしまださん、ありがとうございます。

レシピに書かれていないこと

内容

今回の発表はスライドだけでは伝わらないはずです。いつも、話すことすべてをスライドに盛りこんでいませんが、今回は特にスライドに盛りこまれていないことが多いです*1

仙台では考え方について話しましたが、札幌では技術的なことを話すつもりでした。札幌で何かを伝えることができるのなら、それは技術的なことであって欲しいし、Ruby 逆引きレシピをリリースしているRuby札幌がいる札幌でならそれができるはず、というのが理由です。

そのために、今回はライブコーディングをすることにしました。

自分ができる、一番、技術的なことを伝える方法はプログラムを書くことです。プログラミングに限ったことかもしれませんが、何かを伝えるためには、結果だけではなく、過程も一緒に伝えた方が、より伝わります。ペアプログラミングが成果をあげているという声を聞いたこともあるのではないでしょうか。

ライブコーディングは1対1ではなく、1対nの形になるので、それで本当に伝えることができるのかは不安でしたが、札幌でなら大丈夫だろうということで決行しました。

札幌Ruby会議02の模様はニコニコ動画にアップロードされています。

レシピに書かれていないこと - 須藤 功平

2009-12-22
再生: 76
コメント: 2
マイリスト: 1

レシピに書かれていないこと - 須藤 功平 (20:41)
札幌Ruby会議02 (2009/12/05)mylist: mylist/16614060time table: http://regional.rubykaigi.org/sapporo02

札幌でたいやきってな w

tDiaryの話をした柴田さんも録画してくれました。ありがとうございます。こちらはYouTubeにあります。

FAQ

Q: 練習したのですか?

A: 2, 3回練習しました。

あまり練習すると本番で間違わなくなるのであまり練習しないようにしました。実際、ライブコーディング中にいくつか間違いました。間違ったときにどうやって直していくかという過程も見てほしかったのでよかったです。

実際の開発はデバッグの連続です。間違わずに5分で完成させる動画通りにはいかないものです。

Q: 少し速いですね。

A: 使い慣れたエディタを使ったからです。

自分ではあまり意識して使っていませんが、意識してみると、以下の機能を使っているようです。以下の機能を使うことにより通常より速くコードを書くことができるかもしれません。

  • 動的略語展開: 入力途中の単語を補完します。補完候補は現在開いているすべてのバッファ内にある単語すべてです。デフォルトではM-/にバインドされていますが、補完といえばタブなのでC-c C-iにバインドしています。

    (define-key global-map "\C-c\C-i" 'dabbrev-expand)
  • "end"の挿入: ruby-modeではC-c C-eで"end"を挿入できます。
  • M-f/M-bでの移動: C-f/C-bより大きい単位で移動します。
  • C-hとC-dを同時に使う: C-hはバックスペースでC-dはdeleteですが、行内の箇所を削除する時はC-hとC-dを同時に使って削除しているらしいです。指摘されるまで自分でも気づいていませんでした。
Q: ソースコードは公開されていますか?

A: ラングバプロジェクトのSubversionリポジトリでAGPL v3+で公開しています。

リポジトリのURL
http://groonga.rubyforge.org/svn/examples/message-archiver/

当日利用したリビジョンはr874なので、以下のコマンドで同じソースコードを取得できます。

% svn co -r874 http://groonga.rubyforge.org/svn/examples/message-archiver/

札幌Ruby会議02全体の内容

仙台ともとちぎとも違う雰囲気でした。とてもとても居心地がよかったのが忘れられません。Rubyを使って気分よくプログラムを書いているときと少し似ている気がします。

最初から最後まで最前列で話を聞いていました。文脈は少し違うのですが、ちょうど興味のある話題や、前から考えていたことに関連することが多く、参考にできることがたくさんありました。

田中さんの、ユーザが余計なことを考えなくても簡単に使えるようにしたい、という話、sumimさんや谷口さんの、興味を持って学習するためにはという話、和田さんの、増加するテストに立ち向かう方法の話、高橋さんの、やめる勇気の話、角谷さんの、選んだらそれでハッピーではない、上を見てもキリがないし下は見てもしょうがないんだから自分ができることをやっていく、という話。文脈が違うので自分の中で変換しながら自分に合わせて聞いていたので、発表された方が一番言いたかったこととずれているかもしれません。

ツールでは、しだらさんの紹介していたJekyllも使ってみたくなりました。とちぎではそうでもなかったのですが、今回は使ってみたくなりました。とちぎでは駆け足だったからかもしれません。

Reject Talks

技術的なことは本編とは別のReject Talksが楽しかったです。Reject Talksでは、単に発表者が話すのではなく、観客が随時コメントをしていくという形式に(結果的に)なっていました。その中で、このコードがこう悪い、こうした方がよい、という話をできたことがとても楽しかったです。

話す側と聞く側ではなく、両者が話しあっていることがとても印象的でした。

まとめ

札幌Ruby会議02での発表内容について補足しました。また、1参加者から見た札幌Ruby会議02も簡単にレポートしました。

札幌Ruby会議02がすばらしかったのはスタッフのみなさん、発表者のみなさん、参加者のみなさんなど関係者のみなさんのおかげでしょう。でも、もう少し考えてみると、実行委員長のしまださんがいることが一番大きいのではないかと感じています。みなさんは、司会をしていたしまださんが想いがこもった発表者紹介をしていたのに気づいていましたか。想いのこもった感想を述べていたことに気づいていましたか。しまださんの発表枠はありませんでしたが、しまださんの想いを感じることができたすばらしい札幌Ruby会議02でした。

そんな札幌Ruby会議02に発表者・スポンサーとして参加できてとても光栄です。

今回はActiveLdapには触れられませんでしたが、12/14のRailsセミナーではActiveLdapに触れるので、興味のある方は参加してみてください。

あわせて読みたい

*1  公開用に多少加筆してあります。

Tags: Ruby | このエントリの del.icio.us history 8 users | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | Permalink
2009-12-07

告知: 2009/12/14のRailsセミナーはRailsとActive Directoryについて

来月中旬の12/14(月)に〜Ruby on Rails Technical Night〜Ruby on RailsセミナーでActive Directoryと連携したRailsアプリケーションの開発方法について話します。セミナーの概要は以下の通りです。都合があう方はぜひお越しください。

タイトル
Railsで作るActive Directoryと連携した社内システム
日時
2009年12月14日(月)19時30分〜21時
場所
株式会社オプト会議室
参加方法
atndで申し込み

さも1人で話すように書いていますが、そんなことはなく、株式会社ローハイド.の吉見さんと株式会社万葉の河野さんの3人で、3部構成です。運用のお話や開発時のお話をされるようなので、そちらに興味のある方も参加してみてください。

内容

内容はタイトルの通りで、RailsアプリケーションからActive Directoryの情報を利用する方法、注意しなければいけない点などについて話します。社内システムなど、すでにActive Directoryを導入している環境で動作するRailsアプリケーションを開発する場合、独自にアカウントを管理するのではなく、Active Directory上のアカウント情報を利用する方が、利用者の使い勝手もよくなりますし、運用者の負担も減ります。そういった場合にどのようにActive Directory上のアカウント情報を利用するのがよいか、ということをコード例も示しながら説明します。

Active Directoryとの接続には、ActiveLdap(参考: Rubyist Magazine - ActiveLdap を使ってみよう(前編))を使います。つまり、Active DirectoryをLDAPサーバとして使った場合のRailsアプリケーションの作り方とも言えます。よって、この話の内容はOpenLDAPなどActive Directory以外のLDAPサーバにも応用できます。LDAPサーバと連携したRailsアプリケーションに興味がある方にも楽しんでもらえるのではないでしょうか。

まとめ

来月のRailsセミナーでRailsとActive Directoryについて話すことになったので、それの告知をしました。参加費は無料なので興味のある方はお気軽に参加してみてください。現時点で定員の半分ほど埋まっているようなので、気になる方はお早めにどうぞ。

タイトル
Railsで作るActive Directoryと連携した社内システム
日時
2009年12月14日(月)19時30分〜21時
場所
株式会社オプト会議室
参加方法
atndで申し込み
つづき: 2009-12-07
Tags: Ruby | このエントリの del.icio.us history | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | Permalink
2009-11-30

FSIJ月例会の資料公開

先日開催されたFSIJ月例会でテストとテスティングフレームワークについて話しました。声をかけてくれた上野さん、ありがとうございます。

テスティングフレームワークに必要なもの - 書きやすさとデバッグのしやすさ

内容

これまでの経験から感じていることをまとめたものになっています。どうにかしたいけど、まだよいアイディアがなくて悩んでいる、といったものも含まれています。例えば、違いをわかりやすく示すことはいろいろ工夫できるけど、なかったことをわかりやすく示すことは難しいといったことです。

最後まで話しつづけるのではなく、随時ディスカッションをはさむ形で進みました。参加者のみなさんからもたくさんの意見がでてとても有意義でした。

GUIやDTP、ネットワーク・ハードウェア周辺などテストを自動化することが難しい領域のテストはみなさん悩んでいるところでした。今回の会で決定的な解決策がでたわけではないのですが、悩んでいる部分、他の人が持っているよいアイディアを共有できたことは成果と言えます。明日から劇的に変わるというものではありませんが、確実によくなっていけるというものです。

話の中に何度かでていますが、「完璧を求めない」ことが重要です。完璧を求めてテストがストレスになってしまうことよりも、自分たちのペースでよい状態を継続し、よいソフトウェアの開発を続けられるようにすることの方が重要です。このあたりの力の入れ具合、テストが目的ではなくよいソフトウェアを開発することが目的であることを忘れないことで「壁」を越えることができるでしょう。

あわせて読みたい

つづき: 2009-12-21
2009-11-27

告知: 2009/11/26のFSIJ月例会はテスティングフレームワークについて

今月末、FSIJ月例会でテスティングフレームワークについて話します。月例会の概要は以下の通りです。都合があう方はぜひお越しください。

テーマ
テスティングフレームワークに必要なもの - 書きやすさとデバッグのしやすさ
日時
11月26日(木)18:30〜20:30
場所
秋葉原ダイビル 11階 大会議室1
参加方法
メールで申し込み

(tokyo-emacs #x02)話したときにお会いした上野さんに声をかけてもらいました。声をかけていただきありがとうございます。

進め方

事前に打ち合わせなどはないようなので、自由な形式でやる予定です。

2時間ありますが、ずっと話し続けるわけではありません*1。こちらから考えていることや技術的なことなどを紹介し、それを話題としてディスカッションを挟みながら進めていきます。テスティングフレームワークやテストについて思っていることがある方はぜひ参加してその考えを聞かせてください。

内容

以下の3部構成にする予定です。ここでいう「テスト」とは実装をする開発者自身が書く「自動化されたテスト」のことです。

  1. テストが当たり前の人になるには
  2. テスティングフレームワークに必要なもの
  3. 各種テスティングフレームワークの実例

今では、「テストは書いた方がよい」というのはみんなわかっていることです。しかし、「テストのないコードは怖くてさわれない」という感覚はそうではありません。それがテストが当たり前の人とそうでない人の差です*2。では、どうすればテストが当たり前になるのか。まずは、それについて話し、ディスカッションします。

次に、テストが当たり前の人が価値のあるテストを継続して書くために、テスティングフレームワークが提供するべきものはなにかを考えます。

最後に、現在のテスティングフレームワークはどうなっているかを扱います。先日、C++用xUnitでのテストの書き方を紹介しましたが、他の言語用のテスティングフレームワークでの書き方や、もしかしたら、どのような実装で実現しているかまで紹介できるかもしれません。

随時、参加者からの意見を受け入れますが、それぞれの話題毎および最後にもまとまったディスカッションの時間をとる予定です。

まとめ

今月のFSIJ月例会でテスティングフレームワークについて話すことになったので、それの告知をしました。参加費は無料なので興味のある方はお気軽に参加してみてください。

日時
11月26日(木)18:30〜20:30
場所
秋葉原ダイビル 11階 大会議室1
参加方法
メールで申し込み

*1  2時間もただ聞いていると飽きますよね。

*2  テストがあることをポジティブに捉えるかテストがないことをネガティブに捉えるかの差と言ってもよいかもしれません。

つづき: 2009-11-27
Tags: テスト | このエントリの del.icio.us history | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | Permalink
2009-11-17

Firefox Developers Conference 2009にて発表を行いました

こんにちは。下田(Piro)です。

昨日2009年11月8日に開催されたFirefox Developers Conference 2009にて、トークセッション「Aza Raskin に一問一答!」にパネリストとして参加しました。また、懇親会でのライトニングトーク第2部にて発表を行いました。参加された皆様、お疲れ様でした。また、セッションにお越しいただいた皆様、誠にありがとうございます。

発表資料および映像は以下よりご覧いただけます。

また、ライトニングトーク中で紹介しているアドオン「システムモニター」の最新版であるVer.0.3は、以下からダウンロードできます。

以下、イベントの感想と、イベント中に語ることができなかった点について書いていきたいと思います。

Aza Raskinさん、天野(amachang)さんとのトークセッション「Aza Raskin に一問一答!」について

このセッションでは、次期Firefoxでの標準搭載も視野に入れて開発が進められているJetpackや、新しいUIの可能性を模索するUbiquityの開発者として知られるAza Raskinさんに対して、現在Firefoxアドオンを開発している・開発した経験がある人の立場からの質問を私が、主にWebアプリケーションの開発を行っておりこれからJetpack用のスクリプト(Jetpack feature)の開発に携わる可能性がある人の立場からの質問を天野さんが行う形で、Firefoxの今後とWebアプリケーションの将来像について様々な話を聞かせていただきました。

Firefoxはアドオン(拡張機能)によるカスタマイズ性の高さが最大の特長ですが、自由度の高さゆえに、アドオンの開発には様々な準備と知識が必要になるという欠点もあります。Jetpackは、この欠点をカバーしてFirefoxの機能を拡張するスクリプトをより容易に配布・利用できるようにする物として開発されている、新しいアドオンのメカニズムです。「拡張機能」がテーマである今回のFirefox Developers Conference 2009においては、避けて通れない話題ですね。

今回のトークセッションにあたって事前にいくつかのJetpack featureを開発したのですが、その際に最も強く感じた事は、APIについての不満でした。現状ではAPIに関するドキュメントが圧倒的に不足しており、また、APIがドラスティックに変更されている最中であるため、安定したJetpack featureの開発および利用はまだまだ難しいのが実情です。セッション中でも質問を行いましたが、これについては、むしろ今が開発に口出ししてAPIをより良い物にしていくチャンスと捉えてほしいという回答をいただきました。とはいえ、APIドキュメントの重要性は彼らの間でも認識されており、そのための体制を整えているところであるとの話も聞けました。

Azaさんは、セカンドシステム症候群に陥ってはならないという事を再三述べていました。これまでのアドオンの仕組みに多くの不満があるからといって、その代替として新たに登場してきたJetpackに過剰な要求をして完璧な物を最初から求めると、議論が紛糾するばかりで実装が停滞し、Jetpackという仕組み自体がいつまで経っても完成しなくなってしまいます。ほどほどの所でまずは皆に使ってもらい、その上で実際の利用状況を見ながら少しずつ不足箇所を補っていく、という姿勢が重要ですね。

Jetpack featureで利用可能なライブラリとしては、現在はjQueryが標準添付されていますが、ゆくゆくはPerlにおけるCPANのように、様々なライブラリを簡単に利用できるようにする計画もあるそうです。また、現状のJetpackも将来のバージョンも、jQueryを使うことは必須というわけではなく、ライブラリの機能を使わずにJetpack featureを開発しても問題は無いとのことです。なお、私も天野さんも既存のライブラリをほとんど利用していない理由について、Azaさんより逆に質問をされるという場面もありました。(天野さんの場合は主に既存のライブラリのオーバーヘッドが気になるためで、私の場合はFirefox用アドオンの開発のみを行う都合上、ライブラリの最大のメリットである「ブラウザ間の差異の吸収」の恩恵を受けにくいというのが理由です。)

天野さんがWebアプリの開発者の観点から投げかけられた質問として、WebのUIを良くするにはどのようにすればよいか?という物もありました。これについてAzaさんは、作り手側が絶対に手出し・口出ししないようにして、目の前でエンドユーザに実際に使ってもらいその様子を観察すれば、WebのUIはもっと良くなるだろうとおっしゃっていました。ユーザビリティ・テストはUI開発の基本と言えますが、DTPなど視覚的デザインの文脈で捉えられることがまだまだ多いWebの世界においても、きちんと「インターフェース」としての作り方を意識する必要があるということですね。

国ごとの文化の違いがUIに与える影響について、Azaさんは欧米と中国のWebブラウズの仕方の違いを例に挙げてお答え下さいました。曰く、欧米や日本では1つの画面にかじりついて操作するのが一般的であるのに対し、中国では画面から少し離れて、画面内に非常に多くの機能・要素を詰め込んでそれらを平行して利用する場合が多いそうです(そのため、Googleのかつてのシンプルなトップページは不評だったそうです)。そういった文化ごとの利用形態の違いに合わせるために、末端にいる人達にUIのデザインを行う権限を持たせる必要があるのではないか、という意見も示されていました。

他のブラウザの隆盛、特にWebKitが多数のプロジェクトで採用されている事についての質問もありました。これについては、かつては止まったWebブラウザの進化をFirefoxが刺激して活性化させたように、今はWebKitがWebを活性化させているとして、その結果Webがより便利になるのであれば最終的な勝者は我々エンドユーザだ、との考えをお答えいただきました。

というように、Jetpackの話題のみにとどまらず、WebのUI一般の話にまで言及したトークセッションとなりましたが、皆様お楽しみいただけましたら幸いです。

ライトニングトーク「Webアプリとハードウェアを繋げたい!」について

ライトニングトークへの参加希望者が多かったため、過去に発表経験のある数人については懇親会の場で発表を行うこととなりました。今回はシステムモニターで行った、アドオンによるWebアプリケーション向けAPIの提供方法について発表しました。

「scriptable hardware」というキーワードで、基調講演でChris BlizzardさんがFirefoxの加速度センサー対応やカメラ用APIの実験について語っていらっしゃいました。クリアコードでも独自に同様の研究を行っており、システムモニター以外にも、指紋認証アドオン「Fingerprint-Auth」の実装を進めております。また、その開発の過程で浮上してきたアイデアとして、USB機器全般をJavaScriptから制御できるようにしてみてはどうかという話もあり、それについても「進行中のプロジェクト」としてお話しさせていただきました。Fingerprint-Authについては実際にお試しいただける形の物を近日中に公開する予定ですので、どうぞお楽しみに。

セッション「js-ctypes ~ネイティブコードを呼び出す新しいカタチ」では、JavaScriptからプラットフォームネイティブなバイナリの機能を呼び出すより平易な方法を提供するFirefox 3.6からの新機能js-ctypesを紹介されていましたが、システムモニターやFingerprint-AuthのようにJavaScriptのグローバルな名前空間にAPIを提供しようと思うと、現状ではXPCOMコンポーネントとして実装を行う必要があります。また、XPCOMコンポーネントはC++での開発となるため、各プラットフォーム向けのビルドやnsISecurityCheckedComponentインターフェースの実装などの作業が必要となります。残念ですね。

なお、発表ではすべての実装がJavaScriptによって行われているXUL/MigemoでのAPIの提供方法を例として紹介しました。JavaScript製XPCOMコンポーネントの場合は、比較的簡単に機能をWebアプリケーション向けのAPIとして公開することができます。作成手順についてはFirefox 3 Hacks等の解説と発表資料中の説明を併せてご覧下さい。

セッション「はてなブックマーク Firefox 拡張の裏側」について

株式会社はてなさんはてなブックマークFirefox拡張についての発表では、開発に使用したツールとして弊社開発のテスティングフレームワークUxUをご紹介いただけました。主にデータベース関連の機能等のユニットテストに利用されているとのことで、非同期処理が絡むテストの開発に役立ったとのお言葉をいただけたのは嬉しかったです。また、私が個人的に開発している各種のアドオンについても、ソースコードをアドオン開発の参考にしていただけたそうで、ありがたい限りです

なお、UxUでは最近のバージョンアップでより使いやすいユーザ操作エミュレーション用のAPIが追加されました。基本部分のユニットテスト以外にも、GUIが関係する機能テストやインテグレーションテストにもUxUを活用していただけたらと思っております。

イベント全体について

UbiquityにもJetpackにも共通している1つの狙いとして、「より多くの人が開発を行えるようにする」という目標があるそうです。より簡単に開発できるようになり、より多くの人が開発できるようになれば、より多くのイノベーションが生まれる、というのは前回のFirefox Developers Conferenceの際にもAzaさんが語られていた話ですが、AutoPagerize、ひいてはその原点であるGoogleAutoPagerが、どちらもGreasemonkeyスクリプトという世界から登場したことは、まさにその象徴的な出来事だと思います。

これはFirefoxのアドオン開発のみに限られた話ではなく、Chris Blizzardさんが語ったキーワード「scriptable hardware」もそれに連なる話であると言えるでしょう。折しも、先日GoogleがClosure Toolsという、JavaScriptでのアプリケーション開発のためのツール集を公開しました。APIやツールなど様々な側面から、Webクライアント上で動作する高機能なアプリケーションを開発しやすくするための土壌が整いつつあります。Webアプリケーションがエンドユーザからただの「アプリケーション」として扱われるようになる、Web OSという未来が本当にすぐそこまで来ているのだということを、改めて意識させられたイベントでした。

感想・イベントレポートリンク集

発表資料リンク集

つづき: 2009-12-21
2009-11-09

C++用xUnitでのテストの書き方

注: 長いです。

スクリプト言語でのxUnit実装を使ったことがある方なら、テストを定義するだけでテストが実行されることが当たり前ではないでしょうか。c2.comのWikiによると、これはTest Collectorというそうです。定義したテストを自動的に集めてくる機能のことです。

一般的にTest Collectorの機能は言語が提供するリフレクションやメタプログラミングを使って実現されます。

例えば、Rubyのtest-unit 2.xでは、リフレクションを使う方法とメタプログラミングを使う方法の両方をサポートしています。リフレクションを使う方法ではObjectSpace.each_object(Class)ですべてのクラスを取得し、その中のTest::Unit::TestCaseのサブクラスを集めます。メタプログラミングを使う方法ではTest::Unit::TestCase.inheritedを定義して、サブクラスが定義された時のフックでそのサブクラスを集めます。

Pythonでもモジュールオブジェクトからモジュール内のオブジェクトにアクセスすることができるので、同様の方法でテストを集めることができます。JavaScriptでも、オブジェクトに定義されているプロパティを列挙することができるので、同様の方法でテストを集めることができます。(JavaScriptとXULで実装されているUxUも同様のことをしています。)

一方、CやC++ではリフレクションやクラスをファーストクラスオブジェクトとして扱えないため、自動でテストを集めるためには一工夫必要になります。一昔前のxUnitでは、定義したテストを手動で登録していました。

それでは、C/C++での一工夫の方法として以下の4つをxUnit実装と一緒に紹介します。C++用のxUnitを選択する時の参考にしてください。

  • マクロでごまかす
  • プリコンパイルする
  • マクロでテスト定義と同時に登録する
  • 共有ライブラリから探す

マクロでごまかす

最初はCppUnitのケースです。CppUnitではテストを定義するだけでは、自動でテストを集めてはくれません。しかし、便利マクロを用意して、手動でテストを登録する面倒さを減らしています。

以下はCppUnit Cookbookにあるソースコードをベースにしています。public内のテスト定義とは別にCPPUNIT_TEST_SUITEからCPPUNIT_TEST_SUITE_ENDの間でテストを登録しています。

// complex-number-test.cpp
#include <cppunit/extensions/HelperMacros.h>

class ComplexNumberTest : public CppUnit::TestFixture  {
  CPPUNIT_TEST_SUITE( ComplexNumberTest );
  CPPUNIT_TEST( testEquality );
  CPPUNIT_TEST( testAddition );
  CPPUNIT_TEST_SUITE_END();

private:
  Complex *m_10_1, *m_1_1, *m_11_2;

public:
  void setUp()
  {
     m_10_1 = new Complex( 10, 1 );
     m_1_1 = new Complex( 1, 1 );
     m_11_2 = new Complex( 11, 2 );
  }

  void tearDown()
  {
    delete m_10_1;
    delete m_1_1;
    delete m_11_2;
  }

  void testEquality()
  {
    CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
    CPPUNIT_ASSERT( !(*m_10_1 == *m_11_2) );
  }

  void testAddition()
  {
    CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
  }
};
CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumberTest );

実行する場合は以下のようなmain関数を定義する必要があります。

// main.cpp
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>

int main( int argc, char **argv)
{
  CppUnit::TextUi::TestRunner runner;
  CppUnit::TestFactoryRegistry &registry =
    CppUnit::TestFactoryRegistry::getRegistry();
  runner.addTest( registry.makeTest() );
  bool wasSuccessful = runner.run( "", false );
  return wasSuccessful;
}

上記の二つのファイル(とComplexNumberの実装)を使ってビルドします。

% g++ -o complex-number-test complex-number-test.cpp main.cpp \
    -lcomplex-number -lcppunit

テストを実行するにはビルドしたバイナリを実行します。

% ./complex-number-test
..


OK (2 tests)

マクロを使ってテスト登録を簡単にしている(自動化まではしていない)例としてCppUnitを紹介しました。

CPPUNIT_TEST_SUITEなどの便利マクロを使わない場合は定義したテスト名(上の例ではtestEqualitytestAddition)以外のことも気にしなければいけなくなります。便利マクロを使うと、テスト名だけわかっていればよいので、それに比べるとだいぶテスト作成が楽になっています。

しかし、テストを定義だけして登録し忘れたということを回避することができません。また、テストケース定義とは別にmain関数も定義する必要があり、テスト以外のことにも気を配る必要があることにも注意が必要です。

プリコンパイルする

C++で書かれたテストコードを直接C++コンパイラでコンパイルするのではなく、テストコードに必要なコードを追加したC++ソースコードを生成して、それをコンパイルする方法です。C++コンパイラでのビルドする前に一度変換処理を行えるので、テストコードへの記述が減ることが利点ですが、変換処理を行うのがやや面倒です。自動化されば気にならなくなるでしょう。

CxxTest

まずはCxxTestのケースです。CxxTestではソースコードを直接ビルドするのではなく、C++のソースコードからテスト登録処理などを付加したC++ソースコードを生成し、それをビルドします。

以下はCxxTest User Guideにあるソースコードをベースにしています。テストの定義だけでテスト登録処理は含まれていません。

// MyTestSuite.h
#include <cxxtest/TestSuite.h>

class MyTestSuite : public CxxTest::TestSuite
{
public:
   void testAddition( void )
   {
      TS_ASSERT( 1 + 1 > 1 );
      TS_ASSERT_EQUALS( 1 + 1, 2 );
   }
};

以下のようにビルドします。

% cxxtestgen --error-printer -o cxxunit-tests.cpp MyTestSuite.h
% g++ -o cxxunit-tests cxxunit-tests.cpp

cxxtestgenMyTestSuite.hにあるテスト定義にテスト登録処理などを加えてcxxunit-tests.cppを生成します。余談ですが、cxxtestgenはPythonスクリプトです。また、CxxUnitはライブラリを提供せず、ヘッダーファイルのみを提供します。

バイナリを実行するとテストが走ります。

% ./cxxunit-tests
Running 1 test.OK!

テスト登録が完全に自動化されているのでCppUnitよりも新規テストの追加が容易です。テストの登録しわすれもありません。ただ、cxxtestgenとC++コンパイラで2回コンパイルする必要があることが少し手間だと言えます。

QTestLib

続いてQtが提供するQTestLibのケースです。QTestではQtが提供するスロットの仕組みを使って、定義されているテストを集めます。スロットがどのように定義されているかをプログラム中から扱うために、QtはC++のソースコードをプリコンパイルしますが、QTestでも同様にプリコンパイルする必要があります*1

以下はQTestLibのチュートリアルにあるソースコードををベースにしています。

// test-qstring.cpp
#include <QtTest/QTest>

class TestQString: public QObject
{
    Q_OBJECT
private slots:
    void toUpper()
    {
        QString str = "Hello";
        QCOMPARE(str.toUpper(), QString("HELLO"));
    }
};

QTEST_MAIN(TestQString)
#include "test-qstring.moc"

QTestLibでもmain関数は定義しなければいけませんが、QTEST_MAINという便利マクロが用意されています。

以下のようにビルドします。

% mkdir test-qstring
% mv test-qstring.cpp test-qstring
% cd test-qstring
% qmake -project "QT += testlib"
% qmake
% make

バイナリを実行するとテストが走ります。

% ./test-qstring
********* Start testing of TestQString *********
Config: Using QTest library 4.5.3, Qt 4.5.3
PASS   : TestQString::initTestCase()
PASS   : TestQString::toUpper()
PASS   : TestQString::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of TestQString *********

このようにQTestLibではテスト登録のために必要なコードはQTEST_MAINでクラスを指定している部分だけです。個々のテストは指定する必要がありません。

メタオブジェクト情報を生成すること、また、それを読み込んでいる#include "test-qstring.moc"のところはQTestLib独自のことではなく、Qt全般のことなので、Qtを利用している場合は追加で必要な作業とはいえないでしょう。つまり、QTestLibのテストを集める方法は完全には自動化されていませんが、Qt開発者にはそれほど負担もかからず自然に書けるようになっている使いやすいAPIといえます。一方、Qt開発者でない場合は、面倒に見えるでしょう。

マクロでテスト定義と同時に登録する

CppUnitでもマクロでテストを登録していますが、それをもう一歩進めたのがこの方法です。CppUnitでは、テスト定義は通常の関数定義でしたが、この方法ではそこでマクロを使い、テスト定義と同時にテストを登録します。

Google Test

まずは、Google Testです。Google Testではテスト定義時にTESTマクロを使います。以下はGoogleTestSamplesにあるソースコードをベースにしています。

// test-factorial.cpp
#include <gtest/gtest.h>

int Factorial(int n) {
  int result = 1;
  for (int i = 1; i <= n; i++) {
    result *= i;
  }

  return result;
}

TEST(FactorialTest, Negative) {
  EXPECT_EQ(1, Factorial(-5));
  EXPECT_EQ(1, Factorial(-1));
  EXPECT_TRUE(Factorial(-10) > 0);
}

int main(int argc, char **argv) {
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

プリコンパイル方式でもテストの登録忘れはありませんが、この方法でも登録を忘れることがありません。テスト定義の方法が通常の関数定義とは異なる書式になることに慣れることができるのであれば、この方式で負担が少なくテストを書けるようになるでしょう。

以下のようにビルドします。

% g++ -o test-factorial test-factorial.cpp -lgtest

バイナリを実行するとテストが走ります。

% ./test-factorial
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from FactorialTest
[ RUN      ] FactorialTest.1
[       OK ] FactorialTest.1 (0 ms)
[----------] 1 test from FactorialTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[  PASSED  ] 1 test.

上記の例では触れていませんが、フィクスチャ(setup/teardown)を使う場合は、テストクラス名を揃える必要があるなど、同じグループのテストを作る場合は重複する部分がでてしまいます。例えば、QTestLibのようにクラス内にテストを定義する方法では以下のようになります。

class MyTest
{
   void setup() {...}
   void teardown() {...}

   void test1() {...}
   void test2() {...}
   void test3() {...}
}

一方、Google Testの場合は、スコープが使えず、以下のようにクラス名を複数回書く必要があります。

class FooTest : public testing::Test {
 protected:
  virtual void SetUp() { b_.AddElement(3); }

  Foo a_;
  Foo b_;
};

TEST_F(FooTest, InitializesCorrectly) {
  EXPECT_TRUE(a_.StatusIsOK());
}

TEST_F(FooTest, ReturnsElementCountCorrectly) {
  EXPECT_EQ(0, a_.size());
  EXPECT_EQ(1, b_.size());
}

マクロを使っている場合は、間違ったテストクラス名を指定するなどコンパイルエラーになったときに意味の分からないエラーメッセージを目にすることがあるというのも注意しなければいけないポイントです。エラーメッセージを使えないと問題を発見することが難しくなります。

Boost Test Library

次に、Boost Test Libraryです。やり方はGoogle Testとだいたい同じで、Boost Test LibraryではBOOST_AUTO_TEST_CASEを使います。

// test-add.cpp
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE AddTest
#include <boost/test/unit_test.hpp>

int add( int i, int j ) { return i+j; }

BOOST_AUTO_TEST_CASE( add_test )
{
    BOOST_CHECK_EQUAL( add( 2,3 ), 5);
}

最初にBOOST_TEST_DYN_LINKBOOST_TEST_MODULEを定義しておくと、Boost Test Libraryではmain関数を定義する必要はありません。

以下のようにビルドします。

% g++ -o test-add test-add.cpp -lboost_unit_test_framework

バイナリを実行するとテストが走ります。

% ./test-add
Running 1 test case...

*** No errors detected

Google Testと同じくマクロが気にならない場合やBoostに慣れている場合はテストが書きやすいでしょう。

共有ライブラリから探す

マクロを利用する方法は言語の構文を工夫してテストを集めています。プリコンパイル方式では言語の構文はそのままで、コンパイル前に付加情報を加えることでテストを集めています。

一方、最後の共有ライブラリから探す方法ではコンパイル後の共有ライブラリから情報を取得してテストを集めます。この方式では、テストを共有ライブラリとして作成し、テスト実行用のコマンドからその共有ライブラリを読み込み、テストを実行します。こうすることにより、テスト側にテスト登録処理を埋め込む必要がなくなります*2。共有ライブラリの中からテストを見つける処理はテスト実行コマンドが頑張るからです。

WinUnit

最初はWinUnitです。やり方は共有ライブラリからテストを集める方式なのですが、書き方はマクロを使う方式です。テストを定義するときはBEGIN_TESTEND_TESTで囲みます。

#include "WinUnit.h"

BEGIN_TEST(AddTest)
{
   WIN_ASSERT_TRUE(3 == add(1, 2));
}
END_TEST

すでにGoogle TestやBoost Test Libraryで見たように、この使い方であれば、共有ライブラリにする必要はありません。マクロの中で一工夫することでテストの自動登録を実現できるからです。

WinUnitの利点はVisual C++で使いやすいことでしょう。マクロを使ったAPIが気にならないVisual C++開発者には有力な選択肢です。

Cutter

最後はCutterです。CutterはC言語用の単体テストフレームワークとして開発されていましたが、先日リリースされた1.1.0で大きくC++対応を強化しています。

CutterではWinUnitとは違いマクロを利用しません。通常通り関数を定義するとテストとして認識されます。ただし、すべての関数がテストとして認識されるのではなく、test_からはじまる名前の関数だけをテストとして認識します。

#include <cppcutter.h>

namespace calc
{
   void test_add()
   {
       cppcut_assert_equal(5, add(2, 3));
   }
}

マクロを利用してテストを自動登録する方式では、フィクスチャ定義時に名前を揃える必要がありましたが、Cutterでは以下のようにnamespace内にsetup()/teardown()を定義するだけです。namespaceでグループ化されたテスト全体でフィクスチャを共有します。

#include <cppcutter.h>

namespace calc
{
   void setup() {...}
   void teardown() {...}

   void test_add() // calc::setup()/calc::teardown()が呼び出される
   {
       cppcut_assert_equal(5, add(2, 3));
   }

   void test_sub() // calc::setup()/calc::teardown()が呼び出される
   {
       cppcut_assert_equal(5, add(8, 3));
   }
}

この方式では通常のC++プログラムと同様にテストを書くことができるため、新しくテストを書くことの敷居が低くなります。しかし、tes_などタイプミスをしてしまった場合に、どうしてテストが実行されないのかに気づきにくいという問題点があります。

テストも通常のプログラムと同様に開発したい場合はマクロを使わないこの方式がオススメです。

まとめ

C++用の各種xUnitでのテストの書き方を、方式毎に分類して紹介しました。どんなバックグラウンドを持っているかにより、選びやすいxUnitは変わるでしょう。Visual C++開発者であればWinUnitを選ぶことが多いでしょうし、Qt開発者であればQtTestLibを選ぶことが多いでしょう。しかし、バックグラウンドから選ぶだけではなく、テストの書きやすさも判断材料に加えてみてはいかがでしょうか。

継続して開発すればそれに伴ってテストも増えていきます。しかし、テストは面倒くさがって飛ばしてしまいがちです。新しくテストを書く敷居が下がれば、テストを面倒くさがることが少なくなり、安心して開発を続けていくための土台を固めることができます。新しくテストを書く敷居を下げることは開発を継続するのであれば割に合うということです。

今回は「新しいテストの書きやすさ」を軸に様々なxUnitのやり方を紹介しました。C++用xUnitを選択する時の参考にしてみてください。

念のため書きますが、オススメはCutterです。

*1  プリコンパイルにはmoc(メタオブジェクトコンパイラ)を使います。

*2  「これがテストだよ」という目印は埋め込む必要があります。

2009-11-07

UxUを用いたデータ駆動テストの記述

2009年10月30日付で、テスティングフレームワークUxUのバージョン0.7.5をリリースしました。

UxUはこれまで「Firefoxアドオン開発用テスティングフレームワーク」と銘打っていましたが、Thunderbird用アドオンの開発にも利用されていることと、バージョン0.7.0以降からXULRunnerベースのアプリケーション一般に対してインストール可能なようになったことから、現在のプロジェクトページ上では「Firefox/Thunderbird用アドオン・XULRunnerアプリケーション開発用テスティングフレームワーク」と表記しています。

バージョン0.7.0以降で、UxUはデータ駆動テストの記述に対応しました。今回はUxUでのデータ駆動テストの記述方法の解説を通じて、データ駆動テストの利便性についてご紹介したいと思います。

このエントリ内の目次:

  1. データ駆動テストとは?
    1. べた書きしたテスト
    2. 関数を使ったテスト
    3. ループを使ったテスト
    4. データ駆動テスト
  2. UxUでのデータ駆動テストの書き方
  3. データ駆動テスト用の便利な機能

データ駆動テストとは?

データ駆動テストとは、簡単に言えば、「テストのロジックとデータを分離した自動テスト」の事です。データ駆動テストの利点を理解していただくために、データ駆動テストではないテストの例もいくつか挙げながら、それぞれの利点と欠点を見ていきましょう。

べた書きしたテスト

以下は、XUL/Migemoという「ローマ字入力で普通の日本語の検索を行う」アドオンの中の、半角英数字によるローマ字入力をひらがなに変換するモジュールのテストの一部です。

function test_roman2kana() {
  assert.equals('あいうえお',      transform.roman2kana('aiueo'));
  assert.equals('aiueo',      transform.roman2kana('aiueo'));
  assert.equals('がぎぐげご',      transform.roman2kana('gagigugego'));
  assert.equals('にほんご',        transform.roman2kana('nihongo'));
  assert.equals('ぽーと',          transform.roman2kana('po-to'));
  assert.equals('きゃっきゃ',      transform.roman2kana('kyakkya'));
  assert.equals('うっうー',        transform.roman2kana('uwwu-'));
  assert.equals('\\(\\)\\[\\]\\|', transform.roman2kana('\\(\\)\\[\\]\\|'));
  assert.equals('\\(\\[',          transform.roman2kana('\\(\\['));
  assert.equals('\\)\\]',          transform.roman2kana('\\)\\]'));
  assert.equals('\\|',             transform.roman2kana('\\|'));
}

このモジュールのroman2kana()メソッドは、半角英数字のローマ字入力をひらがなに変換し、それ以外の入力はそのまま返すという仕様になっています。この仕様通りに動作するかどうかを検証するため、ここでは11種類の引数を渡し、戻り値をassert.equals()で検証しています。検証しないといけないパターンが増えた時には、行をコピーして引数と期待値の部分を書き換えることになります。

パッと見て分かるかと思いますが、このテスト用コードには以下のような問題があります。

  • 同じコードの繰り返しが多く冗長である
  • テスト対象のメソッドを呼び出す記述が直接書かれているため、メソッド名や引数の取り方が変わった時などには、11行すべてを書き換える必要がある

「テストのロジック」と「テストしなければいけないデータ」が一緒に記述されているため、メンテナンス性が低いコードになってしまっていると言えます。

関数を使ったテスト

このようなテストのメンテナンス性を高めるための方法の1つとしては、アサーションを行う部分を関数としてまとめておくというやり方が考えられます。例えば以下の要領です。

function test_roman2kana() {
  function oneTest(aExpected, aInput) {
    assert.equals(aExpected,
                  transform.roman2kana(aInput));
  }
  oneTest('あいうえお',      'aiueo');
  oneTest('aiueo',      'aiueo');
  oneTest('がぎぐげご',      'gagigugego');
  oneTest('にほんご',        'nihongo');
  oneTest('ぽーと',          'po-to');
  oneTest('きゃっきゃ',      'kyakkya');
  oneTest('うっうー',        'uwwu-');
  oneTest('\\(\\)\\[\\]\\|', '\\(\\)\\[\\]\\|');
  oneTest('\\(\\[',          '\\(\\[');
  oneTest('\\)\\]',          '\\)\\]');
  oneTest('\\|',             '\\|');
}

コードの冗長さが減りました。また、メソッド名や引数の取り方が変わった時も、変更が必要な箇所は1箇所だけになりました。

これでも悪くはないのですが、実際にテストを繰り返し走らせていると、以下のような問題が浮き彫りになってきます。

  • テストする入力パターンのどれか1つでアサーションに失敗したら、そこから後の記述がスキップされてしまう

例えば以下のようなシナリオが考えられます。

  1. テストを実行した。
  2. 2番目のパターンでfailした。
  3. 2番目のパターンでfailする原因となっていたバグを直した。
  4. 再度テストを実行した。
  5. 5番目のパターンでfailした。
  6. 5番目のパターンでfailする原因となっていたバグを直した。
  7. 再度テストを実行した。
  8. 6番目のパターンでfailした。
  9. …(以下続く)

「3の段階で行った修正で5や8のバグが発生した」という可能性もありますが、最初から2や5や8のバグがあったのであれば、まとめて直せていたかもしれません。これでは、バグを直しても直してもきりがないという、モグラ叩きのような感覚に陥ってしまいます。

ループを使ったテスト

メンテナンス性を高める別の手法として、アサーションに使う期待値とメソッドに渡す引数だけを配列で別途定義しておくというやり方も考えられます。

function test_roman2kana() {
  var patterns = [
        ['あいうえお',      'aiueo'],
        ['aiueo',      'aiueo'],
        ['がぎぐげご',      'gagigugego'],
        ['にほんご',        'nihongo'],
        ['ぽーと',          'po-to'],
        ['きゃっきゃ',      'kyakkya'],
        ['うっうー',        'uwwu-'],
        ['\\(\\)\\[\\]\\|', '\\(\\)\\[\\]\\|'],
        ['\\(\\[',          '\\(\\['],
        ['\\)\\]',          '\\)\\]'],
        ['\\|',             '\\|']
      ];
  for each (var pattern in patterns) {
    assert.equals(pattern[0],
                  transform.roman2kana(pattern[1]));
  }
}

これにもやはり問題があります。

  1. テストする入力パターンのどれか1つでアサーションに失敗したら、そこから後に記述された入力パターンに対するアサーションが行われない。(関数を使った書き方の場合と同じ問題)
  2. 配列の内容の順番を覚えておかないといけない
  3. failした時に、どの入力パターンでfailしたのかが分からない
  4. 1、2、3の問題を回避するための対処を毎回しなければならないのが面倒

1つ目の問題点については前述したとおりです。

2つ目の問題は、この例のように2次元配列を使った場合に現れます。上記の例では配列の0番目の要素が期待値、1番目の要素が入力となっていますが、これではどっちがどっちなのかを常に意識する必要があります。

3つ目は、ループに特有の問題です。UxUではテストにfailした時にスタックトレースが表示され、べた書きした場合や関数を使った書き方の場合であれば、スタックトレースを辿れば「どのパターンで失敗したのか」の情報に辿り着くことができます。しかし、ループを使っていると、スタックトレースの行き着く先はループの中になってしまうため、どのパターンに対して失敗したのかが一目では分からなくなってしまいます

この対策として、UxUのアサーションでは最後の引数として任意のメッセージを渡せるので、以下のようにして「どのパターンで失敗したのか」を表示させることは可能です。

assert.equals(pattern.expected,
              transform.roman2kana(pattern.input),
              pattern.input+'に対するテスト');

しかし、実際にたくさんテストを書くようになってくると、これが地味に面倒です。これが4つ目の問題点です。

べた書きした場合や関数を使った書き方の場合であれば、このような配慮なしに淡々とテストを書いていても、テスト実行時にはスタックトレースを辿ればデバッグに必要な情報を得られます。それなのに、ループに対してはこのような配慮をしなければいけないわけです。この面倒さによって、テストを新しく書いたり過去に書いたテストをメンテナンスしたりする意欲がじわじわと削がれてしまう、というのが一番の問題点だと言えます。

データ駆動テスト

UxU 0.7.0以降で導入されたデータ駆動テストの仕組みを使うと、上記のテストはこのように書くことができます。

test_roman2kana.parameters = utils.readParametersFromCSV('patterns.csv');
function test_roman2kana(aParameter) {
  assert.equals(aParameter.expected,
                transform.roman2kana(aParameter.input));
}

テスト用のコードにはロジックだけを書き、データは外部ファイルで定義します(後述しますが、テストケース内にデータを埋め込むこともできます)。データを定義しているファイルの形式はCSVです。

patterns.csvの内容
 inputexpected
半角英数aiueoあいうえお
全角英数aiueoaiueo
濁音のみgagigugegoがぎぐげご
濁音混じりnihongoにほんご
音引きpo-toぽーと
拗音kyakkyaきゃっきゃ
撥音uwwu-うっうー
paren\\(\\)\\[\\]\\|\\(\\)\\[\\]\\|
parenOpen\\(\\[\\(\\[
parenClose\\)\\]\\)\\]
pipe\\|\\|

この時、UxUは「test_roman2kana」という1つのテストではなく、「test_roman2kana (半角英数)」「test_roman2kana (全角英数)」……という名前の11個のテストを実行するようになります。

  • コードの繰り返しが無く、簡潔に書ける
  • すべてが独立したテストとして扱われるので、入力パターンのどれか1つでアサーションに失敗しても、他のテストはスキップされない
  • (CSVの場合)カラム名が引数として渡ってくるハッシュのキーとなるので、引数の順番を覚えておかなくていい
  • failした時は、実行されたテストの名前の中に実行時の入力パターンの名前が含まれるので、どのパターンで失敗したのかがすぐ分かる
  • これらをフレームワークの機能として提供しているので、テストを記述する時に面倒なことを考えなくてもよい

このように、データ駆動テストには多くのメリットがあります。単純な入出力のパターンを数多く検証しなければいけない場面で、データ駆動テストは威力を発揮します。

UxUでのデータ駆動テストの書き方

UxUでは、テスト関数のparametersプロパティに配列またはハッシュを代入すると、そのテストをデータ駆動テストとして実行するようになります。この時テスト関数には引数として、parametersプロパティの配列またはハッシュの要素が1つずつ渡されます。

以下は、配列を指定した場合の例です。

test_someFunc.parameters = [
  { expected : '29',   input : 'niku' },
  { expected : '2929', input : 'nikuniku' },
  { expected : '029',  input : 'oniku' }
];
function test_someFunc(aParameter) {
  /*
    1回目: aParameter = { expected : '29',   input : 'niku' }
    2回目: aParameter = { expected : '2929', input : 'nikuniku' }
    3回目: aParameter = { expected : '029',  input : 'oniku' }
  */
  ...
}

ハッシュを指定した場合は、以下のようになります。ハッシュのキーはテスト関数には渡されず、結果を表示する時のテスト名として表示されます。

test_someFunc.parameters = {
  simgle: { expected : '29',   input : 'niku' },
  double: { expected : '2929', input : 'nikuniku' },
  o:      { expected : '029',  input : 'oniku' }
};
function test_someFunc(aParameter) {
  /*
    1回目: aParameter = { expected : '29',   input : 'niku' }
    2回目: aParameter = { expected : '2929', input : 'nikuniku' }
    3回目: aParameter = { expected : '029',  input : 'oniku' }
  */
  ...
}

データ駆動テスト用の便利な機能

データ駆動テストのサポートに併せて、いくつかの新しいヘルパーメソッドが追加されています。これらを使うことで、データ駆動テストをより簡単に作成・メンテナンスすることができます。

前述の例で使用しているutils.readParametersFromCSV()は、CSVファイルの内容を読み込み、最初の行のカラム名をキーとしたハッシュとして返します。例えば前述の例のCSVは、以下のようなハッシュとして解釈されます。

// test_roman2kana.parameters = utils.readParametersFromCSV('patterns.csv');
// これは、以下のように書くのと同じ
test_roman2kana.parameters = {
  '半角英数':   { input: 'aiueo',           expected: 'あいうえお' },
  '全角英数':   { input: 'aiueo',      expected: 'aiueo' },
  '濁音のみ':   { input: 'gagigugego',      expected: 'がぎぐげご' },
  '濁音混じり': { input: 'nihongo',         expected: 'にほんご' },
  '音引き':     { input: 'po-to',           expected: 'ぽーと' },
  '拗音':       { input: 'kyakkya',         expected: 'きゃっきゃ' },
  '撥音':       { input: 'uwwu-',           expected: 'うっうー' },
  paren:        { input: '\\(\\)\\[\\]\\|', expected: '\\(\\)\\[\\]\\|' },
  parenOpen:    { input: '\\(\\[',          expected: '\\(\\[' },
  parenClose:   { input: '\\)\\]',          expected: '\\)\\]' },
  pipe:         { input: '\\|',             expected: '\\|' }
};

CSVファイルはRFC4180準拠の形式の読み込みに対応しています。カンマ区切りではなくタブ区切りのファイルを使用したい場合は、utils.readParametersFromTSV()を使用して下さい。どちらも、読み込みたいファイルのパス(相対パスも利用できます)を第1引数に、ファイルのエンコーディングを第2引数に指定します。エンコーディング指定を省略した場合はUTF-8として読み込みます。

以下のリンク先に、各メソッドの詳しい説明があります。

また、JSON形式で保存した外部ファイルを読み込むためのutils.readJSON()というメソッドもあります。

test_roman2kana.parameters = utils.readJSON('patterns.json');

こちらも、読み込みたいファイルのパス(相対パスも利用できます)を第1引数に、ファイルのエンコーディングを第2引数に指定します。エンコーディング指定を省略した場合はUTF-8として読み込みます。詳しい説明は以下のリンク先をご覧下さい。

まとめ

データ駆動テストの仕組みを利用すると、テストのロジックとデータを分離できるため、メンテナンス性が高まることが期待できます。様々なパターンの入力を受け付ける機能を開発する時は、データ駆動テストをぜひ一度試してみて下さい。

2009-10-30

とちぎRuby会議02の資料公開

先日開催されたとちぎRuby会議02で社長の一人として話しました。声をかけてくれたtoRubyのみなさん、ありがとうございます。

儲かるRuby - 支えるRuby

システム構成

Debian GNU/Linux sidが動いているMacBookを持っていきました。事前の接続テストでうまくプロジェクターに出力できなかったので、ワイクル株式会社kdmsnrさんのMacBook経由で出力しました。突然のお願いにもかかわらず快く貸してくれました。ありがとうございます。

システム構成

Rabbit本体はDebian上で動いていて、携帯電話からのリモート操作もDebian上のRabbitに対して行っています。しかし、画面表示は別マシンのMac OS X上のX11で行っています。プロジェクターへの出力も別マシンのMac OS Xが行っています。

dRubyなど咳プロダクツを用いたプレゼン環境の1つのパターンのデモとして、上記のような構成を用いました。かっこいいですね。

内容

8割ほどRubyととちぎをまじえた発表者紹介・会社紹介をし、最後にかるく現在の仕事の内容、これから向かおうとしている方向を紹介しました。他の方たちのように具体的な方法は提示していません。私たちも続けられるしくみを模索しているのが現状です。

雰囲気がよい

とちぎRuby会議02はとても楽しく居心地のよい雰囲気に満ちていました。どうしてなのかはわかりません。まだ体験していない人はぜひ一度体験してみることをおすすめします。toRubyに参加すると体験できるでしょう。

まとめ

地域Ruby会議2ndシーズン最初のとちぎRuby会議02に参加しました。

お昼ご飯をゆっくり食べていたおかげで開始時刻が遅れてしまい、すみませんでした。あのよい雰囲気の中でtoRuby勉強会を味わう時間が減ってしまったことが残念です*1

とちぎRuby会議にはRuby札幌の方も参加していましたが、12月には札幌Ruby会議02があります。こちらにも発表者として参加する予定です。とちぎRuby会議02のようにとてもすばらしい地域Ruby会議になりそうな予感がします。都合のあう方は参加してみてはいかがでしょうか。

札幌Ruby会議02までには関西Ruby会議02TokyuRuby会議01もあります。こちらの内容もとてもおもしろそうですね。

*1  お昼ご飯タイムもとてもよい雰囲気でした。

Tags: Ruby | このエントリの del.icio.us history 2 users | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | Permalink
2009-10-25

CPUの使用率を表示するFirefoxアドオン「システムモニター」を更新しました

CPUの使用率をFirefoxのツールバー上に表示するアドオン「システムモニター」のバージョン0.2をリリースしました。以下のリンク先からダウンロードできます。

前のバージョンからの変更点は以下の通りです。

  • グラフの左右端をドラッグすることで任意の幅に変更できるようになりました。
  • ツールバーのカスタマイズでグラフを任意の位置に移動できるようになりました。(初回起動時に、ツールバーに項目を追加するかどうかの確認を求めるようになりました。)

導入手順

インストールすると、初回起動時に以下のイメージのようなダイアログが表示されます。

初回起動時の確認ダイアログ

「はい」を選択すると、メニューバーの右端(Mac OS Xではナビゲーションツールバーの右端)に、以下のイメージのようにCPU使用率のグラフが表示されます。

CPUモニター

プラットフォームは、WindowsXP SP1以降、Mac OS X、Linuxをサポートしています。Linuxではlibgtop2を使用していますので、別途インストールしてください。

Web APIの利用

このアドオンを導入した環境では、システムモニターが提供するAPIを通じて、Webページ上のスクリプトからシステムモニターと同じ情報を取得できるようになります。以下は、Webページ内にCPU使用率のグラフを埋め込む例です。実際に上記リンク先からアドオンをインストールした状態で、このページを表示してみて下さい。

<div id="system-monitor-demo"></div>
<script type="text/javascript"><!--
var container = document.getElementById("system-monitor-demo");
if (!window.system || !window.system.addMonitor) {
  container.innerHTML = "システムモニターがインストールされていません";
  container.style.color = "red";
} else {
  container.innerHTML = "<div><canvas id='system-monitor' width='300' height='60'></canvas>"+
                        "<br /><span id='system-monitor-console'></span></div>";

  var width = 300;
  var interval = 1000;

  var CPUTimeArray = [];
  var arrayLength = width / 2;
  while (CPUTimeArray.length < arrayLength) {
    CPUTimeArray.push(undefined);
  }

  // リスナとして登録する関数には、CPUの使用率が割合として渡される。
  function onMonitor(aUsage) {
    var console = document.getElementById("system-monitor-console");
    console.textContent = aUsage;

    CPUTimeArray.shift();
    CPUTimeArray.push(aUsage);

    var canvasElement = document.getElementById("system-monitor");
    var context = canvasElement.getContext("2d")
    var y = canvasElement.height;
    var x = 0;

    context.fillStyle = "black";
    context.fillRect(0, 0, canvasElement.width, canvasElement.height);

    context.save();
    CPUTimeArray.forEach(function(aUsage) {
      var y_from = canvasElement.height;
      var y_to = y_from;
      if (aUsage == undefined) {
        drawLine(context, "black", x, y_from, 0);
      } else {
        y_to = y_to - (y * aUsage);
        y_from = drawLine(context, "green", x, y_from, y_to);
        drawLine(context, "black", x, y_from, y_to);
      }
      x = x + 2;
    }, this);
    context.restore();
  }

  function drawLine(aContext, aColor, aX, aBeginY, aEndY) {
    aContext.beginPath();
    aContext.strokeStyle = aColor;
    aContext.lineWidth = 1.0;
    aContext.lineCap = "square";
    aContext.moveTo(aX, aBeginY);
    aContext.lineTo(aX, aEndY);
    aContext.closePath();
    aContext.stroke();
    return aEndY - 1;
  }

  // リスナを登録する。
  window.system.addMonitor("cpu-usage", onMonitor, interval);

  window.addEventListener("unload", function() {
    window.removeEventListener("unload", arguments.callee, false);
    // ページのアンロード時にリスナの登録を解除する必要がある。
    window.system.removeMonitor("cpu-usage", onMonitor);
  }, false);
}
// --></script>

次世代のWebアプリケーションに向けて

上の例をご覧いただいても分かるとおり、このアドオンは、Webアプリケーション(Webページ上のスクリプト)から各種ハードウェアデバイスへアクセスする技術のデモンストレーションとして開発されています。

Webカメラとの連動によるAR(拡張現実)や、指紋認証、Felica認証といった特殊なハードウェアを必要とする認証技術など、この技術を応用することにより今までのWebアプリケーションではできなかった様々なことが可能となります。

クリアコードでは、このようにWebとハードウェアを直結する技術の開発に取り組んでおります。特殊なデバイスを搭載したハードウェアへのWebブラウザエンジンの組み込みや、特殊なデバイスの利用を前提としたWebベースのシステム開発など、ご用命がございましたらぜひinfo@clear-code.comまでお問い合わせください

Tags: Mozilla | このエントリの del.icio.us history 1 user | このエントリを含む Yahoo!ブックマーク | このエントリを含むはてなブックマーク | このエントリを含む livedoor クリップ | このエントリを含む FC2ブックマーク | このエントリを含む Buzzurl | このエントリをTweetする | Permalink
2009-10-20

最新
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|