OracleストアドプロシージャのTDD

最近のプロジェクトの1つで、深刻な問題に直面しました。 私たちが開発していたWebアプリケーションは、金融機関の内部データベースを使用することでした。 セキュリティ上の理由から、アクセスは非常に制限されていました。ストアドプロシージャを使用して変更を行う必要があり、データの読み取りはビューを使用してのみ可能でした。 そのため、アプリケーションは複雑なデータ操作を実行する必要がありましたが、その構造についてはわかりません。 私たちの主な問題は、アプリケーションが自動化されたテストがない大規模で複雑な手順に依存するようになったことです。







少しグーグルで、標準のOracle SQL Developerツールキット[1]に自動テストを作成する機能があることを発見しました。 私たちはすぐにそれを研究し始めました。 最も複雑な手順のテストは作成後に作成する必要がありましたが、それでもこのツールキットはいくつかのエラーを排除するのに役立ち、機能の拡張とリファクタリングのプロセスを大いに促進しました。 以下では、TDDを使用してストアドプロシージャを構築する例を示します。また、ツールを使用した経験を共有します。







使用例



顧客がSMSメッセージを送信できる既存のアプリケーションを持っているとします。 別のチームは、既存のアプリケーションと並行して動作する必要のある新しいアプリケーションを開発しています。そのため、ビジネスロジックの共通の場所があると便利です。







データ構造



アプリケーションは次のデータ構造を使用します。







CREATE TABLE CLIENTS( ID NUMBER GENERATED BY DEFAULT AS IDENTITY NOT NULL, NAME NVARCHAR2(255) NOT NULL, BALANCE NUMBER(*,2) DEFAULT 0 NOT NULL, IS_ACTIVE NUMBER(1) DEFAULT 0 NOT NULL, IS_PREPAY NUMBER(1) DEFAULT 0 NOT NULL ); CREATE TABLE MESSAGE_QUEUE( ID NUMBER GENERATED BY DEFAULT AS IDENTITY NOT NULL, ID_CLIENT NUMBER NOT NULL, SENDER VARCHAR2(20), RECIPIENT VARCHAR(20), MESSAGE NVARCHAR2(255) NOT NULL, QUEUED_ON TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, SEND_ON TIMESTAMP WITH TIME ZONE NULL, SENT_ON TIMESTAMP WITH TIME ZONE NULL ); CREATE TABLE TRANSACTIONS( ID NUMBER GENERATED BY DEFAULT AS IDENTITY NOT NULL, ID_CLIENT NUMBER NOT NULL, VALUE NUMBER(*,2) NOT NULL, TRANSACTION_TIME TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP );
      
      





簡潔にするために、主キーと外部キーの定義は省略されています。







環境設定



SQL Developerの単体テストでは、データベースを使用して、テスト、その設定、ライブラリ、および実行結果を保存します。 これらの目的のために、テスト用のユーザーを作成してから、データベースにリポジトリを作成することを強くお勧めします。 このプロセスの詳細は、ユニットテストのドキュメント[2]で説明されています。







Oracleテストの用語



Oracleが使用するテスト用語は、xUnit [3]の一般に受け入れられている用語とは多少異なります。







xUnit SQL Developer SQL Developerに関するコメント
テストスイート テストスイート 他のテストスイートやスクリプトを含めることができます
テストシナリオ テスト テストできる関数またはプロシージャは1つだけです。
テスト テスト実装
コンテキスト設定(セットアップ) 起動プロセス テストおよびテストスイートレベルで利用可能
コンテキストのリセット(分解) 分解プロセス 上記参照


さらに本文では、ロシア語版のxUnit用語を使用します。







驚き



アプリケーションで作業するとき、期待どおりに動作しない場合があることがわかりました。









テスト開発



開始する前に、空のプロシージャを作成する必要があります。作成しないと、テストを作成できません。 引数リストは空のままにできますが、その必要はありません。







最初は、メッセージを送信するために、クライアント識別子、送信者、受信者、およびメッセージ自体が必要であると想定できます。 また、たとえば、出力パラメータを通じて結果を通知する必要があります。 プロシージャを作成するためのダイアログを使用すると、非常に適切な定義を取得できます。







 CREATE OR REPLACE PROCEDURE QUEUE_MESSAGE( V_ID_CLIENT IN NUMBER, V_SENDER IN VARCHAR2, V_RECIPIENT IN VARCHAR2, V_MESSAGE IN NVARCHAR2, V_IS_QUEUED OUT NUMBER) AS BEGIN NULL; END QUEUE_MESSAGE;
      
      





Oracleの場合、あいまいな場合には有名なDBMSがフィールドを優先して紛争を解決するため、名前がフィールドの名前と一致する可能性のある変数にプレフィックスを設定することは理にかなっています。 また、混乱を避けるために、例外なくすべての変数にプレフィックスを付ける方が簡単です。







