Workflows から BigQuery を実行する基盤の SQL テストを Workflows で構成するメリット

# テスト用 Workflows のメリット

シンプルなデータ処理基盤を GCP 上で構成するときに、 Workflows を使う機会がありました。
BigQuery を軸にした基盤なのですが、 SQL を正しく書けたかどうかのテストをするときに Workflows でテストを実行する基盤を作ってみたところメリットがあったので記事を書くことにしました。

## 前提



### この記事に書いてあること

- 大まかな処理の流れ
- テスト用 Workflows のメリット

### この記事に書いていないこと

- Workflows を定義する yaml

後述する変数のメモリ上限があるためちょっとした工夫は必要ですが、特別なことはしていないため今回の記事では書きません。

## 作ったもの

### アーキテクチャ図

テスト用 Workflows のアーキテクチャ図はこんな感じです。テスト用のクエリを実行するだけなのでシンプルです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415560/9cffb4c5-9ae4-250d-3837-9eacad143a66.png)

### 処理の流れ

次の流れになります。

1. Workflows の引数からテストケース名が渡される
1. テストケースに応じて以下のクエリを GCS から取得する
   1. テストの入力データを生成するクエリ
   1. テストの正解データを生成するクエリ
   1. テスト対象となるクエリ
1. 取得したクエリが SELECT 文の場合と DML 文の場合で使用するテストクエリを変える
1. 3 のクエリとテストデータを生成するクエリを BigQuery で実行する
1. テスト結果に応じた後続の処理を実行する
   1. 成功した場合は成功したことを出力する
   1. 失敗した場合は失敗の原因を出力し、差分をテーブルにする

## メリット

### テスト用のクエリを書く手間が省ける

SQL が正しく書けているかどうかを確認するときは、テスト対象となるクエリによってできるテーブルと正解テーブルを比較して差分がなければ正しいとします。
今回は次のようなクエリで結果が返って来なければテスト成功とします。

```sql
(SELECT
  *
FROM
  test_target_table
EXCEPT DISTINCT
SELECT
  *
FROM
  TEST_DATASET.corect_table)
UNION ALL
(SELECT
  *
FROM
  TEST_DATASET.corect_table
EXCEPT DISTINCT
SELECT
  *
FROM
  test_target_table
)
;

SELECT
  count(1)
FROM
  test_target_table
EXCEPT DISTINCT
SELECT
  count(1)
FROM
  TEST_DATASET.corect_table
;
```

このクエリを手作業で実行するのはちょっと面倒です。
例えばテスト対象となるクエリが SELECT 文だった場合は次のように、`WITH` 句や `CREATE TEMP TABLE` を使ってテーブルを指定します。

```sql
WITH test_target_table AS (
-- テスト対象クエリ
)
-- 差分比較
```

カッコ内に入れるクエリをコピペすればいいといえばそれまでですが、手作業は手間がかかりミスも発生しうるのであまりやりたくありません。

また `INSERT` や `UPDATE` など副作用のあるクエリの場合はクエリを実行し、差分比較のクエリの FROM 句で `test_target_table` から `TEST_DATASET.table` のようにデータセットを指定するように変えなければならず、これもまた地味に面倒です。

Workflows で仕組みを作れば、テストケース名とテスト対象テーブル名（INSERT, UPADTE, DELETE のみ）を指定するだけでテストを実施できるため手間が省けました。

### 本番に近い環境でテストができる

Workflows 制限の１つに変数のメモリ上限があり、1 つの Workflow あたり 512kB しか格納できません。
長い SQL を書くと、意外とこの制限に引っかかることがあります。データ処理基盤の実装当初はあまりこの制限のことを意識していませんでした。
しかしテストを実施した結果、この制限に引っかかってしまい、変数のメモリ上限を意識することになりました。
[Workflows のリソース上限](https://cloud.google.com/workflows/quotas?hl=ja#resource_limits)

今回のケースではクエリ文字列はそこそこ大きかったものの、そもそもテスト用 Workflows の実装が悪く、無駄に使っていた変数のメモリを開放することで解決しました。

ここでのメリットは、早いうちからリソースの上限に抵触するクエリの存在に気づくことができたことです。
もしもクエリの文字列だけで 512 kB を超えるケースが存在し、かつ手作業で BigQuery のコンソール画面からテストを実行していた場合、Workflows の上限にひっかかるのに気づくのが遅れていたと思われます。下手をすると一通りのクエリ作成を終えてから制限に引っかかることに気づき、クエリの大幅な書き直しやアーキテクチャの変更につながっていた可能性があります。

本番に近い環境を用意したことで、早いうちからエラーに気づける点が大きなメリットです。

### テスト後の処理を自動的に実行できる

テストに失敗した場合、どのレコードで差分が出てしまったかを把握しておきたいです。
BigQuery のコンソールからでも確認はできますが、テーブルにしておくことでいつでも見直せる状態にしておくと便利です。
手動で実行している場合はテーブルにするのに手間がかかりますが、テスト用 Workflows にテーブル作成を組み込んでおけば自動的に差分のテーブルを出力することができます。

この他にもテスト結果のログを手動で作りたい場合にログを出力する処理を後続処理に追加することなどができます。

## 注意点

### Workflows のエラーをすべて検出できるわけではない

当たり前ですが、本番とはアーキテクチャが異なるためこれ以外のエラーも出る可能性があります。
あくまで SQL の単体テストレベルでの動作を確認できる程度で、本番と同じ構成での結合テストやシステムテストは必須です。

### テスト用 Workflows の実装に手間がかかる

これも当たり前ですが簡単なアーキテクチャとはいえ実装には数時間かかっています。
テスト対象となるクエリの量が少なければテスト用 Workflows の実装コストのほうが高くなってしまい、先述したメリットをすべて打ち消してしまいます。

## まとめ

テスト用の Workflows を作ることで様々な手間を省力化することができました。
また、本番に近い環境でクエリを実行することで早いうちから引っかかる可能性のあるリソース制限が分かりました。
実装自体はやや時間がかかりますが、テスト対象が多ければそれに見合うだけの手間を削減できます。