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

指定されたmessage_codeが見つからない場合にエラーメッセージを出してみる

前回のソースにエラー処理も足そうよ、という訳で以下のようにスペックを書きます。

  it "(3) 存在しないmessage_codeを指定するとエラーメッセージとエラーコードが返る" 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
    cursor[':ov_message'].should == "GET_MESSAGE ERROR:存在しないエラーコードが指定されました。"
    cursor[':ov_retcode'].should == "1"
  end

ついカッとなって引数を増やしてしまったので、(2)のケースも修正する必要がありやんす。。

そしてできたのが以下のPL/SQLコード。

CREATE OR REPLACE
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)
AS
  cv_program_name CONSTANT VARCHAR2(100) := 'GET_MESSAGE';
  lv_ret_message VARCHAR2(2000);
  lv_error_message VARCHAR2(2000);
BEGIN
  SELECT  message INTO lv_ret_message
  FROM    Messages
  WHERE   message_code = iv_message_code;
  
  -- OUT変数にMessagesから取得した文字列を代入
  ov_message := lv_ret_message;
  -- 正常終了
  ov_retcode := '0';
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    lv_error_message := cv_program_name || ' ERROR:存在しないエラーコードが指定されました。';
    ov_message := lv_error_message;
    ov_retcode := '1';
END GET_MESSAGE;
pasta:get_message mahm$ spec -c -fs get_message_spec.rb 

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

Finished in 0.158438 seconds

3 examples, 0 failures

テストも通りました、と。

そろそろ本題の「私の名前は もっこす です。」を実装する

そろそろ本題の機能(もっこす)を実装してみましょう。例のごとく先にスペックを記述します。前回までの機能とは違うので新しくdescribeを記述しますよ。

describe "トークンを1つ指定してメッセージを取得するとき" do
  before(:all) do
    # テスト前準備 apxe/apxe@xeでデータベースへ接続
    conn = OCI8.new('apxe','apxe','xe')
  end
  
  after(:all) do
    # データベースからログオフ
    conn.logoff
  end

  it "(1)「MES000001」でトークン1に「もっこす」と指定すると「私の名前は もっこす です。」と返る" 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
    cursor[':ov_message'].should == "私の名前は もっこす です。"
    cursor[':ov_retcode'].should == "0"
  end
end

とりあえずエラーを出してみる。

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

1)
'トークンを1つ指定してメッセージを取得するとき (1)「MES000001」でトークン1に「もっこす」と指定すると「私の名前は もっこす です。」と返る' FAILED
expected: "私の名前は もっこす です。",
     got: "GET_MESSAGE ERROR:存在しないエラーコードが指定されました。" (using ==)
./get_message_spec.rb:105:

前に実装したエラーメッセージが表示されてて良い感じですね。

message_code message token1 token2 token3 token4 token5
MES000001 私の名前は &NAME です。 &NAME

上記のような形でMessagesにデータ登録して実装してみましょう。

INSERT INTO "APXE"."MESSAGES" (ID, MESSAGE_CODE, MESSAGE, TOKEN1) VALUES ('2', 'MES000001', '私の名前は &NAME です。', '&NAME')

コミットは成功しました

そしてエラー処理とかは考えずに、指定箇所が置換されるだけのソースを書いてみます。

create or replace
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)
AS
  cv_program_name CONSTANT VARCHAR2(100) := 'GET_MESSAGE';
  rec_message Messages%ROWTYPE;
  lv_ret_message VARCHAR2(2000);
  lv_error_message VARCHAR2(2000);
BEGIN
  SELECT  * INTO rec_message
  FROM    Messages
  WHERE   message_code = iv_message_code;
  
  lv_ret_message := rec_message.message;
  
  -- TOKEN1が指定されている場合に埋め込み処理
  IF iv_token1 IS NOT NULL THEN
    -- レコードに設定されているTOKEN1の値がmessage中に設定されているか?
    IF REGEXP_LIKE( rec_message.message, rec_message.token1, 'i') = TRUE THEN
      lv_ret_message := REGEXP_REPLACE( lv_ret_message, rec_message.token1, iv_token1 );
    END IF;
  END IF;
  
  -- OUT変数にMessagesから取得した文字列を代入
  ov_message := lv_ret_message;
  -- 正常終了
  ov_retcode := '0';
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    lv_error_message := cv_program_name || ' ERROR:存在しないエラーコードが指定されました。';
    ov_message := lv_error_message;
    ov_retcode := '1';
END GET_MESSAGE;

コンパイルが通ったのでスペック実行!

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

トークンを指定せずにメッセージを取得するとき
- (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

軽やかに通りました。

ここまでRSpecを使ってきて思うこと

やはりスペックファイルの書き方の方針に悩みます。。

ノープランで書いてきましたが、どんな単位でdescribeを書いていくべきか、その中のitはどのように区分けするか、この辺は方針として持っておかないと混沌としていきますね。itの書き方の整理の付け方も難しいです。

そもそもクラスの振る舞いを記述するためのものであってPL/SQLのプロシージャの振る舞いを記述する想定はないのですから、ノープランでやれば混沌とするのがむしろ当たり前ではありますが。。

次回はこの混沌としたSpecファイルを少し整理してみようと思います。

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