ご注意



プロシージャパラメータが変更されている場合、[ テスト同期... ]コンテキストメニュー項目をクリックして、各テストスクリプトを手動で更新する必要があります


最初のシナリオ



この例を単純化するために、1つのメッセージのコストが0.03



お金だと仮定します。 そして、奇妙なことに、ガーキンはシナリオの説明に非常に適しています。







 :  -  :    :   ,       ,   ,    .
      
      





テストを作成する最速の方法は、オブジェクトツリーのプロシージャを右クリックして、[ ユニットテスト作成... ]メニュー項目を選択することです。 表示されるウィンドウで、すぐに[ 完了 ]ボタンをクリックできます。 単一のテストを含むQUEUE_MESSAGEスクリプトがユニットテストパネルに表示されます。







コンテキスト設定


まず、データベースに必要なデータを入力する必要があります。 私たちにとって最も便利なのは、PL / SQLモードを使用してコンテキストを構成およびリセットすることでした。 ただし、オプションはいずれも、ライブラリに公開することで簡単に再利用できます。 ライブラリから既存のステップをコピーするには、ドロップダウンリストから選択して、[ コピー ]ボタンをクリックします。 変更せずに、[ コピー ]ボタンの代わりに使用する場合は、[ 購読 ]チェックボックスをクリックする必要があります。







ご注意



既存のデータベースをテストに使用するというアイデアは魅力的に思えるかもしれません。 設定にデータを保存し、コンテキストをリセットすると復元されたように見えます...しかし、いずれかの段階でテストの実行中に予期しないエラーが発生した場合、データベースはエラー中の形式で表示されることに注意してください、そしてコンテキストのリセットは実行されません。 したがって、クリーンなデータベースを使用するのが最善です。このデータベースは、構造やデータが破損した場合に怖くなく、簡単に完全に再作成できます。


コンテキストを調整するために空のデータベースで作業していると仮定すると、後払いの顧客レコードを1回挿入するだけで済みます。 Post-paid clientと呼ばれるライブラリにすぐに保存できます







コンテキストをリセット


テストを再実行するには、追加したデータをクリアする必要があります。 ただし、この場合、テストの影響を受けるすべてのテーブルを単純にクリアできます。 このステップは、将来使用するためにライブラリに保存する必要もあります。







挑戦する


テスト自体の実行は、ストアドプロシージャのパラメーターを設定することで決定されます。 ここで、検証のために出力パラメータの値を設定することもできます。 [ テスト結果]チェックボックスを使用して、出力パラメーターのチェックを無効にできます。 テーブルで動的に指定されたパラメーターを参照します。







ご注意



外観上、テーブル内でマウスを使用してパラメータを設定することは非常に便利に思えるかもしれませんが、このテーブルはコピーできないことに留意する必要があります。 これは、特に新しいテストが現在のテストと1つの値だけ異なる場合に、次のテストを作成するためにすべての引数を手動で再設定する必要があるため、多数の引数を持つプロシージャにとって特に重要です。 テーブルとは異なり、動的クエリ(動的値クエリ)をライブラリに保存してから、再利用またはコピーできます。


上で示したように、動的クエリの方が便利です。 また、リクエストの出力パラメータの名前は、名前の最後に$



記号を追加する必要があることに注意してください。







 select 1 as V_ID_CLIENT, '79052222222' as V_SENDER, '79161111111' as V_RECIPIENT, ' !' AS V_MESSAGE, 1 as V_IS_QUEUED$ from DUAL
      
      





ご注意



動的クエリモードからテーブルモードに戻るには、動的クエリの値を完全にクリアする必要があります。


出力パラメーターのチェックを指定したので、スクリプトを既に実行して失敗を確認できます。 すべてが正しく行われると、システムはエラーを報告するはずです。 この段階で他の障害が発生した場合、設定が正しくありません。







テストを落ち着かせる最も簡単な方法は、プロシージャの本文の出力パラメータに1



を入力することSELECT 1 INTO IS_QUEUED FROM DUAL;









声明


テストは再び緑色になりますが、必要な条件をすべて確認していません。 それらは、同じシナリオの他のテストで確認できます。 新しいテストを作成する前に、既存のテストの名前をデフォルトの「テスト実装1」から「ポジティブな結果」に、シナリオ全体を「アクティブな後払いクライアントがメッセージを送信する」に変更することをお勧めします。







重要です



各テストはトランザクション内で実行されると簡単に推測できます。 しかし、実際にはそうではありませんでした。 予期しないエラーが発生した場合、データベースは未定義の状態にある可能性があります。 予期されるエラー。この動作は適用されません。


