mysql の CHECKSUM TABLE が 自動テストのDBロールバックする時に便利だった


はじめに

去年は、言語バージョンアップにおけるE2Eテストの考え方とアプローチ方法についてを書きました。
今回は、「DBの差分とロールバック」 に関して書きたいと思います。

DB Diff を実際に使ってみた結果

前回の記事では、「DB Diff」というツールを使うことで、比較とロールバックを自動で行うことができると書いていました。

★E2Eで検証するデータ種類によって比較方法

新規登録で同じデータが登録される。
→ 「どのテーブルに変更が加わったのか?」は DBデータファイルの更新日時等から自動取得させる。
→ 登録されたDBデータの差分の比較。( DB Diff )
→ 比較後にテスト実行前の状態に戻す( DB Diff )

とある問題が発生し、本格的な導入を断念することにしました。

レコードが多いテーブルの比較を行う場合に時間がかかり、テスト実行後のデータロールバックに時間がかかりすぎたためです。
テストケースは非常に多いため、1テストケースが終わってからのロールバックに時間がかかってしまうのは致命的でした。
レコードが多いテーブルが存在している理由は下記の理由からでした。

E2E作成環境のシステム状態(DB状態)の前提

前回の記事では、困ることとして下記のようなことを書きました。

★歴史のあるシステムでE2Eテストを書く時に困ること
この機能がどのように動くのか正解がわからない
(だって、沢山機能あるし、、、業務フローが難しいし、、、)
正解とは?
厳密にやるなら、
この機能を使うと、「どのテーブル」の「どのカラム」の「どういうデータ」が「こういうデータ」に変わる
ということが明確になっていてそれを結果とすることが理想です。
しかしそれをやるには、プログラムを隅まで追って、データの流れを全て掴むことが必要です。
うーん、ちょっと大変そうです。

これは、「とある機能を使えるようにするために、どのようなデータをすれば良いのかわからない」とも言えます。
これも困りますね。

仕様は理解したほうが良いので、本来であればなるべく初期状態から始めて、プログラムを見ながらイチからデータを作っていったほうが良いでしょう。
ですが、よく知らない機能が沢山あったらとても時間はかかりますね。

そこで、ある程度「検証データ」が揃っている環境をベースとして使うことにしました。
幸い、歴史のあるシステムということもあり、様々な機能が動く環境が共通検証環境として存在していました。
この環境では、あるテーブルに関しては数万レコード以上もあり、DBDiff では対応しきれなかったのです。

ベースデータ(seed データ)として使う場合の問題と対策

E2Eテスト作成に使用するテストフレームワーク(Codeception)では、テスト開始時にDUMPファイルを投入することができますが、
検証データの揃っている環境のDUMPファイルはなかなか重く、テスト開始時のリストアに時間がかかってしまいました。

そこで、検証データがリストア済みの Dockerイメージを作成し、コンテナを起動するだけで環境が整うようにし、テスト開始時のリストアを不要にしました。

テスト実行後のDBロールバック

テスト実行後は、他のテストに影響しないよう、そのテストで操作されたデータは元に戻してあげる必要があります。
本来であれば setFixture 等で、そのテスト実行前に必要なデータを投入するのが正しいとは思いますが、
前述している理由により、ある程度データが揃っている環境をベースにしているため、ロールバックが必要です。

検証データの揃っている Dockerイメージを作成しているということで、テスト実行後にコンテナ破棄・再作成を行えば、データは元の状態に戻りますが、数秒かかってしまったため、断念しました。
数百テストケースあった場合、ロールバックだけでも、(数百×数秒)の時間がかかってしまいます。

そこで、やっとこの記事の本題の CHECKSUM TABLE です。

CHECKSUM TABLE 構文とは?

MySQL 5.6 リファレンスマニュアル
https://dev.mysql.com/doc/refman/5.6/ja/checksum-table.html

CHECKSUM TABLE tbl_name [, tbl_name] ... [ QUICK | EXTENDED ]

CHECKSUM TABLE は、テーブルの内容に対するチェックサムをレポートします。チェックサム操作中、そのテーブルは InnoDB および MyISAM に対する読み取りロックでロックされます。このステートメントを使用すると、その内容が、バックアップ、ロールバック、またはデータを元の既知の状態に戻すことを目的としたその他の操作の前後で同じであることを検証できます。このステートメントには、このテーブルに対する SELECT 権限が必要です。

