1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

おにぎり万太郎Advent Calendar 2019

Day 16

SQLをテストする

Last updated at Posted at 2019-12-15

テスト、書いてますか?

私の担当業務の1つに、データマートの構築があります。
分析しやすいような分析用テーブルをいくつかのSQLを元に作っているのですが、元データ自体のバリデーションが弱かったり、ビジネスロジックが複雑ゆえ、論理エラーが多く発生していました。

そこで、Pythonのunittestライブラリを活用したテストを現在は導入しています。

良かったこと

テストを書くことで、枕を高くして寝れるようになりました。

具体的には、

  • 過去指摘された論理エラーをテストに組み込むことで再発防止になる
  • 別テーブルで発生した論理エラーが起きそうなテーブルに、同じテストを組み込む
  • 将来発生しうるリスクに対して予めテストを仕込んでおく

といった取り組みができており、アウトプットの品質向上につながっています。

unittestとは

Pythonにおける標準のユニットテストフレームワークです。
テストの自動化、テストのセットアップおよびシャットダウンコードの共有、テストのコレクションへの集約、レポートフレームワークからのテストの独立性などを提供しています。
25.3. unittest — Unit testing framework — Python 2.7.17 documentation

テストのセットアップ

下記のように、setUpModule()関数を作れば、特別な呼び出し不要でセットアップが可能です。
今回はテストを実行したいcreate tableするSQLをここで呼び出して、実際にテーブルを作成しています。
このとき、本メソッドは単にテーブルを作るだけでなく、prefixとしてtest_をつけるようにしています。

def setUpModule():
    print("Setting up…")
    test_table_name = test_util.generate_test_table(executing_file_name)

実行したいテストの記述

このように、事項したいテストをtest_hogeという形式で記述します。
setUpModule()によって作られたテスト用テーブルに対して集計をかけるSQLを記述しておいて、それを呼び出します。
結果は真偽値で返ってくるようにすることで、最後のself.assertTrue()で一律で判定しています。
ここは、より複雑な判定方法も取ること可能ですが、今回はロジックをSQL側に持たせて関心を分離しています。

class TestQuery(unittest.TestCase):
    def test_mrr_growth(self):
        query = test_util.get_query_by_execution_info(executing_file_name, sys._getframe().f_code.co_name)
        rows = test_util.get_result_in_dataframe(query)
        actual = rows.iloc[0][0]
        self.assertTrue(actual)

    def test_gap_between_new_and_old_less_than_n(self):
        query = test_util.get_query_by_execution_info(executing_file_name, sys._getframe().f_code.co_name)
        rows = test_util.get_result_in_dataframe(query)
        actual = rows.iloc[0][0]
        self.assertTrue(actual)

    def test_uniqueness(self):
        query = test_util.get_query_by_execution_info(executing_file_name, sys._getframe().f_code.co_name)
        rows = test_util.get_result_in_dataframe(query)
        actual = rows.iloc[0][0]
        self.assertTrue(actual)

テストのシャットダウン

最後に環境を汚さないよう、テーブルを削除して完了です。

def tearDownModule():
    test_util.delete_test_table(test_table_name)
    print("Tear down.")

工夫

このようにシンプルに扱うために、上記ではtest_utilという独自ライブラリを作成しています。
これは実装しているSQLスクリプトをよしなに扱うためのスクリプトです。
今回コードは公開しませんが、上記のように各create tableスクリプトに対するテストが、シンプルに書けるような設計にしておくと良いでしょう。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?