ビヘイビア駆動とかよく分からないけど、とりあえずRSpec書きながらプロシージャを作ってみる #3 まとめ

正直なところ、今までノープランで書いてきたスペックファイルはあまりにあんまりなので、前回の最後に記述した通り、ここらで一回整理してみようと思います。

describeに記述したシチュエーションの振る舞いをitで確かめるのだとすれば、describeの分割単位はパラメータの入力パターン毎であり、itはそのときの振る舞いを一つ一つ定義するのが自然です。例えば「Dogクラスのインスタンスをnewしたとき」というdescribeを描いたならば、itはそのインスタンスの状態を一つ一つ確かめるものであるべきです。

このような前提でスペックファイルを書き直してみると、そこそこスッキリします。

describe "MES000000を指定し、トークンに何も入力しないとき" do
  it "「正常終了しました。」というメッセージを返す" do
  end
  it "ov_retcodeは0(正常終了)である" do
  end
end

describe "MES_ERROR_TESTを指定し、トークンに何も入力しないとき" do
  it "「GET_MESSAGE ERROR:存在しないエラーコードが指定されました。」というメッセージを返す" do
  end
  it "ov_retcodeは1(異常終了)である" do
  end
end

describe "MES000001を指定し、トークン1に「もっこす」と入力したとき" do
  it "「私の名前は もっこす です。」というメッセージを返す" do
  end
  it "ov_retcodeは0(正常終了)である" do
  end
end

上記のスペックを実行した結果は以下のようになります。

MES000000を指定し、トークンに何も入力しないとき
- 「正常終了しました。」というメッセージを返す
- ov_retcodeは0(正常終了)である

MES_ERROR_TESTを指定し、トークンに何も入力しないとき
- 「GET_MESSAGE ERROR:存在しないエラーコードが指定されました。」というメッセージを返す
- ov_retcodeは1(異常終了)である

MES000001を指定し、トークン1に「もっこす」と入力したとき
- 「私の名前は もっこす です。」というメッセージを返す
- ov_retcodeは0(正常終了)である

ちなみに、今までの実行結果は以下の通りです。

トークンを指定せずにメッセージを取得するとき
- (1) SELECT message FROM Messages WHERE message_code = 'MES000000'で「正常終了しました。」というメッセージを取得できる
- (2) 「MES000000」を指定すると「正常終了しました。」というメッセージを取得
- (3) 存在しないmessage_codeを指定するとエラーメッセージとエラーコードが返る

トークンを1つ指定してメッセージを取得するとき
- (1)「MES000001」でトークン1に「もっこす」と指定すると「私の名前は もっこす です。」と返る

Finished in 1.247753 seconds

4 examples, 0 failures

今まで書いてきたスペックがどうにもスッキリしないのは、itにまとめていろんなことを書いていたからなのでした。itで確かめる内容は1つに限定すれば綺麗にまとまります。(theyではないしねえ。。)

実際に実装してみると以下のようになります。

#
# get_message_ref1_spec.rb
# describeに引数のパターンを記述し、その振る舞いを
# itに書いていくやり方
#
require 'rubygems'
require 'spec'
require 'oci8'
#
# テスト対象のPL/SQLプロシージャ
# (こんな感じのプロシージャ作る)
# PROCEDURE get_message( iv_message_code IN Messages.message_code%TYPE,
#                        iv_token1 IN Messages.token1%TYPE,
#                iv_token2 IN Messages.token2%TYPE,
#                iv_token3 IN Messages.token3%TYPE,
#                iv_token4 IN Messages.token4%TYPE,
#                iv_token5 IN Messages.token5%TYPE,
#                ov_message OUT VARCHAR2
#                        ov_retcode OUT VARCHAR2);
#
target_plsql = <<-PLSQL
BEGIN
  get_message(:iv_message_code,
              :iv_token1,
              :iv_token2,
              :iv_token3,
              :iv_token4,
              :iv_token5,
              :ov_message,
              :ov_retcode);
