最近、八角研究所で技術記事を書いているのですが、そこで、はてなブックマークの作り直しについて - naoyaのはてなダイアリーに触発されてですが、TDD(テスト駆動開発)が新規性の高いサービス開発に適するかどうか考察してみました。

TDD は新規性の高いサービス開発には適さない
http://www.hakkaku.net/articles/20080331-181

この話題にはもう少し踏み込みたいところです。

Posted by あかさた
Kodougu のテストを RSpec で書いてみようと思ったので、RSpec をインストールしてみました。

どうしたわけか、Rubyist Magazine - スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)のやり方では,
RSpec on Rails がインストールできなかったので、以下を参考にインストールしてみました。

RSpec-1.1.1: Installation
http://rspec.info/documentation/rails/install.html

ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec
ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec_on_rails

URL が多少変わっていますが、そのせいですかね??? どうも、RubyForge の事情には疎いのでわかりかねます。(--;

そして、こちらの記事のやり方でテストデータを作成して、今テストを書いています。今のところ、RSpec のメリットは全くわからないのですが(オイオイ)、書いていて気持ちいいですね。

Posted by あかさた
C# でオープンソースとして開発している RPG 制作ツール RmakeDycoon 氏 に使ってもらいました。かなりいやらしい使い方をしてくれたようで、致命的なバグをいくつか発見してくれました。(ユーザビリティ上の問題点もたくさん挙げてくれました。)いわゆる小規模なベータテストというやつです。

これまで自分でドッグフードしてきましたが、品質の改善には限界があるなとつくづく思いました。なにより、開発者よりもユーザーの方がヘビーユーザーになる可能性の高いツールなので、ドッグフードのやり方では、普通のテストでは見過ごしてしまう重要なバグを見つけきれないという問題もあります。

やはり品質を上げるには、使ってもらうのが一番なんですよね。開発途上版を使ってくれる人なんてそうそういないでしょうが。。。

Rmake は(ソースコードも含めて)こちらから入手できます。現在、アルファ版です。

Posted by あかさた
ソフトウェア開発における「ドッグフードを食べる」とは、ソフトウェアの開発者もしくはその会社が、自分たちが開発したソフトウェアを実際に業務などで使用することです。要は自らを実験台にするわけです。有名なのはマイクロソフトのドッグフードです。どこよりも早く MS 製品を使い始めるわけですから、命がけ(?)ですね。(^^; それゆえあれだけ複雑な製品をそれなりの品質で提供できているともいえます。

ソフトウェアをドッグフードしなければいけない理由より

ソフトウェアのドッグフードというのは、開発を継続できる最大の武器だよね。自分がアプリを開発していけるのは、自分で日常的に使ってるから、というのが大きい。バグだってどんどん見つかるし、改善すべき点も見えてくる。自分は使わずに誰かの為に開発しているソフト、なんてものを私は信じない。何年何十年と粘着的に考え続け使い続け、開発するからこそ見えてくる何かがあるはずだ。


自分は使わずに誰かのために開発しているソフト、例を挙げると「オーダーメイドの業務アプリ」です。システムを内製できる会社はそれほど多くないので、業務システムは外部の企業に発注するわけですが、そうした請負の会社でドッグフードして実業務で使用してから納品なんて話はあまり聞きません。(「あまり」と書いたのは、システムそのものをドッグフードしないのであれば、たとえばソリューションのドッグフードなどは行われているケースがあるからです。)

続きを読む

Posted by あかさた
最近、JavaScript のテストを行う方法を探していました。xUnit 系の JsUnitMochiKit に付属してくるテストフレームワークが有名どころのようです。とりあえずテスト用のメソッド名になじみのある JsUnit を使ってみることにします。

■ 目的
alert デバッグから開放される!

■ この記事に書かれていること
・ JsUnit の基本的な情報
・ 複数のテストファイルを同時に実行する方法
・ testRunner.html で毎回テストファイル名を指定しないでテストを実行する方法

■ JsUnit の概要
まず、testRunner.html というテストランナーが存在します。html ファイルの script タグ内にテストコードを書いて、テストランナーにテストファイル(html)を指定すると、テストコードを実行するという仕組みになっています。

まずは動かしてみてください。JsUnit を DL すると、testRunner.html というファイルが含まれているので、このファイルをブラウザで開いてください。テストファイル名の入力を促されるので、tests フォルダ内の適当な html ファイル(テストファイル)をフルパス指定して、Run ボタンを押してください。見慣れたグリーンバーが表示されるはずです。

テストの書き方は、テストファイルを見ればわかります。イメージとしては以下のようなコードになります。

<script language="JavaScript" type="text/javascript" 
  src="./framework/app/jsUnitCore.js">
</script>
<script language="JavaScript" type="text/javascript">
function testAssert() {
    assert("true should be true", true);
    assert(true);
}
</script>

■ テストのディレクトリ構造
とりあえず、私は以下のような構造にしてみました。
 + e:\tests
   + framework <- ここに testRunner.html など JsUnit の全てのファイル/フォルダを入れる
   - tests.html <- テストコードの一覧を持つ
   - xxxTest.html <- テストコード

■ suite を使って複数のテストファイルを実行する
複数のテストファイルを同時に実行するには、tests.html に以下のように書いて、tests.html を testRunner.html 実行してください。
<script language="JavaScript" type="text/javascript" 
    src="./framework/app/jsUnitCore.js">
</script>
<script language="JavaScript" type="text/javascript">
function suite(){
  var newsuite = new top.jsUnitTestSuite();
  newsuite.addTestPage("../xxxTest.html");
  return newsuite;
}
</script>

テストファイル名を指定する際、tests.html からではなく、testRunner.html からの相対パスを指定していることに注意してください。

■ URL にテストファイル名を含めることで、毎回の入力を回避する
毎回 testRunner.html にテストファイル名を入力するのが面倒なので、URL のパラメータ「testpage」を指定すると、ファイル名を自動的に入力してくれます。以下のような URL を作成してお気に入りに入れておくと良いでしょう。

URL の例:file:///E:/tests/framework/testRunner.html?testpage=E:/tests/tests.html

■ 参考
Javascript/JsUnit - Bobchin's Wiki
http://bobchin.ddo.jp/wiki/index.php?Javascript%2FJsUnit


Posted by あかさた
act_as_authenticated に自動生成された UserTest の test_should_reset_password が失敗するという現象が発生しています。具体的には以下のコードで失敗します。
# 前からこんな現象あったかなー???

def test_should_reset_password
  users(:quentin).update_attributes(:password => 'new password', 
    :password_confirmation => 'new password')
  assert_equal users(:quentin), User.authenticate('quentin', 'new password')
end

エラーの発生条件:RadRails 0.7.1 上で Run Unit Tests を実行する、rake test:units を実行する

ただし、上記テストメソッド単体を実行すると、テストは成功することから、他のテストメソッドの実行結果に影響されて失敗していることがわかります。DB の内容を見ると、users(:quentin) の login(ユーザ名)は 'quentin' ではなく 'quentin2' になっています。このことから、test_should_reset_password は、test_should_not_rehash_password に依存しているようです。

Ruby(と Rails)の UnitTest フレームワークの性質をよく知らないのですが、テストメソッドを実行するたびに DB の初期化が実行されればこういった問題は発生しないと考えられるので、DB の初期化を行うタイミング(=fixtures メソッドが呼ばれるタイミング)はテストクラスを読み込む一度きりということでしょう。

とはいえ、原因がわかっても、テストコードの修正は簡単ではありません。他のテストの実行の有無で結果が変わってしまうテストを変わらないコードにするというのはなかなか難しいことです。とりあえず以下のように考えてみました。

このテストのやりたいこと:レコード更新時にパスワードを変更する(:password_confirmation があるときは、パスワードを更新することを確認したい。※)

※ そういう意味では、更新時に :password のみを変更して :password_confirmation を変更しない or 間違っているテストが無いことは少し疑問です。もっとも、生成時のテストはあるからそれで問題はありませんが。

修正案1:レコード更新時に login も更新する
def test_should_reset_password
  users(:quentin).update_attributes(:login => 'quentin', 
    :password => 'new password', :password_confirmation => 'new password')
  assert_equal users(:quentin), User.authenticate('quentin', 'new password')
end

メリット:修正は簡単
デメリット:テストコードの意図がやや不明瞭になる

修正案2:更新したユーザのインスタンスから名前を取得して、認証を行う
def test_should_reset_password
  users(:quentin).update_attributes(:password => 'new password', 
    :password_confirmation => 'new password')
  assert_equal users(:quentin), User.authenticate(users(:quentin).login, 'new password')
end

メリット:テストの意図はそのまま
デメリット:元と比べてコードがわかりにくい?(というか、なんか気持ち悪い・・・)

とりあえず、修正案2で行きます。


Posted by あかさた
私は Ruby の module のテストは以下のように行っています。

[code: def test_hoge

@dummy_object = Object.new
@dummy_object.extend(HogeModule)
assert ...hogehoge...
end]

他にも以下のような方法も考えられます。クラスを生成してからモジュールを拡張する方法です。

[code: def test_hoge

@new_class = Class.new
@dummy_object = @new_class.allocate
@dummy_object.extend(HogeModule)
assert ...hogehoge...
end]

コードとしては以下がわかりやすいかも。明示的にテスト用のクラスを作成する場合です。

[code: class Dummy

include HogeModule
end

def test_hoge
@dummy_object = Dummy.new
assert ...hogehoge...
end]

Ruby の module は Java や C# には無い概念なのでどういうテストをしたらいいのか悩みますね。

Posted by あかさた