実際に実行した結果を見てみましょう。
今回、サンプルとして使ったのは チケット管理ツールの Redmine です。
Redmineのセットアップから書きますね。

サンプル Redmine のセットアップ

ファイル内容とコマンドだけ、書いていきます。

docker-compose.yml
version: '3.1'
services:
  redmine:
    image: redmine
    ports:
      - 8080:3000
    environment:
      REDMINE_DB_MYSQL: db
      REDMINE_DB_PASSWORD: example
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: redmine
docker-compose up -d
→ 初回実行時は長いです。
 ・
 ・
 ・
Starting redmine_db_1      ... done
Starting redmine_redmine_1 ... done
docker ps
→
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
42d33dfe19b3        mysql:5.7           "docker-entrypoint.s…"   4 days ago          Up 25 seconds       3306/tcp, 33060/tcp      redmine_db_1
b5ea4d48a576        redmine             "/docker-entrypoint.…"   4 days ago          Up 26 seconds       0.0.0.0:8080->3000/tcp   redmine_redmine_1

http://localhost:8080/

無事に表示されましたか?

ログインします。

Login: admin
Password: admin

ログインできたら、チケット作成ができるまで整えます。

  • パスワード変更
  • プロジェクト作成
  • 初期状態のトラッカーを作成したプロジェクトに設定

整っていると、「新しいチケット」のリンクが出てきます。

適当に何個かチケットを作ってみます。

できました。
この状態で CHECKSUM を見てみましょう。

何個かチケット作った状態のDBのCHECKSUM

docker exec -it redmine_db_1 bash
root@42d33dfe19b3:/# mysql -uroot -pexample

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| redmine            |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> use redmine;
Database changed

mysql> CHECKSUM TABLE projects,issues;
+------------------+------------+
| Table            | Checksum   |
+------------------+------------+
| redmine.projects | 2392964020 |
| redmine.issues   |  180285818 |
+------------------+------------+
2 rows in set (0.00 sec)

ロールバックした時の確認用として、issues テーブルのバックアップをとっておきます。

mysqldump -uroot -pexample redmine issues > issues_dump.sql

チケットを追加で1つ作った時のCHECKSUM

test4  という題名のチケットが作られました。
この状態でもう一度 CHECKSUM TABLE を見てみます。

mysql> CHECKSUM TABLE projects,issues;
+------------------+------------+
| Table            | Checksum   |
+------------------+------------+
| redmine.projects | 2392964020 |
| redmine.issues   |  332506067 |
+------------------+------------+
2 rows in set (0.00 sec)

変わりましたね。

ロールバックした後のCHECKSUM

では、チケット追加前にとったDUMPファイルをリストアしてみます。

mysql -uroot -pexample redmine < issues_dump.sql
mysql> CHECKSUM TABLE projects,issues;
+------------------+------------+
| Table            | Checksum   |
+------------------+------------+
| redmine.projects | 2392964020 |
| redmine.issues   |  180285818 |
+------------------+------------+
2 rows in set (0.00 sec)

元に戻りました。

テスト実行後のDBロールバック方法

話を戻します。
検証データの揃っている Dockerイメージを使っています。
この段階で、全テーブルの CHECKSUM TABLE の値とテーブル別でDUMPファイルを作っておきます。

テスト実行後に、再度 全テーブルの CHECKSUM を取得し、テスト実行前(Dockerイメージ作成時に作成)の CHECKSUM とを比較します。

つまり、CHECKSUM の値に差異のあるテーブルが、そのテスト実行で変更のあったテーブルということになります。
あとは、そのテーブルのDUMPファイルを個別にリストアすることで、テスト実行前のデータに戻すことができます。
動かすサーバ・PCのスペックにもよると思いますが、短時間でロールバックできるようになりました。

最後に

「こうやったらもっと良くなるのではないか?」「どうしたらもっと良くなるのか?」を考え、行動し、そこから学び、次に活かすこと。
仕事に限らず、この世を生きる上で、とても大切なことだと思います。