2013/12/16

SQLの単体テストとpgtap

このエントリーをはてなブックマークに追加

本記事は2013年のPostgreSQL Advent Calendarの 12/16の記事です。

近頃、SQLの単体テストをやらないといけないなーということが、仕事であったので、本日は、単体テストを行う上での考え方と一つのやり方の例を紹介したいと思います。

●最初に明確にすること

当たり前の話になりますが、単体テストに限らず、試験を行うときには、「何を確認するか」、「どの程度試験を実施するか」を明確にする必要があります。
重要なことは、その明確にしたことを「テストの受入者」と合意することです。
この話をしだすと、このブログだけでは語りきれなくなるので、これ以上は割愛します。

●試験を行う上で、ツールを使うことの是非

試験を行う上で、「ツールを使って試験をするべきだ」、「いや、使う必要はない」というようなやりとりを耳にすることがよくあります。
このときによくないのは、「なんとなくツールを使うとよさそうだから」、「今までのやり方で慣れているから」、「ツール使うと学習コストが、かかりそうだから」などと、あまり先のことを考えずに、適当にやり方を決めてしまうことです。

ツールを使う、使わないはあくまでも手段であり、目的は別にあります。
ツールのメリット、デメリットを考えた上で、決めるべきでしょう。

私が考えるツールを使用するメリット、デメリットは以下の通りです。

□ 単体テストツールを使うメリット

○再テストの容易さ
 - テストプログラムを再実行することで、繰り返しテストを行うことができる

○テストが楽に
 - 試験結果を自動的に判定するため、試験結果を確認する際に誤りが発生しにくい
 - 証跡取得の漏れを防ぐことができる

○汎用性
 - 一度やり方を確立すれば、他のプロジェクトにも展開可能

□ 単体テストツールを使うデメリット

○工数が大きくなる可能性
 - 大量のテストプログラムを書くことになり、試験準備に大きい工数がかかる場合がある
 - テストプログラム自体が正しいか、確認が必要で、長大なプログラムの場合、さらなる工数が発生する可能性がある

私が単体テストを担当するプロジェクトでは、定期的にアプリケーションのメンテナンスが入ったり、仕様の変更が入ったりする可能性がありました。
そのため、繰り返しテストを行う可能性があり、ツールを使って、試験を自動化したいなーと考えました。

※ ツールを使うと速く試験を行うことができるという声を耳にすることがよくあります。個人的な意見ですが、ツールを使うと早くなるというのはケースバイケースだと思います。特に、繰り返しテストを実施せず、一回のみの時間で比較した場合はツールの方が早くならないケースも多いと思います。早くなる、ならないは、どの程度のテストプログラムをどのように書くのかに依る所が大きいのではないかと思います(当然、速く正確に書くことを意識するのは重要です)。


●ツール選択

SQLの単体テストを行うにあたっては、大きく以下の2つの種類のツールに分かれるのではないかと思います。

  1. SQLのみを確認するツール:pgtapなど
  2. アプリケーションからSQLを実行した結果を確認する:xUnit(jUnit, dbUnitなど)
ここで重要になるのは、「何を確認するか」です。
JavaからSQLが実行されるアプリケーションで、そのJavaで実行される処理結果を確認したいのに、1のツールを使用してもテストの意味は半減してしまうでしょう。

私のケースでは、アプリケーションとSQLで責任分解点を設けていたため、1のツールを使用することにしました。

●pgtapとは

pgtapでは、SQLの確認だけではなく、スキーマ、プロシージャのテストも行うことができます。pgtapについては、昨年のPostgreSQL Advent Calendar 2012(全部俺)で、永安さんが解説しています。基本的なことはこちらで書かれているので、ここでは永安さんのブログで解説されていない注意点などを意識して、記述したいと思います。

□ 対応バージョンなどについて

私の環境では、pgtap最新バージョンの0.93を使用しています。
2013年12月16日現在、PostgreSQL9.2系までは対応していますが、9.3系には未対応のようです。

□ 使用するための準備

pgtapをインストールするには、依存パッケージとして、TAP::Parser::SourceHandler::pgTAPのインストールが必要です。
# tar xvfz TAP-Parser-SourceHandler-pgTAP-3.29.tar.gz
# cd TAP-Parser-SourceHandler-pgTAP-3.29
# perl Build.PL
# ./Build
# ./Build install

こちらのインストールが完了してから、pgtapをインストールします。

# unzip pgtap-0.93.0.zip
# cd pgtap-0.93.0
# make USE_PGXS=1
# make USE_PGXS=1 install

そして、対象となるデータベースにログインし、pgtapの拡張登録をします。

$ psql -d [データベース名] -c "CREATE EXTENSION pgtap"