次のテストは、微妙なフィードバックのために別のテストに配置されますが、新しいテストはそれぞれコンテキストのセットアップとリセットに時間を費やし、各テストの失敗にはその原因に関する明確なメッセージが提供されることを覚えておく価値があります。 このシナリオでは異なるテストのテストを分離し、次のシナリオではすべてのチェックを1つのテストに結合します。







ご注意



SQL Developerでは、2つのテストを同時に表示することはできません。 ツリー内の別のテストに移動すると、現在のテストは同じパネル内の新しいテストに置き換えられます。 さらに、このパネルを2つの独立したスクロール可能な領域に分割することはできません。 ただし、2つのパネル間の迅速な移行のために、テストウィンドウと並行してプロシージャのソースコードを開くと非常に便利です。


次のテストは、メッセージがキューに入れられたことを確認することです。 コンテキストの設定とリセットはすでに指定されているため、ライブラリからの動的クエリを使用し、承認チェックを設定する必要があります。 動的クエリをコピーした後、すでに検証された出力パラメーターをチェックすることは役に立たないように見える場合があります。 テスト結果チェックボックスをリセットできます。 ただし、この状態でテストを実行すると、テストの1つが無視されることがわかります。 個人的には、無視されたテストは未完成の作業の象徴なので、チェックボックスを配置する必要があります。







申し立てを確認する方法はいくつかあります。 リストの最初の項目はブール関数です。 ブール関数を作成するとき、ダイアログは完全に適切なテンプレートを提供します:







 -- Please replace this code with either a boolean -- expression like this: -- RETURN FALSE; -- or else a code block which returns a boolean value -- similar to the following: DECLARE l_count NUMBER; BEGIN SELECT count(*) INTO l_count FROM dual; IF l_count <> 0 THEN RETURN TRUE; ELSE RETURN FALSE; END IF; END;
      
      





検証のために、このテンプレートを使用して、 dual



MESSAGE_QUEUE



に置き換えてから、必要なフィルターを適用します。 精度を高めるには、条件をl_count <> 0



からl_count = 1



に変更する必要もあります。 その後、将来の使用のために関数をライブラリに安全に保存できます。







ご注意



ライブラリ内のすべてのエントリは、タイプに従って保存されます。 これは、たとえば、承認の検証などを将来使用する必要がある場合、名前だけでなくタイプも覚えておく必要があることを意味します。 これは、特に大規模なプロジェクトでは、非常に短時間で非常に不便になります。


テストを実行すると、エラーが表示されるはずです。 非常に簡単に修正できます。







  INSERT INTO MESSAGE_QUEUE(ID_CLIENT, SENDER, RECIPIENT, MESSAGE) VALUES(V_ID_CLIENT, V_SENDER, V_RECIPIENT, V_MESSAGE);
      
      





これで、すべてのテストが成功したことを確認できます。







ご注意



テストで作業する場合、リポジトリはブロックされるため、作業の最後にSQL Developerを閉じるか、リポジトリを閉じる必要があります(リポジトリの選択解除)。


最後に、トランザクションレコードを確認します。 これを行うには、次の種類の検証-クエリ結果の比較を選択します。 名前が示すとおり、非常に単純に機能します。結果が一致する2つのクエリを指定する必要があります。 正確な日付と時刻を見つけることは不可能であるため、10秒以内に任意の値で満足することができます。







 -- Source query SELECT 1 AS ID_CLIENT, 0.03 AS SUM_VALUE FROM DUAL -- Target query SELECT ID_CLIENT, SUM(VALUE) FROM TRANSACTIONS WHERE TRANSACTION_TIME BETWEEN CURRENT_TIMESTAMP AND (CURRENT_TIMESTAMP - 1/24/6) GROUP BY ID_CLIENT;
      
      





テストを実行した後、あいまいなValidation : Compare query results check found differences



エラー、 Validation : Compare query results check found differences



表示されますValidation : Compare query results check found differences



。 「1つの最近のトランザクション」は、ライブラリ内の最後のチェックの名前です。 そして、このオプションはすでに価値のあるツールですが、結果の違いを正確に示すことができれば素晴らしいでしょう。







手順に必要な機能を追加します。







  INSERT INTO TRANSACTIONS(ID_CLIENT, VALUE) VALUES(V_ID_CLIENT, 0.03);
      
      





デバッグ


次のテスト実行後、エラーが消えていないことが突然判明しました。 おそらく上記のコードですでにエラーに気づいているでしょうが、実際の状況では、状況ははるかに複雑です。 ツールには違いが表示されないため、理由を手動で見つける必要があります。 残念ながら、SQL Developerのデバッグ機能はここでは役に立ちません。 つまり、リセットを実行せずにテストを実行する必要があります。 これを行うには、別のスクリプト-デバッグを作成できます。 より正確には、2つ:1つ-リセットなしで、非動作テストと同じ動的要求を使用して-何が起こっているかを把握するため。 2番目-コンテキストを設定せずにリセットを使用して-最初を削除するため。







