LDAPのエントリを ActiveRecord風のAPIでア クセスするためのライブラリ、 ActiveLdap 1.0.1がリリースされました。
ActiveRecord風のAPIとは1エントリを1オブジェクトとして扱える ということです。例えば、ユーザの説明を変更する場合は以下のよ うになります。
alice = User.find("alice") alice.description = "New user" alice.save!
ActiveRecordと同じように、各クラス間の関係を設定して便利にア クセスすることもできます。
class User < ActiveLdap::Base belongs_to :groups, :many => "memberUid" end class Group < ActiveLdap::Base has_many :users, :wrap => "memberUid" end alice = User.find("alice") alice.groups # => [Group("friend"), Group("office"), ...] alice.groups << Group.find("home") alice.groups # => [Group("friend"), Group("office"), Group("home"), ...] friend = Group.find("friend") friend.users # => [User("alice"), User("bob"), ...]
ActiveRecordと同じように、Ruby on Railsと使用することもでき ます。
% script/plugin install http://ruby-activeldap.googlecode.com/svn/tags/r1.0.1/rails/plugin/active_ldap % script/generate scaffold_active_ldap % vim config/ldap.yml
ActiveLdapは以下のライブラリをバックエンドとして利用できます。
以下はActiveLdapに付属するベンチマークの結果です。ベンチマー クでは100エントリを検索しています。「Rehearsal(リハーサル)」 を行って、それぞれ2回ずつ実行しているのは、以前はキャッシュ などで2回目以降の結果がよくなることなどがあったためです。現 在はあまり意味がありませんが、歴史的に残っています。
% ruby benchmark/bench-al.rb --config benchmark/config.yaml
Populating...
Rehearsal -------------------------------------------------------
1x: AL 0.080000 0.010000 0.090000 ( 0.098738)
1x: AL(No Obj) 0.010000 0.000000 0.010000 ( 0.016623)
1x: LDAP 0.000000 0.000000 0.000000 ( 0.008674)
1x: Net::LDAP 0.030000 0.000000 0.030000 ( 0.045199)
---------------------------------------------- total: 0.130000sec
user system total real
1x: AL 0.080000 0.020000 0.100000 ( 0.100959)
1x: AL(No Obj) 0.010000 0.010000 0.020000 ( 0.020697)
1x: LDAP 0.000000 0.000000 0.000000 ( 0.010129)
1x: Net::LDAP 0.030000 0.000000 0.030000 ( 0.042075)
Entries processed by Ruby/ActiveLdap: 100
Entries processed by Ruby/ActiveLdap (without object creation): 100
Entries processed by Ruby/LDAP: 100
Entries processed by Net::LDAP: 100
Cleaning...
各項目はそれぞれ以下の通りです。
上記の結果からは以下のことが言えます。
多くの場合、1度に100エントリを処理することは少ないでしょう。 そのため、通常はActiveLdapで各エントリをオブジェクト化しても 問題は少ないといえます。
もし、1度に多くのエントリを扱う場合で、読み込み専用ならば、 オブジェクト化しない方法で利用することでパフォーマンスを改善 することができます。
ActiveLdapを利用することでLDAPのエントリをオブジェクト指向的 なAPIで自然に処理することができます。
ActiveLdapは複数のLDAPバックエンドに対応しており、Rubyがイン ストールされている環境さえあれば動かすこともできます。 (Net::LDAPバックエンド使用時。ただしそんなに速くない)また、 JRubyでもほとんどの機能が動きます。
もし、Ruby/LDAPを利用できる環境であれば、Net::LDAPを直接利用 するよりも、ActiveLdap + Ruby/LDAPバックエンドを利用した方が よりオブジェクト指向らしいAPIでLDAPのエントリを操作できます。 また、速度が要求される場合であれば、オブジェクト化を行わない (オブジェクト指向らしいAPIを利用しない)ことにより、より高 速にLDAPのエントリを読み込むことができます。
Firefox用アドオンやXULRunnerアプリケーションなどのいわゆるXULアプリケーションは、ロジック部を主にJavaScriptで記述するため、script.aculo.usのテスト関連機能などJavaScript用のテストツールを使って自動テストを行えます。しかし、一般的なJavaScript用のテストツールはWebアプリケーションをテストすることを主眼において開発されているため、利用できる機能に制限があったり、HTMLではなくXULを使用するXULアプリケーションのテストでは不具合が生じたりする場合があります。
UxU(UnitTest.XUL)は、著名なXULアプリケーション開発支援ツールであるMozLabをベースにクリアコードで開発を行っている自動テスト実行ツールです。FirefoxやThunderbirdなどのXULアプリケーション上での利用を前提としているため、前述のような制限や問題を気にすることなく自動テストを記述できる、便利なヘルパーメソッドを利用できる、などの特長があります。
テストの記述方法やヘルパーメソッドの一覧はUxUの紹介ページに情報がありますが、ここではFirefox用アドオンのXUL/Migemoのテストを実例として示しながら、UxUによる自動テストの方法について簡単にご紹介をしたいと思います。Subversionのリポジトリ内にテストのサンプル用に用意されたタグがありますので、まずはこちらから必要なファイル一式をチェックアウトしておいてください。
まずは最も簡単な例として、「tests」→「unit」とフォルダを辿った中にあるdocShellIterator.test.jsを見てみましょう。
XUL/MigemoはFirefoxのページ内検索を拡張するアドオンですので、フレーム内に検索語句が見つからなかったときは、子フレームや親フレームを検索対象として自動的に再検索を行うといった処理が必要になります。docShellIterator.test.jsでは、このための処理を担当するクラス「DocShellIterator」が正しく機能するかどうかをテストしています。
utils.include('../../components/pXMigemoFind.js', null, 'Shift_JIS');
冒頭では、ヘルパーメソッドのutils.includeを使って、DocShellIteratorクラスが定義されているファイルpXMigemoFind.jsの内容を取り込んでいます。第3引数で読み込むファイルのエンコーディングを指定していますが、これはファイルの中に含まれる日本語のコメントがShift_JISになっているためです。
なお、何らかの事情でそのままファイルをincludeできない(includeするとまずい)場合には、ファイルの内容をテキストとして読み込んで加工した後に評価するという方法もあります。上の例は、以下のように書き換えても同様に動作します。
var extract; eval('extract = function() { '+ utils.readFrom('../../components/pXMigemoFind.js') + '; return DocShellIterator }'); var DocShellIterator = extract();
utils.readFromはファイルの内容を文字列として返しますので、replaceなどを使って邪魔な部分を消してやれば、そのままではincludeできないファイルから必要な部分だけを取り出して評価できます。
このファイルにはテストケースが一つだけ定義されています。テストの前処理(setUp)と後処理(tearDown)は以下の通りです。
var DocShellIteratorTest = new TestCase('DocShellIteratorのユニットテスト'); DocShellIteratorTest.tests = { setUp : function() { yield Do(utils.loadURI('../res/frameTest.html')); }, tearDown : function() { iterator.destroy(); yield Do(utils.loadURI()); },
setUpとtearDownは、それぞれのテストを実行する前と後に毎回実行されます。このテストケースでは、テスト実行前に「テストに使用するフレームにHTMLファイルを読み込む」という処理を行い、テスト終了後に「フレームに空のページを読み込んで内容を破棄する」という処理を行うことで、毎回必ずクリーンなテスト環境を準備するようにしています。
setUpの中では、「フレームに指定したページを読み込み、読み込みが完了するのを待って次に進む」といった処理待ちを行うために、ヘルパーメソッドとyield式を使用しています。yieldは本来はJavaScript 1.7で導入されたジェネレータのための物ですが、UxUではこれを応用して処理待ちを実現しています。JavaScriptで処理待ちというと、タイマーやonloadのようなイベントハンドラを使う方法が真っ先に思い浮かぶと思いますが、yield式を使用すれば、それらの場合に比べて処理の流れをフラットに記述することができます。
個々のテストの定義を見てみましょう。以下のテストでは、DocShellIteratorクラスのインスタンスを生成して、検索対象のフレームを移動する処理を実際に行い、処理結果が期待されたものと等しいかどうかをテストしています。
'前方検索': function() { // 1番目のフレームを初期状態としてインスタンス生成。 iterator = new DocShellIterator(content.frames[0], false); // 初期化は成功したか? assert.initialized(iterator, content.frames[0]); // 次のフレームに移動するメソッドを実行。 assert.isTrue(iterator.iterateNext()); // フォーカスは正常に2番目のフレームに移動したか? assert.focus(iterator, content.frames[1]); assert.isFalse(iterator.isInitial); // もう一度フレームを移動。 // 3番目のフレームは無いので、一巡して最上位のフレームにフォーカスする。 assert.isTrue(iterator.iterateNext()); // フォーカスは正常に最上位のフレームに移動したか? assert.focus(iterator, content); assert.isFalse(iterator.isInitial); // もう一度フレームを移動。1番目のフレームに戻る。 assert.isTrue(iterator.iterateNext()); // フォーカスは正常に1番目のフレームに移動したか? assert.focus(iterator, content.frames[0]); },
処理が成功したかどうかを確認する手続きを、アサーション(宣言)と呼びます。assert.isTrue、assert.isFalse、assert.equalsなどのアサーション用ヘルパーメソッドは、実行されると渡された値を評価します。実際に渡された値が期待された値と等しければそのまま処理を続行しますが、値が異なっていた場合は「アサーションに失敗した」という内容の例外を発生させてテストの実行が中断されます。これらの例外やメソッド実行時に発生した未知の例外はUxUのインターフェース上に逐次表示され、例外が発生した行のスタックトレースを後で辿ることができるため、どの時点で問題が起こったのか、どこまでは予想通りに正常に動いたのかを詳しく調べてデバッグに役立てられます。
UxUのページのアサーション一覧にないassert.initializedやassert.focusはこのテスト専用に定義したカスタムアサーションです。その実体は、このファイルの冒頭でassertオブジェクトに追加されている新しいメソッドで、以下のように、内部でより単純なアサーションを複数行っています。
assert.focus = function(aIterator, aFrame) { assert.equals(aFrame.location.href, aIterator.view.location.href); assert.equals(aFrame, aIterator.view); assert.equals(aFrame.document, aIterator.document); assert.equals(aFrame.document.body, aIterator.body); var docShell = getDocShellFromFrame(aFrame); assert.docShellEquals(docShell, aIterator.current); assert.isTrue(aIterator.isFindable); }
複数の条件を満たしているかどうかを確認する必要がある場合、そのままテストを記述すると、同じコードがテストケースの中に大量に並んでしまいます。そういった一連の処理はカスタムアサーションとしてまとめておけば、テストケースの内容を綺麗に見やすくできます。
テストの実行は、MozRepl互換のUxUサーバを起動してコンソールから接続して行うか、MozUnit互換のテストランナーを起動して行います。「ツール」メニューから「UnitTest.XUL」→「MozUnitテストランナー」と辿り、テスト実行用のGUIを起動します。
UxUのテスト実行用GUIは、テストケースのファイルのドラッグ&ドロップを受け付けます。実行したいテストのファイル(docShellIterator.test.js)をウィンドウにドラッグ&ドロップすると「作業中のファイル」欄のファイルのパスがドロップされたファイルのものになりますので、後は「実行」ボタンを押すだけで自動テストを実行できます。
テストの現在の実行状況はプログレスメーターで表示されます。アサーションに失敗したり予期しないエラーが起こったりするとプログレスメーターが赤くなりますが、正常にテストが成功すれば緑色のままです。すべてのテストケースの実行結果が緑となることを目指して開発や修正を進めていきましょう。
このように、一連の処理と期待される結果をまとめておき、自動的にテストできるようにしておくと、開発した機能がきちんと動作しているかどうかを人手を煩わさず機械的に確かめられます。一通りのテストケースを作成するにはそれなりの手間と時間を要しますが、一度作成しておけばテストは何度でも簡単に実行できますので、うっかりミスによるエンバグを未然に防ぐ*1ことができます。
Firefox用のアドオンはFirefox自身が持っている機能と連携して動作するように作られることが多く、自動テストを作るのはなかなか大変です。UxUには処理待ち機能をはじめとした多くの便利な機能があり、「このような操作を行った時に、こういう結果になる」といった人間の操作をシミュレートする形の複雑なテストでも、処理の流れを追いやすい形で記述できます。Firefox用アドオンの自動テスト作成にはまさにうってつけと言えるでしょう。
*1 エンバグしてしまってもすぐにそれに気がつくことができる
Gauche 用の単体テストフレームワーク GaUnit の0.1.4がリリースされました。
Gaucheには標準でgauche.testという単体テスト用のモジュールが付 属しています。このモジュールはテストスクリプトをはじめから順 に実行していくという素直なテスト実行方式を採用しています。こ の方式では一連のテストがどのように実行されていくかがわかりや すい半面、(場合によっては)以下のような問題があります。
また、gauche.testはテストの成功、失敗にかかわらずテスト結果が 常に冗長であるという問題があります。テストが失敗した項目(修正 の必要がある項目)についてのみ情報を詳細する方が、より合理的で しょう。
ということで、GaUnitです。GaUnitは xUnit系の単体テ ストフレームワークで、上記のgauche.testの問題を解決します。 また、テスト失敗時以外はテスト結果に余計な情報を出力しないため 無駄がありません。そんなGaUnitが今回のリリース(正確には1つ前の 0.1.3)からより書きやすいAPIを提供して、以下の2点を行うだけで すむようになりました。
実際のコードは以下のようになります。
(define-module test-your-library (extend test.unit.test-case) (use your-library)) (select-module test-your-module) (define (test-your-module-procedure1) (assert-equal "Good!" (your-module-procedure1)) ... #f) (define (test-your-module-procedure2) (assert-equal 29 (your-module-procedure2)) ... #f) (provide "test-your-module")
最後にtest-*手続きの最後に#fを付けているのは末尾再帰の最適化 でバックトレースを落とさないようにするためです。
新しいAPIでは、普通のGaucheライブラリと同じようにテストを書 けます。テストのために覚えることと言えば、どのassert-*を使お うかということくらいです。これは、普段の別のライブラリを用い た開発と同じですね。
GaUnitでのテスト書き方をもっと知りたい人はチュートリアル を読んでください。
Rubyで実装されたSubversion用*1リポジトリブラウザ(兼ITS(Issue Tracking System)/BTS(Bug Tracking System))としてRetrospectivaがあります。
Retrospectivaにはコミットログを解析してより便利にRetrospectivaを使うための機能がいくつかあります。Retrospectivaを採用しているプロジェクトであれば、コミットログの書き方をRetrospectivaの解釈できる書き方にすることにより、さらにRetrospectivaを便利に使うことができます。
RetrospectivaはチケットベースのITS機能を備えています。コミットログには「XXX番のチケットの問題を修正した」というようなログを書くことも多いでしょう。その時、以下のフォーマットでチケットの番号を書くことにより、そのコミットログをRetrospectiva上で見ると該当するチケットにリンクが張られます。(例)
[#XXX]
この機能はブラウザ上から変更履歴を見ているときにとても便利です。
RetrospectivaにはExtensionという拡張機能の仕組みがあります。この仕組みを利用したSCM Ticket Updateという拡張機能を導入することにより、コミットログでチケットの状態を更新することができます。
SCM Ticket Updateの導入方法は以下の通りです。(現時点(2008-05-23)のtrunkを利用している場合)
% RAILS_ENV=production ruby script/rxm checkout http://retrospectiva.googlecode.com/svn/extensions/1-1/scm_ticket_update % RAILS_ENV=production ruby script/rxm install scm_ticket_update % # Retrospectivaを再起動
拡張機能をインストールした場合はRetrospectivaを再起動することを忘れないでください。
この拡張機能を入れた後は以下のような書式でコミットログを書くことにより、コミットと一緒にチケットも更新することができます。
チケット#123の状態を修正済みに変更:
クラッシュバグを修正 [#123] (status:fixed)
チケット#29の割り当てユーザをaliceに変更:
[#29] テストを追加 (assigned:alice)
他にも以下のような書式が使えます。
[#N] (NAME1:VALUE1 NAME:VALUE2 ...) ログ
また、もし変更後の値に空白が入っている場合は「"..."」とダブルクォートで囲みます。
[#2929] (status:fixed milestone:"2.9 (バラ)") fix a trivial bug.
この機能を使うと、コミットした後にブラウザからチケットを変更する作業がなくなるのでチケットのクローズし忘れも減るかもしれません。また、コミットメールで(コミットログから)チケットがクローズされたことが分かるのも便利な点です。
このようにコミットログの書き方を少しRetrospectivaよりにするだけでもっと便利にRetrospectivaが使えるようになります。
Retrospectivaが要求している書き方を使ってもコミットログが見づらくなるわけではないので、少し意識して使ってみてはいかがでしょうか。
*1 Gitにも微妙に対応している
昨年の10月から開発しているCutterというC言語用の単体テストフレームワークのバージョン1.0が昨日リリースされました。
フリーソフトウェアの世界には簡単にテストを書けるC言語用の単体フレームワークがそれまではありませんでした。例えば、GLibに含まれているGTesterでは以下のようにmain関数を定義してその中で実行したいテストを一つづつ登録しなくてはいけません。
GLibのtests/testingbase64.cから抜粋:
static void test_base64_encode_decode (void) { ... } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/misc/base64/decode", test_base64_decode); return g_test_run (); }
Cutterではこのような冗長な記述をさけるため以下のようなルールでテストが書かれていることを仮定しています。
このようなルールを定めることによって、main関数を書く必要がなく、テストを登録する関数も呼ぶ必要がないために、より楽にテストを書けるようになっています。
Cutterでは上記のテストは以下のように書くだけですみます。
void test_base64_encode_decode (void); /* prototype */ void test_base64_encode_decode (void) { ... }
テスト関数はダイナミックリンクライブラリとして読み込まれて実行される必要があるのでstaticな関数ではなくなってプロトタイプ宣言を書く必要がありますが、それでもGTesterに比べて簡潔に書けるようになっています。
Cutterではその他にもアサート文が充実してることなど他の単体テストフレームワークよりもかなり使いやすくなっています。Cでテストを書かなくてはいけなくなった時にはぜひ使ってみてください。