MySQL Test Frameworkとは、Unix/Linux用のMySQLのためのリグレッションテスト用フレームワークです。
https://dev.mysql.com/doc/refman/8.0/en/mysql-test-suite.html
MySQL/MariaDB/Percona Serverで提供されており、Tar ball、もしくはバイナリパッケージ(rpm, deb等)で提供されています。
数多くのテストスイート(一連のテストプログラムを実行するセット)が同梱されており、事前定義済みのMySQL専用のテストを再利用しつつ効率的に独自のテストも実装する事ができます。
今回は簡単に使用方法について検証したいと思います。
MySQL Test Frameworkのインストール
MySQL Test Frameworkのドキュメントは以下にあります。
今回は、検証環境にCentOS7を使用しましたので、まずはRPMをインストールします。
バイナリパッケージは、 [package prefix name]-test という名称で提供されています。
| 1 2 | $ yum -y install https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm $ yum -y install mysql-community-server mysql-community-test | 
インストールされる主なプログラムは以下です。
| プログラム名 | 説明 | 
|---|---|
| mysql-test-run.pl | テストスイートを実行するためのPerlスクリプトです。mtrというシンボリックリンクが用意されています | 
| mysql-stress-test.pl | ストレステスト用のPerlスクリプトです。 | 
| mysqltest | 実際にテストを実行するためのC++プログラムです。mysql-test-run.plからはこのプログラムが呼び出されています。 | 
mtrはインストール時に作成されたmysql-testディレクトリで実行される事を想定しています。
事前にmysql-testディレクトリに移動しましょう。
| 1 | $ cd /usr/share/mysql-test | 
テストケースを構成するファイル
基本的なテストに使用するファイルとしては以下があります。
インストール先にはtとrという名称のディレクトリがありますが、tはtest、rはresult用のファイルが格納されています。
| ファイル名 | 説明 | 
|---|---|
| t/[test name].test | テスト本体のファイルです | 
| r/[test name].result | テスト実行後の想定される結果テキストファイルです | 
| t/[test name]-(master/slave/client).opt | テスト実行時に作成されるmysqld(master/slave)やクライアントで使用するパラメータ | 
| t/[test name]-(master/slave).sh | テスト実行時のmysqldインスタンス(master/slave)ごとに実行されるスクリプト | 
| t/[test name].inc | 別のテストからインクルードされるテストスクリプト | 
また、テスト実行後の結果は [install dir]/var 配下に格納されます。
以下のように同じテストのファイル名は.incを除いて同じテスト名にします。
| 1 2 3 4 5 6 7 8 9 | $ ls -l /usr/share/mysql-test/t | head -3 total 16412 -rw-r--r--. 1 root root    200 Sep 20 08:30 1st.test -rw-r--r--. 1 root root  12422 Sep 20 08:30 admin_interface.test $ ls -l /usr/share/mysql-test/r | head -3 total 58564 -rw-r--r--. 1 root root     668 Sep 20 08:30 1st.result -rw-r--r--. 1 root root    8221 Sep 20 08:30 admin_interface.result | 
1つのテストは少なくとも同じ名称のtestファイルとresultファイルが必要になります。
testファイルには一連のコマンドを、resultファイルには、期待される標準出力の結果を記載します。
テストの作成
早速、簡単なテストケースを作成します。
| 1 2 | $ echo -e "select 1;" > t/select_one.test $ echo -e "select 1;\n1\n1" > r/select_one.result | 
select 1 というテストの結果が、select_one.resultの内容と同じかを確認します。
テストを実行するときは、テスト名をmtrに指定します。
mtrはmysqldを必要に応じて起動してテストを実行します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | $ ./mtr select_one Logging: ./mtr  select_one MySQL Version 8.0.18 Checking supported features Using 'all' suites Collecting tests Checking leftover processes Removing old var directory Creating var directory '/usr/share/mysql-test/var' Installing system database Using parallel: 1 ==============================================================================                   TEST NAME                       RESULT  TIME (ms) COMMENT ------------------------------------------------------------------------------ worker[1] mysql-test-run: WARNING: running this script as _root_ will cause some tests to be skipped [100%] main.select_one                           [ pass ]      1 ------------------------------------------------------------------------------ The servers were restarted 0 times The servers were reinitialized 0 times Spent 0.001 of 20 seconds executing testcases Completed: All 1 tests were successful. | 
実行したテストで使用したmy.cnf、datadir、ログ等は、 [install dir]/var に格納します。
一般ユーザで実行する場合は、任意の権限を持つディレクトリを --vardir に指定する必要があります。
| 1 | $ ./mtr --vardir=/home/normaluser/mysql-test select_one | 
一般ユーザで実行する際の注意点として、すでに [install dir]/var にディレクトリが存在した場合、実行時にクリーンアップが行われるため権限エラーが発生します。
その場合は、rootユーザで該当のディレクトリを削除/移動してからテストを実行してください。
テストは無事成功したようなので、次は失敗させてみましょう。
.resultファイルの1を2に変更してみました。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | $ echo -e "select 2;\n2\n2" > r/select_one.result $ ./mtr select_one  : ==============================================================================                   TEST NAME                       RESULT  TIME (ms) COMMENT ------------------------------------------------------------------------------ worker[1] mysql-test-run: WARNING: running this script as _root_ will cause some tests to be skipped [100%] main.select_one                           [ fail ]         Test ended at 2019-10-21 01:27:07 CURRENT_TEST: main.select_one --- /usr/share/mysql-test/r/select_one.result   2019-10-21 04:26:41.870947985 +0300 +++ /usr/share/mysql-test/var/log/select_one.reject     2019-10-21 04:27:07.312243519 +0300 @@ -1,3 +1,3 @@ -select 2; -2 -2 +select 1; +1 +1 mysqltest: Result content mismatch safe_process[30834]: Child process: 30835, exit: 1  - the logfile can be found in '/usr/share/mysql-test/var/log/main.select_one/select_one.log' ------------------------------------------------------------------------------ The servers were restarted 0 times The servers were reinitialized 0 times Spent 0.000 of 23 seconds executing testcases Completed: Failed 1/1 tests, 0.00% were successful. Failing test(s): main.select_one The log files in var/log may give you some hint of what went wrong. If you want to report this error, please read first the documentation at http://dev.mysql.com/doc/mysql/en/mysql-test-suite.html mysql-test-run: ** ERROR: there were failing test cases | 
期待された結果が異なる事が報告され、Diffが表示されました。
テストを実行した際の結果ログは [install dir]/var/log 以下に格納されています。
出力される主なログは以下です。
| ログファイル名 | 説明 | 
|---|---|
| bootstrap.log | mysqld起動時のオプションと起動ログを出力 | 
| mysqld.[n].err | テストに使用したmysqldのエラーログ | 
| mysqltest.log | 実行した全テスト結果を格納するログ | 
| [testname].reject | テストが失敗した際の標準出力 | 
| [suite name].[test name] | テストごとに使用されるファイル等を格納するディレクトリ | 
テストスイートの作成
一連のテストをまとめたテストスイートを定義してみます。
テストスイートはsuiteディレクトリ配下の任意の名称のディレクトリです。
原則としてr,tのディレクトリを含み、一連のテストを格納します。
それではまた簡単なテストを格納したテストスイートを作成してみましょう。
| 1 2 3 4 5 | $ mkdir -p suite/mytest/{t,r} $ echo -e "select 1;" > suite/mytest/t/select_one.test $ echo -e "select 2;" > suite/mytest/t/select_two.test $ echo -e "select 1;\n1\n1" > suite/mytest/r/select_one.result $ echo -e "select 2;\n2\n2" > suite/mytest/r/select_two.result | 
--suite でテストスイート名を指定すると2つのテストが実行されます。
| 1 2 3 4 5 6 7 8 9 10 11 12 | $ ./mtr --suite=mytest Logging: ./mtr  --suite=mytest  : ==============================================================================                   TEST NAME                       RESULT  TIME (ms) COMMENT ------------------------------------------------------------------------------ worker[1] mysql-test-run: WARNING: running this script as _root_ will cause some tests to be skipped [ 50%] mytest.select_one                         [ pass ]        [100%] mytest.select_two                         [ pass ]        ------------------------------------------------------------------------------  : Completed: All 2 tests were successful. | 
suiteディレクトリ配下には数多くのテストスイートが存在します。
中には今をときめくclone pluginのテストスイートなどもありますので、 t/*.test のファイルを確認すると、どのように実行することを想定しているのかという事の理解の助けになります。
| 1 2 3 4 5 6 7 8 9 10 11 | $ ls suite | head audit_null auth_sec binlog binlog_gtid binlog_nogtid clone collations connection_control encryption engines | 
また、テストケースにはコーディングガイドラインがありますが、自作のテストケースの参考として利用するのもよいと思います。
外部データベースに対するテストの実行
mtrは内部的にmysqldを起動してテストを実行することを想定しています。
ですが、プリセットされたデータベースに対してテストしたいというニーズは一定数あるかと思います。
起動済みのデータベースに対するテストを行うには、 --extern オプションを指定します。
--extern オプションを付けて実行された場合は、mtrはmysqldインスタンスを作成しません。
--extern を使用する事により意図しないデータの変更等を避けるために、以下のページに注意点がまとめられています。
https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_NAMING_CONVENTIONS.html
今回、稼働済みの環境に対してテストを実行するために、以下の作業を行います。
- testデータベースの作成
- テスト用ユーザの作成と必要な権限の付与
外部データベースは、mysql-testとは異なるサーバ(mysql2)としました。
| 1 2 3 4 5 6 7 8 | mysql2> create database if not exists test; mysql2> create role mysql_test;  mysql2> grant all on test.* to 'mysql_test'; mysql2> grant all on mtr.* to 'mysql_test'; mysql2> grant all on mysql.* to 'mysql_test'; mysql2> create user mysqluser1 identified by "Password1!"; mysql2> grant mysql_test to mysqluser1; mysql2> set default role all to mysqluser1; | 
それではテストを実行してみます。
| 1 2 3 4 5 6 7 8 9 10 11 | $ ./mtr --suite=mytest --user=mysqluser1 --extern password=Password1! --extern host=mysql2  : ==============================================================================                   TEST NAME                       RESULT  TIME (ms) COMMENT ------------------------------------------------------------------------------ worker[1] mysql-test-run: WARNING: running this script as _root_ will cause some tests to be skipped [ 50%] mytest.select_one                         [ pass ]      2 [100%] mytest.select_two                         [ pass ]      2 ------------------------------------------------------------------------------  : Completed: All 2 tests were successful. | 
想定どおり成功しました。
外部データベースでは、起動済みであるために初期化時に読み込まれる.optファイルが使用できない、ローカルでの実行前提である.shが実行できない等の制限がありますので、
シンプルな用途のみに利用する事をお勧めします。
まとめ
今回は、MySQL Test Frameworkの簡単な動作について検証しました。
再現テストや設定確認等、アイデア次第で様々な用途に利用ができると思います。
MySQL Test Frameworkは基本的にはSQLで構成されますが、制御構文や外部スクリプトの実行、Perl構文の埋め込み等にも対応していますので、興味がありましたら
mysqltest Language Referenceをご確認ください。
ぜひMySQLの健康管理にご利用ください。


 
		 
		 
			 
			 
			 
			 
			 
			 
			 
			