END;
PLSQL
#
describe "GET_MESSAGEプロシージャで" do
  before(:all) do
    # Oracle databaseへ接続
    @conn = OCI8.new('apxe','apxe','xe')
  end
  after(:all) do
    # databaseへの接続を切断
    @conn.logoff
  end
  
  describe "MES000000を指定し、トークンに何も入力しないとき" do
    before(:all) do
      @cursor = @conn.parse(target_plsql)
      @cursor.bind_param(':iv_message_code', 'MES000000')
      @cursor.bind_param(':iv_token1', '')
      @cursor.bind_param(':iv_token2', '')
      @cursor.bind_param(':iv_token3', '')
      @cursor.bind_param(':iv_token4', '')
      @cursor.bind_param(':iv_token5', '')
      @cursor.bind_param(':ov_message', nil, String, 2000)
      @cursor.bind_param(':ov_retcode', nil, String, 10)
      @cursor.exec
    end
    it "「正常終了しました。」というメッセージを返す" do
      @cursor[':ov_message'].should == "正常終了しました。"
    end
    it "ov_retcodeは0(正常終了)である" do
      @cursor[':ov_retcode'].should == "0"
    end
  end
  
  describe "MES_ERROR_TESTを指定し、トークンに何も入力しないとき" do
    before(:all) do
      @cursor = @conn.parse(target_plsql)
      @cursor.bind_param(':iv_message_code', 'MES_ERROR_TEST')
      @cursor.bind_param(':iv_token1', '')
      @cursor.bind_param(':iv_token2', '')
      @cursor.bind_param(':iv_token3', '')
      @cursor.bind_param(':iv_token4', '')
      @cursor.bind_param(':iv_token5', '')
      @cursor.bind_param(':ov_message', nil, String, 2000)
      @cursor.bind_param(':ov_retcode', nil, String, 10)
      @cursor.exec
    end
    it "「GET_MESSAGE ERROR:存在しないエラーコードが指定されました。」というメッセージを返す" do
      @cursor[':ov_message'].should == "GET_MESSAGE ERROR:存在しないエラーコードが指定されました。"
    end
    it "ov_retcodeは1(異常終了)である" do
      @cursor[':ov_retcode'].should == "1"
    end
  end
  
  describe "MES000001を指定し、トークン1に「もっこす」と入力したとき" do
    before(:all) do
      @cursor = @conn.parse(target_plsql)
      @cursor.bind_param(':iv_message_code', 'MES000001')
      @cursor.bind_param(':iv_token1', 'もっこす')
      @cursor.bind_param(':iv_token2', '')
      @cursor.bind_param(':iv_token3', '')
      @cursor.bind_param(':iv_token4', '')
      @cursor.bind_param(':iv_token5', '')
      @cursor.bind_param(':ov_message', nil, String, 2000)
      @cursor.bind_param(':ov_retcode', nil, String, 10)
      @cursor.exec
    end
    it "「私の名前は もっこす です。」というメッセージを返す" do
      @cursor[':ov_message'].should == "私の名前は もっこす です。"
    end
    it "ov_retcodeは0(正常終了)である" do
      @cursor[':ov_retcode'].should == "0"
    end
  end

end

実行結果は以下の通り。

pasta:get_message mahm$ spec -c -fs get_message_ref1_spec.rb 

GET_MESSAGEプロシージャで MES000000を指定し、トークンに何も入力しないとき
- 「正常終了しました。」というメッセージを返す
- ov_retcodeは0(正常終了)である

GET_MESSAGEプロシージャで MES_ERROR_TESTを指定し、トークンに何も入力しないとき
- 「GET_MESSAGE ERROR:存在しないエラーコードが指定されました。」というメッセージを返す
- ov_retcodeは1(異常終了)である

GET_MESSAGEプロシージャで MES000001を指定し、トークン1に「もっこす」と入力したとき
- 「私の名前は もっこす です。」というメッセージを返す
- ov_retcodeは0(正常終了)である

Finished in 0.213314 seconds

6 examples, 0 failures

逐次的にスペックを増やしていくよりも、最初に日本語のレベルでスペックをある程度書いてから開発するのが吉ですね。スペックを実行したときに、実際の仕様書のように日本語(もしくは英語)が繋がっていると良い感じです。

仕様をコードで書いていくようなイメージなんですね。。って、だからビヘイビア駆動なのか。

Groovy + DbUnit + easybの組み合わせもアリだなぁ

今回RSpecでやってみましたが、上記の組み合わせも個人的にはアツいです。DB系のテストだとDbUnitが既に受け入れられているので、現場で使う分にも良いかも。(groovy + easybが難関すぎるが)

参考 : 2007-12-27

rubyならrailsのfixturesですが、これって単独で使うにはどうすれば良いのだろう。。railsがあんまりよく分かっていないのでアレですが、この辺は後々の宿題ですかね。。

まとめ

  • ビヘイビア駆動は仕様をコードで書くイメージ。仕様に対する実装は逐次的にやっても良いけれども、仕様を逐次的に追加するのはあまりよろしくない。(#1, #2が悪い例)
  • #1, #2は少し悪い例だったが、PL/SQLプロシージャのビヘイビア駆動開発は十分可能(だと思う)。
  • RSpecはdescribeに記述する単位とitに記述する単位を明確にするのが大事。