□ 使用方法

ここでは、SQL結果を確認するための方法について記述します。
pgtapで確認するのは、「実行したSQLの結果が、どの"型"でどういう"値"を返すか」です。これは、後述の注意点でも書きますが、少しやっかいな面があります。

以下、私の使っているサンプルDBを使ってpgtapを試します。
# 今回使用したDBの情報、テーブルはこちらからダウンロードできます。

pgtapで試験を開始するには、トランザクションを開始しする必要があります。
hr=# BEGIN;
BEGIN
続いて、plan関数を使用して、実施するテスト数を決めます。
hr=# SELECT plan(1);
 plan
------
 1..1
(1 行)
requets_eq関数を使用して、下記のように試験対象のSQL、期待する結果を記述して、想定通りの結果が返ってくるかを確認します。
hr=# SELECT results_eq(
hr(#   'SELECT
hr'#      first_name,
hr'#      last_name
hr'#    FROM
hr'#      employees emp,
hr'#      departments dept
hr'#    WHERE
hr'#      emp.department_id = dept.department_id AND
hr'#      dept.department_id = 60;
hr'#   ',
hr(#     $$
hr$#       VALUES
hr$#         ('Alexander'::varchar, 'Hunold'::varchar),
hr$#         ('Bruce'::varchar, 'Ernst'::varchar),
hr$#         ('David'::varchar, 'Austin'::varchar),
hr$#         ('Valli'::varchar, 'Pataballa'::varchar),
hr$#         ('Diana'::varchar, 'Lorentz'::varchar)
hr$#     $$);
 results_eq
------------
 ok 1
(1 行)
okが返ってくれば、試験成功です。 すべてのテストが終わったら、下記のようにトランザクションをロールバックさせて、テストを終了させます。
hr=# ABORT;
ROLLBACK

□pgtapの注意点

○結果確認の厳密性
pgtapの注意点として、「結果確認がものすごく厳密に行われる」ということがあげられます。
pgtapで確認するのは、「実行したSQLの結果が、どの"型"でどういう"値"を返すか」なので、上記で示した通り、SQLで確認する結果は、SQLで確認する項目をキャストしてあげる必要があります。
下記のようにキャストをしないで実行すると、NGとなります。

hr=# SELECT results_eq(
hr(#   'SELECT
hr'#      first_name,
hr'#      last_name
hr'#    FROM
hr'#      employees emp,
hr'#      departments dept
hr'#    WHERE
hr'#      emp.department_id = dept.department_id AND
hr'#      dept.department_id = 60;
hr'#   ',
hr(#     $$
hr$#       VALUES
hr$#         ('Alexander', 'Hunold'),
hr$#         ('Bruce', 'Ernst'),
hr$#         ('David', 'Austin'),
hr$#         ('Valli', 'Pataballa'),
hr$#         ('Diana', 'Lorentz')
hr$#     $$);
                           results_eq                             
-------------------------------------------------------------------
 not ok 1                                                           +
 # Failed test 1                                                  +
 # Number of columns or their types differ between the queries
(1 row)
このことは、pgtapのMLなどでも質問にあげられていたこともあるようで、
"You always have to compare objects of the same types"などと言われています。

ちなみに、文字列型は、TEXT型、整数型はINTEGER型がデフォルトのようで、これらの試験を行う場合は、キャストしなくても大丈夫です。


スキーマを確認する関数でも大文字小文字の一致や、型名の完全一致(VARCHARではNGで、character varying(20)などと記述する必要がある)が厳密に行われるので、結果が正しいはずなのにNGと表示される場合は、そういった部分を疑ってみてください。

○エスケープ処理
とても些細な話ですが、試験対象のSQLのWHERE句で文字列型などの列を条件式で使用する場合、下記のようにエスケープしてあげないとSQLがエラーになります。

hr=# SELECT results_eq('SELECT first_name FROM employees WHERE employee_id=''105''', ARRAY['David'::varchar]);

●テストプログラムをどう書くかについて

最後にテストプログラムを書き方について、一言。
単他テストにツールを用いるデメリットとして、「工数が大きくなる可能性」をあげました。pgtapも大量のプログラムを書くことになります。
それを時間をかけずに行うには、テストプログラムを人力で書くのではなく、別のプログラムやシェルに書かせるという方法があげられます。
テスト対象のSQLや期待される結果セットを、CSVでもたせるようにして、pgtap用のスクリプトに書き換えるのでもいいですし、EXCELでもたせてマクロでプログラムを生成してもいいと思います。なんでもよいので、仕組みをつくることが重要です。
その具体的な方法はまた書けたらと思いますが、本日はこんな所で


このエントリーをはてなブックマークに追加