ビヘイビア駆動とかよく分からないけど、とりあえず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 まとめ - ランバダ

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

とりあえず簡単なプロシージャをRSpec書きながら作ってみようと思います。

どんなの作るん?

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 );

iv_message_codeにメッセージコードを入力すると、対応したメッセージ文字列が返ってくるようなプロシージャです。テーブルを以下のように設定しておき

message_code message token1 token2 token3 token4 token5
MES100000 私の名前は &NAME です。 &NAME
get_message( iv_message_code => 'MES10000',
             iv_token1 => 'もっこす',
             ov_message => lv_message )

てな感じでプロシージャを実行すれば「私の名前は もっこす です。」とlv_messageに突っ込まれるイメージです。簡単すぎてすんません。。

とりあえずRSpec書いてみるか(ノープラン)

#
# messagesテーブルに保存されたメッセージを返すPL/SQLプログラムを
# RSpec書きながら作ってみるテスト
#
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 );
#
target_plsql = <<-PLSQL
BEGIN
  get_message(:iv_message_code,
              :iv_token1,
              :iv_token2,
              :iv_token3,
              :iv_token4,
              :iv_token5,
              :ov_message);
END;
PLSQL
#
# データベース接続用変数
conn = nil
#
describe "トークンを指定せずにメッセージを取得するとき" do
	before(:all) do
		# テスト前準備 apxe/apxe@xeでデータベースへ接続
		conn = OCI8.new('apxe','apxe','xe')
	end

	after(:all) do
		# データベースからログオフ
		conn.logoff
	end
	
	it "(1) SELECT message FROM Messages WHERE message_code = 'MES000000'で「正常終了しました。」というメッセージを取得できる" do
		cursor = conn.exec("SELECT message FROM Messages WHERE message_code = 'MES000000'")
		cursor.fetch.should == ["正常終了しました。"]
	end

	it "(2) 「MES000000」を指定すると「正常終了しました。」というメッセージを取得" 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.exec
		cursor[':ov_message'].should == "正常終了しました。"
	end
end

既にデータベースにMessagesというテーブルを作成し、一つメッセージを登録してあるので(1)のテストは成功します。でも、まだプロシージャを作っていないので(2)のテストは下記のようにコケます。

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