最初のスクリプトを開始した後、テーブルの内容を確認し、検証要求を確認できます。 これで、問題が検証リクエストに正確にあったことが明らかになりました。 データを消去するために2番目のスクリプトを実行することを忘れないでください。テスト条件を修正し、2回目の実行を手配します。 これですべてが整いました。 デバッグスクリプトは将来のために残しておくことができ、最初に完成したスクリプトは新しいテストスイートに配置できます。







2番目のシナリオ



メッセージを正常に送信するためのスクリプトができたので、送信に失敗した場合のスクリプトを試すことができます。 たとえば、後払いの顧客が非アクティブの場合:







 :  -  :    :   ,    ,     .
      
      





新しいスクリプトを作成する必要があります。 また、コンテキスト設定と動的クエリを微調整する必要がありますが、これは最初から新しいものを作成するよりもはるかに簡単です。







コンテキストを設定するには、PL / SQLステップ「Active post-pay client」をコピーします。ここで、 1



0



置き換え、「Inactive post-pay client」というライブラリに公開します。 動的リクエストに対しても同じことを繰り返し、新しいリクエストに「未送信メッセージ」という名前を付けます。 コンテキストをリセットするには、既存の手順を使用します。







実行後、テストでエラーが表示されます。 修正は非常に簡単です。 SELECT 1 INTO V_IS_QUEUED FROM DUAL



SELECT IS_ACTIVE INTO V_IS_QUEUED FROM CLIENTS WHERE ID=V_ID_CLIENT



-すべてが再び機能します。







次に、トランザクションが保存されていないことを確認する必要があります。 これを行うには、次のタイプの検証-テーブルの比較を使用します。 最初は比較するものがないように見えるかもしれませんが、コンテキストを設定する際に、既存のテーブルを一時的なテーブルにコピーする機会があります。 これは私たちにぴったりです。トランザクションを一時テーブルにコピーし、プロシージャを呼び出した後、結果を比較できます。 主なことは、コンテキストをリセットするときにこのテーブルを削除することを忘れないことです。 復元、削除、および単純な削除の2つのオプションがあります。 復元するものがないため、2番目のオプションを選択します。 クエリ比較の場合のように、唯一のフィードバックオプションは一致があるかどうかです。







テストの実行後にエラーを賞賛した後、解決策について考えることができます。 たとえば、新たに更新されたV_IS_QUEUEDを使用して、条件で挿入をラップできます。







 IF V_IS_QUEUED = 1 THEN INSERT INTO TRANSACTIONS (ID_CLIENT, VALUE) VALUES (V_ID_CLIENT, 0.03); END IF;
      
      





プロシージャをコンパイルし、テストを実行します-すべてが機能します。







結論として、メッセージキューが変更されていないことを確認する必要があります。 また、トランザクションボックスの横の条件内にメッセージボックスをすぐに置くために手がかゆみを伴いますが、これは規律への励ましになります。 したがって、最初にこのステートメントの追加チェックを作成します。 次のタイプのチェックは、行を返さないクエリです。 各テスト後にすべてのデータを完全にクリアするため、このようなクエリとしてSELECT * FROM MESSAGE_QUEUE



を指定するだけで十分です。







テスト実行ではエラーが表示されますが、条件内に挿入を配置することで簡単に除去できます。 これで、2番目のシナリオは終了です。







結論



SQL Developerを使用して、TDDメソッドを使用してストアドプロシージャを開発できます。 多数の欠点にもかかわらず、このパッケージは、ストアドプロシージャを開発するためのプラットフォームを提供し、開発者が既存のプロシージャの機能を簡単かつ自信を持って変更および拡張できるようにします。







残念ながら、テストリポジトリはOracle DBMSでのみ作成できます。 さらに、PostgreSQLやMySQLなどのサードパーティDBMSをテスト用のデータベースとして使用しようとすると、テストサブシステムがクラッシュします。 また、継続的統合システムでSQL Developerを使用すると多くの問題が発生することが判明しましたが、これは別の話です。










[1] Oracle SQL Developer- http://www.oracle.com/technetwork/developer-tools/sql-developer/overview/index.html

[2] Oracle SQL Developerヘルプ:ユニットテストリポジトリ-https: //docs.oracle.com/cd/E15846_01/doc.21/e15222/unit_testing.htm#RPTUG45067

[3] xUnit- https://ru.wikipedia.org/wiki/XUnit








All Articles