2009.09.24
開発

テスト駆動開発と、RSpec  (その1)

テスト駆動開発(TDD)という言葉を聞いたことがあるだろうか?
アジャイルソフトウェア開発では、一般的な技法です。
テストという名前が付きますが、テストの技法ではなく、あくまでの開発のための技法です。
テスト駆動開発とはなんなのかと説明すると、以下のような開発手順になります。

1.仕様にそってテストプログラムを書きます。
2.この段階で、テストを実行してエラーが発生することを確認します。
3.テストの仕様を満たすように、本体コードを書きます。
4.テストを実行して、エラーがないことを確認します。
5.必要に応じてリファクタリング(改善)を行います。
6.テストを実行して、エラーが発生しないことを確認します。
7.1.に戻って新しいテストの仕様を追加します。

あくまでも、テストプログラムが先行していることが一つの条件になっています。
しかし、人間の特性として、先にテストを書くというのは、しっくりこないのか、最近では、振舞い駆動開発(BDD)という考え方が出てきています。

1.仕様に沿ったサンプルを作成します。
2.実行しエラーが起きることを確認します。
3.コードを作成し、仕様を満たすことを確認します。
4.必要に応じてリファクタリング(改善)を行います。
5.必要があれば、1.に戻って仕様を追加します。

基本的な流れは一緒なのですが、テストケースを考えるというよりは、詳細仕様を書くように変わっています。

今回はRubyのBDDフレームワーク、RSpecを紹介します。

振舞い駆動開発では、最初に作成する部品(関数なりクラス)サンプルを作成します。
たとえば、サンプルとしてtrim関数を作ってみます。

trim関数の仕様を以下のように定義します。
引数1個をとります。
引数が文字列の場合、先端と末尾に空白があった場合、その空白を削除します。
空白文字は、スペースとタブです。

引数が、nilの場合、nilを返します。
文字列以外の場合は、to_sメソッドを呼び出して文字列に変換して、文字列の場合と同じ処理をします。

では、この仕様を、specで定義してみましょう。
最初に空のファイルtrim.rbを作成して、実行してみます。

require     "trim.rb"
describe "trim関数の存在" do
    it "trim関数の呼び出し" do
        ret = trim(" aa ").should
    end
end

describe "trim関数にnilを渡す" do
    it "trim関数にnilを渡す" do
        trim(nil).should == nil
    end
end

describe "trim関数に文字列を渡す" do
    it "trim関数に文字列を渡す" do
        trim("abc").should == "abc"
    end
    it "trim関数に文字列を渡す" do
        trim(" abc").should == "abc"
    end
    it "trim関数に文字列を渡す" do
        trim("abc ").should == "abc"
    end
    it "trim関数に文字列を渡す" do
        trim(" abc ").should == "abc"
    end
    it "trim関数に文字列を渡す" do
        trim("   abc   ").should == "abc"
    end
    it "trim関数に文字列を渡す" do
        trim("\tabc").should == "abc"
    end
    it "trim関数に文字列を渡す" do
        trim("abc\t").should == "abc"
    end
    it "trim関数に文字列を渡す" do
        trim("\tabc\t").should == "abc"
    end
    it "trim関数に文字列を渡す" do
        trim("\t \tabc\t \t").should == "abc"
    end
end

describe "trim関数に文字列以外を渡す" do
    it "trim関数に数値を渡す" do
        trim(123).should == "123"
    end
    it "trim関数に数値を渡す" do
        trim(-123).should == "-123"
    end
    it "trim関数に数値を渡す" do
        trim(0).should == "0"
    end
    it "trim関数に数値を渡す" do
        trim(0.0).should == trim((0.0).to_s)
    end
    it "trim関数にクラスを渡す" do
        class   Abc
            def initialize(v)
                @value = v
            end
            def to_s()
                return "Abc(#{@value})"
            end
        end
        abc = Abc.new(" test ")
        trim( abc ) == trim(abc.to_s)
    end
end

最初に空のファイルtrim.rbを作成して、実行してみます。

> spec trim.spec.rb
FFFFFFFFFFFFFFFFF
1)
NoMethodError in 'trim関数の存在 trim関数の呼び出し'
undefined method 'trim' for [RSpec example]:#
./trim.spec.rb:6:
.....

すべて失敗します。
では、なにもしないtrim関数を作成し、実行してみます。

def		trim(x)
end

> spec trim.spec.rb

..FFFFFFFFFFFFFFF
1)
'trim関数に文字列を渡す trim関数に文字列を渡す' FAILED
expected: "abc",
     got: nil (using ==)
./trim.spec.rb:18:
.....

では、インプリしてみましょう。(かなり、いい加減なコードです)

def     trim(x)
    if x == nil then
        return x
    elsif ! x.is_a? String then
        x = x.to_s
    end
    while( x[0] == 0x20 || x[0] == 0x09 )
        x = x[1..-1]
    end
    while( x[-1] == 0x20 || x[-1] == 0x09 )
        x = x[0..-2]
    end
    return x
end

> spec trim.spec.rb
.................
Finished in 0.187 seconds
17 examples, 0 failures

BDDは、仕様(コードレベルでの仕様)をコードで記述することで、リファクタリング(変更)に強い仕組みを作成する技法といえます。

今回はRubyのBDDフレームワークRSpecを紹介しました。次回は、PHPのBDDフレームワーク、PHPSpecを紹介できたらいいなと思っています。

(by yna)

一覧に戻る