1)
OCIError in 'トークンを指定せずにメッセージを取得するとき (2) 「MES000000」を指定すると「正常終了しました。」というメッセージを取得'
ORA-06550: 行2、列3:
PLS-00201: 識別子GET_MESSAGEを宣言してください。
ORA-06550: 行2、列3:
PL/SQL: Statement ignored
stmt.c:539:in oci8lib.so
/Library/Ruby/Site/1.8/oci8.rb:759:in `exec'
/Library/Ruby/Site/1.8/oci8.rb:142:in `do_ocicall'
/Library/Ruby/Site/1.8/oci8.rb:759:in `exec'
./get_message_spec.rb:60:

Finished in 0.134598 seconds

2 examples, 1 failure

(1)のテストはdescribeの文章的には無い方がよさげですが、そもそもデータベースにテストデータ入ってるんかいという確認が欲しかったので付けました。

そういうわけで、(2)のテストを成功させるべくプロシージャを書いてみましょう。

プロシージャを書く

テストを成功させるために最小限のプロシージャを書きます。

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 )
AS
  lv_ret_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;
END GET_MESSAGE;

そんでもってspecを実行!

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

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

Finished in 0.077543 seconds

2 examples, 0 failures

おおー、通ってる。って、そりゃそうだろ。。

ソースコードのせいでエントリが長くなってきたので、続きは次のエントリで。今度はエラーハンドリングでも実装してみますか。

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

テストデータを自由に追加削除できないようなテーブルをプログラム中で使わなければならないときのテスト

例えばシステムログが保存されているテーブルなど、プロジェクト内規約的にもライセンス的にもデータ削除しちゃいけないテーブルがあったりします。で、そのテーブルの情報を元にロジックをごにょごにょしなきゃならないときがあって、そういう場合テストデータをどうしようか、と。そんなことを考えていました。(ちなみにPL/SQLの話です)

案1) そっくりなテーブルを作って、テスト中はそっちのテーブルを参照する。

普通の方法なんですが、本番リリース時にプログラム内のテーブル参照箇所をいじらなければいけないのが何ともはやです。

案2)そういうテーブルを参照するところはViewにしておいて、ViewのFROM句をテスト中はテストテーブルを参照するようにしておく

こっちだとソースコードをいじらずに本番リリースできる分、良さげです。ただ、本番環境のViewと開発環境のViewが異なるということで、後々問題を起こしそうな気がします。バージョン管理が弱めなプロジェクトでは半年ぐらい経った後に問題が起こりそうです。

もっと良い方法ないかなぁ

スキーマに丸ごとコピーを作って……とかは一身上の都合でできません。案2で行こうかなぁ、とか考えているのですが、こういうのっていろいろ良い方法がどこかにありそうだよなぁ、とか思う今日この頃です。

ruby-oci8でPL/SQLを呼んだとき、PL/SQL例外はどう処理される?

簡単なコードで検証してみた。

-- 例外を起こすだけのプロシージャ
CREATE OR REPLACE PROCEDURE EXCP_TEST_1 AS
  test EXCEPTION;
BEGIN
  RAISE test;
EXCEPTION
  WHEN test THEN
    RAISE;
END EXCP_TEST_1;

これをsqlplus上で実行すると以下のようになる。

Connected.
SQL> begin
  2  apxe.excp_test_1();
  3  end;
  4  /
begin
*
ERROR at line 1:
ORA-06510: PL/SQL: ユーザー定義の例外が発生しましたが、処理されませんでした ORA-06512:
"APXE.EXCP_TEST_1", 行7
ORA-06512: 行2

ふむふむ。ではrubyで実行すると?

pasta:learn_rspec mahm$ irb
>> require 'oci8'
=> true
>> OCI8.new('apxe','******','xe').exec('begin apxe.excp_test_1(); end;')
OCIError: ORA-06510: PL/SQL: ユーザー定義の例外が発生しましたが、処理されませんでした
ORA-06512: "APXE.EXCP_TEST_1", 行7
ORA-06512: 行1
	from stmt.c:539:in oci8lib.so
	from /Library/Ruby/Site/1.8/oci8.rb:759:in `exec'
	from /Library/Ruby/Site/1.8/oci8.rb:142:in `do_ocicall'
	from /Library/Ruby/Site/1.8/oci8.rb:759:in `exec'
	from /Library/Ruby/Site/1.8/oci8.rb:255:in `exec'
	from (irb):2

OCIErrorが発生するんですね。じゃあちゃんとした例外を返すようにしてみよう。

-- NO_DATA_FOUND例外を起こすプロシージャ
CREATE OR REPLACE PROCEDURE EXCP_TEST_2 AS
  test EXCEPTION;
BEGIN
  RAISE test;
EXCEPTION
  WHEN test THEN
    RAISE NO_DATA_FOUND;
END EXCP_TEST_2;
>> require 'oci8'
=> true
>> OCI8.new('apxe','******','xe').exec('begin apxe.excp_test_2(); end;')
OCINoData: No Data
	from stmt.c:539:in oci8lib.so
	from /Library/Ruby/Site/1.8/oci8.rb:759:in `exec'
	from /Library/Ruby/Site/1.8/oci8.rb:142:in `do_ocicall'
	from /Library/Ruby/Site/1.8/oci8.rb:759:in `exec'
	from /Library/Ruby/Site/1.8/oci8.rb:255:in `exec'
	from (irb):3
>> 

OCINoDataとな? ということはRuby側で例外処理したい場合はこんな風に書けば良いのかな。

pasta:learn_rspec mahm$ irb
>> require 'oci8'
=> true
>> begin
?> OCI8.new('apxe','apxe','xe').exec('begin apxe.excp_test_2(); end;')
>> rescue OCINoData
>> puts "OCINoData was catched!"
>> end
OCINoData was catched!
=> nil

実際にはエラー内容をもっと詳細にロギングしたり、プロジェクト内で定義したエラーコードを返したりしなければならないのでしょう。raise_application_errorを使っていくのも手か。

Oracle PL/SQL Best PracticeのChapter6ではQuest Error Managerを使用する例が載っていたりと、Error Handling周りの話はなかなか興味深い話で一杯です。ちゃんと考えなきゃね。

Oracle PL/SQL Best Practices: Write the Best PL/SQL Code of Your Life

Oracle PL/SQL Best Practices: Write the Best PL/SQL Code of Your Life

RubyからOracleにちょっかいを出すためにruby-oci8をmake install

何故かsudo gem install ruby-oci8では上手く行かなかったので、RubyForgeからファイル一式をダウンロードしてmakeからやることにします。

ruby setup.rb config 
checking for load library path... 
  DYLD_LIBRARY_PATH... 
    checking /u01/app/oracle/product/10.2.0/client_1/lib... yes
  /u01/app/oracle/product/10.2.0/client_1/lib/libclntsh.dylib.10.1 looks like a full client.
checking for cc... ok
checking for gcc... yes
checking for LP64... no
checking for ruby header... ok
Get the version of Oracle from SQL*Plus... 1020
try  -I/u01/app/oracle/product/10.2.0/client_1/rdbms/public
checking for oci.h... yes
checking for OCIInitialize() in oci.h... no
Running make for $ORACLE_HOME/rdbms/demo/demo_rdbms.mk (build) ...OK
checking for OCIInitialize() in oci.h... no
---------------------------------------------------
error messages:
cannot compile OCI
---------------------------------------------------
See:
 * http://ruby-oci8.rubyforge.org/ja/HowToInstall.html
 * http://ruby-oci8.rubyforge.org/ja/ReportInstallProblem.html

しかし初回は上手くいかず。どうやら-arch i386オプションが必要らしい。オプションを加えてやると上手くいった。

pasta:ruby-oci8-1.0.4 mahm$ export ARCHFLAGS="-arch i386"
pasta:ruby-oci8-1.0.4 mahm$ make
ruby setup.rb config 
checking for load library path... 
  DYLD_LIBRARY_PATH... 
    checking /u01/app/oracle/product/10.2.0/client_1/lib... yes
  /u01/app/oracle/product/10.2.0/client_1/lib/libclntsh.dylib.10.1 looks like a full client.
checking for cc... ok
checking for gcc... yes
checking for LP64... no
checking for ruby header... ok
Get the version of Oracle from SQL*Plus... 1020
try  -I/u01/app/oracle/product/10.2.0/client_1/rdbms/public
checking for oci.h... yes
checking for OCIInitialize() in oci.h... yes
checking for OCIEnvCreate()... yes
checking for OCITerminate()... yes
checking for OCILobOpen()... yes
checking for OCILobClose()... yes
checking for OCILobCreateTemporary()... yes
checking for OCILobGetChunkSize()... yes
checking for OCILobLocatorAssign()... yes
checking for OCIRowidToChar()... yes
creating ../../lib/oci8.rb from /Users/mahm/Downloads/ruby-oci8-1.0.4/ext/oci8/../../lib/oci8.rb.in
(以下略)

RubyからOracleへの疎通確認。

pasta:ruby-oci8-1.0.4 mahm$ irb
>> require 'oci8'
=> true
>> OCI8.new('apxe','******','xe').exec('select SYSDATE from dual'){|out| puts out}
2009/03/06 17:39:13
=> 1
>> 

これで土台が整ったことに。今から用事があって外出するので、続きはまた後で。。

まずはMacOSXにOracle databse10g Client for IntelMacをインストール

http://www.oracle.com/technology/software/products/database/oracle10g/htdocs/10204macsoft.html

こちらから10g Clientをダウンロードします。(何故か日本語のサイトの方にはIntelMac版がない。。)

FullPackageとInstantどちらでもいいっぽいのですが、私はFullの方をDLしました。で、中に入っているohomeの中身を下記ディレクトリへ格納。

/u01/app/oracle/product/10.2.0/client_1

ユーザーのhomeディレクトリ直下の.bash_profile(なければ作成)に以下のように環境変数を設定。

export ORACLE_BASE=/u01/app/oracle
export ORACLE_HOME=$ORACLE_BASE/product/10.2.0/client_1
export DYLD_LIBRARY_PATH=$ORACLE_HOME/lib:$DYLD_LIBRARY_PATH
export PATH=$ORACLE_HOME/bin:$PATH
export NLS_LANG=japanese_japan.UTF8

source .bash_profileで環境変数を適用します。次にtnsnames.oraのセッティング。下記ディレクトリへ格納します。

/u01/app/oracle/product/10.2.0/client_1/network/admin
# tnsnamens.ora
XE =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.11.100)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = xe)
    )
  )

sqlplusの疎通確認。

pasta:admin mahm$ sqlplus /nolog

SQL*Plus: Release 10.2.0.4.0 - Production on 金 3月 6 17:31:48 2009

Copyright (c) 1982, 2007, Oracle.  All Rights Reserved.

SQL> conn apxe/******@xe
Connected.
SQL> 

これでOK。次にRubyからOracleへちょっかいを出すためにruby-oci8をセットアップします。

Oracle PL/SQLのUnit TestingをRSpecで行う試み

PL/SQLのUnit Testing FrameworkにutPLSQLというものがあるけれども、正直使いやすいとは言えないです。ドキュメントを見ているだけで少しゲンナリする。

PL/SQLプロシージャをCallableStatementで呼んでJUnitでテストするという試みはあるらしいけれども、Javaでやるのも仕事なら良いけど勉強がてらだと何だか重いなぁ、なんて思い、いっそRubyでやってみることにします。

じゃあRubyのTestUnit使おうか?というのも、何だか面白くない。どうせならビヘイビア駆動開発なんつってRSpecで行こう。

そういう訳でRSpecPL/SQLパッケージをビヘイビア駆動開発してみようと思います。

# 平日のこんな時間に記事を書いていますが、ニートになった訳ではないです。。一応、長期休暇中です。