テストケースを表に入れて実行する。
成果物
.help
.selftest ?OPTIONS? Run tests defined in the SELFTEST table
sqlite3 :memory: ".help selftest"
.selftest ?OPTIONS? Run tests defined in the SELFTEST table Options: --init Create a new SELFTEST table -v Verbose output
.selftest
引数なし
sqlite3 :memory: ".selftest"
Missing SELFTEST table - default checks only 0 errors out of 1 tests
--init
sqlite3 :memory: ".selftest --init"
Tests generated by --init 0 errors out of 2 tests
-v
sqlite3 :memory: ".selftest -v"
0: memo Missing SELFTEST table - default checks only Missing SELFTEST table - default checks only 1: run PRAGMA integrity_check Result: ok 0 errors out of 1 tests
SELFTEST
sqlite3 :memory: ".selftest --init" ".headers on" "select * from SELFTEST;" "select sql from sqlite_master;"
Tests generated by --init 0 errors out of 2 tests tno|op|cmd|ans 100|memo|Tests generated by --init| 110|run|SELECT hex(sha3_query('SELECT type,name,tbl_name,sql FROM sqlite_master ORDER BY 2',224))|EFDBCA69E0514C06E30E3C0428946EDEACCB90B48A952950A8651BF1 120|run|PRAGMA integrity_check|ok CREATE TABLE selftest( tno INTEGER PRIMARY KEY, op TEXT, cmd TEXT, ans TEXT )
これをマネすればテストできるってわけか。
列名 | 英名予想 | 意味予想 |
---|---|---|
tno |
Test No |
テスト番号。値が小さい順に実行する |
op |
Operation type |
memo ,run のいずれか。memo は実行せず飛ばす。run は実行対象。 |
cmd |
Command |
テスト内容。.testcase で指定するSQL文と同じ。 |
ans |
Answer |
期待値。 |
レコードをみてみるとop
は以下。
op 値 |
意味予想 |
---|---|
run |
テストコード。cmd の出力結果がans と一致するか確認する |
memo |
コメント。標準出力される |
テスト作成
sqlite3 :memory: \ ".selftest --init" \ "delete from SELFTEST;" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select 1;', '1');" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select 1.2;', '1.2');" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select ''A'';', 'A');" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select x''FF'';', x'FF');" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select 1, ''A'' union select 2, ''B'';', '1,A|2,B');" \ "select * from SELFTEST;" \ ".selftest"
実行結果は以下。
Tests generated by --init 0 errors out of 2 tests 1|run|select 1;|1 2|run|select 1.2;|1.2 3|run|select 'A';|A 4|run|select x'FF';|� 5|run|select 1, 'A' union select 2, 'B';|1,A|2,B 0 errors out of 5 tests
列デリミタ,
、行デリミタ|
sqlite3 :memory: \ ".selftest --init" \ "delete from SELFTEST;" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select 1;', '1');" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select 1.2;', '1.2');" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select ''A'';', 'A');" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select x''FF'';', x'FF');" \ "insert into SELFTEST(op,cmd,ans) values('run', 'select 1, ''A'' union select 2, ''B'';', '1|A\n2|B');" \ "select * from SELFTEST;" \ ".selftest"
結果は以下。
Tests generated by --init 0 errors out of 2 tests 1|run|select 1;|1 2|run|select 1.2;|1.2 3|run|select 'A';|A 4|run|select x'FF';|� 5|run|select 1, 'A' union select 2, 'B';|1|A\n2|B 5: Expected: [1|A\n2|B] 5: Got: [1,A|2,B] 1 errors out of 5 tests
注目すべきは以下。列デリミタ,
、行デリミタ|
になるらしい。
5: Expected: [1|A\n2|B] 5: Got: [1,A|2,B] 1 errors out of 5 tests
変更する方法は見当たらない。
op
未指定エラー
sqlite3 :memory: \ ".selftest --init" \ "delete from SELFTEST;" \ "insert into SELFTEST(op,cmd,ans) values('', 'select 1;', '1');" \ "select * from SELFTEST;" \ ".selftest"
Unknown operation "" on selftest line 1
op
はmemo
かrun
を指定すべき。
自作selftest
テーブル
op
はmemo
かrun
を指定すべきなら、テーブル定義をop TEXT default('run') check(op='memo' or op='run'),
にして欲しかった。
自作したらできた。
echo "create table selftest( tno INTEGER PRIMARY KEY, op TEXT default('run') check(op='memo' or op='run'), cmd TEXT, ans TEXT );" > selftest_create.sql
sqlite3 :memory: \ ".read selftest_create.sql" \ "insert into SELFTEST(cmd,ans) values('select 1;', '1');" \ "insert into SELFTEST(op,cmd) values('memo','コメント');" \ ".selftest"
コメント 0 errors out of 1 tests
おお、このほうがいいじゃん。まあこれでもinsert
文が冗長だと思うが。
.testcase
+ .check
のほうが簡単
これと同じことは以下ドットコマンドでできる。
sqlite3 :memory: \ ".testcase 001" \ "select 1;" \ ".check 1"
testcase-001 ok
どうみてもこっちのほうが簡単。
でも.check
は期待値が複数行にまたがっていると指定できない……。
期待値が複数行なら.selftest
でやる
.check
では指定できない。確認してみる。まずは準備。
sqlite3
create table T(A text); insert into T values('1行目 2行目');
.selftest
.selftest --init delete from selftest; insert into selftest values(100,'run','select * from T;','1行目 2行目'); .selftest
結果は以下。成功。
0 errors out of 1 tests
.testcase
+ .check
いろいろ試したがダメだった。
改行そのまま
.testcase multi_line select * from T; .check '1行目 2行目'
結果は以下。
testcase-multi_line FAILED Expected: [1行目] Got: [1行目 2行目 ] sqlite> 2行目' ...>
- 1行目と2行目の改行でコマンド終端とみなされてしまう
- 文字列終端の
'
が開始とみなされて...>
になってしまう
- 文字列終端の
\n
.testcase multi_line select * from T; .check '1行目\n2行目'
testcase-multi_line FAILED Expected: [1行目\n2行目] Got: [1行目 2行目 ]
\n
はそのまま文字とみなされてしまう。
char(10)
.testcase multi_line select * from T; .check '1行目' || char(10) || '2行目'
Usage: .check GLOB-PATTERN
SQL文内なら\n
をchar(10)
で表現できる。だが、.check
ドットコマンド構文では無効。
(select char(10))
先述と同じくダメ。
.testcase multi_line select * from T; .check (select '1行目' || char(10) || '2行目')
Usage: .check GLOB-PATTERN
.import
+ .selftest
でテストケースをまとめる
selftest
テーブルのデータをCSVにして.import
する手もある。
echo "tno,op,cmd,ans 1,run,\"select 1;\",1 2,memo,\"コメント\"," > selftests.csv
sqlite3 :memory: \ ".mode csv" \ ".import selftests.csv selftest" \ ".selftest"
コメント 0 errors out of 1 tests
だが、そうまでしてテーブルにまとめる意味や価値があるだろうか。一覧性があがる?
cat selftests.csv
tno,op,cmd,ans 1,run,"select 1;",1 2,memo,"コメント",
一覧性はあるが、tno
,op
が冗長で微妙。
あと、CSVのメタ文字,
は、select 1, 'A';
などSQL文でも使う。エスケープするためにダブルクォートする。だがSQLでも識別子化するためにダブルクォートを使う。そのときは\"
でダブルクォートをエスケープすることになるだろう。面倒くせぇ……。
でも一括実行&保存するには一番便利な方法か。
SQLテストの最適な方法は?
知らん。.testcase
は1件ずつしかできないし、.selftest
は冗長で行デリミタが|
という謎仕様。
だったらcsv
で出力してdiff
すればいいのでは? でもシェルがクソ言語すぎてうんぬん。
蛇足
もっとこう、以下みたいな形式で書けないの?
# テスト名 期待値 SQL文 # 次のテストとの間に空行をあける 期待値 SQL文 (テスト名は省略できる) 期待値 SQL文
たとえば以下みたいな。
testcases.case
# 1であることを確認する 1 select 1; # 2であることを確認する 2 select 2; 3 select 3;
改行がウザいなら期待値とSQL文との間を,
で区切るとか。
1,select 1;
コメントも含めて一行でやるならSQL文の終端;
以降をすべてコメントにするとか。
1,select 1;1かどうかを確認する
併用できるとか。
1,select 1;1かどうかを確認する 2,select 2;2かどうかを確認する 3,select 3; # 複数レコード確認 1,A|2,B select 1, 'A' union select 2, 'B';
期待値の行デリミタを|
から\n
にしたいが、テスト間の区切りや、期待値とテスト間の区切りと区別がつけられなくなってしまう。さりとて余計なメタ文字をつくれば元の木阿弥。
1,select 1;1かどうかを確認する 2,select 2;2かどうかを確認する 3,select 3; # 4かどうかを確認する 4,select 4; # 複数レコード確認 1,A 2,B select 1, 'A' union select 2, 'B';
2,B
も期待値であると、どうやって判定する? select
でないこと? もし期待値が文字列select ...
を期待していたら? 完結にうまく表現できない。XMLみたいにタグで囲って意味づけすれば元より冗長になってしまう。
空行する。もし期待値とSELECT文が揃っていないのに空行があれば、それは期待値が複数行であるということ。そう解釈させればいけるか。
# 複数レコード確認 1,A 2,B select 1, 'A' union select 2, 'B'; # 複数レコード確認 1,A 2,B select 1, 'A' union select 2, 'B';
パーサ実装するの面倒そう。
類似コマンド
.testcase
:SELFTEST
.cmd
相当。直下のSQLをそれにする.check
:SELFTEST
.ans
相当。引数をそれにする。テスト実行も兼ねる。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
- SQLite 3.29.0
- MeCab 0.996ユーザ辞書
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux
前回まで
- SQLite3学習をはじめよう
- SQLite3学習 SQLiteについて
- SQLite3学習 SQLiteの適切な用途
- SQLite3学習 SQLiteの特徴
- SQLite3学習 SQLiteのクセ
- SQLite3学習 データ型とアフィニティ
- SQLite3学習 演算子の一覧
- SQLite3学習 よくある質問
- SQLite3学習 SQLiteダウンロード&コンパイル
- SQLite3学習 Tclで操作する
- SQLite3学習 ビルドオプション動作確認(SQLITE_ALLOW_URI_AUTHORITY)
- SQLite3学習 面白そうなコンパイルオプション
- SQLite3学習 SQLiteの拡張について
- SQLite3学習 JSON拡張
- SQLite3学習 JSON拡張(json_extract)
- SQLite3学習 JSON拡張(json_each)
- SQLite3学習 JSON拡張(json_tree オブジェクト→行)
- SQLite3学習 JSON拡張(json_tree オブジェクトツリー→行)
- SQLite3学習 JSON拡張(json_tree オブジェクト配列→行)
- SQLite3学習 JSON拡張(json_group_array 行→配列)
- SQLite3学習 JSON拡張(json_group_object 行→オブジェクト)
- SQLite3学習 JSON拡張(json_array_length)
- SQLite3学習 JSON拡張(json_type)
- SQLite3学習 JSON拡張(json_valid)
- SQLite3学習 JSON拡張(json_quote)
- SQLite3学習 JSON拡張(json_array)
- SQLite3学習 JSON拡張(json_object)
- SQLite3学習 JSON拡張(json_patch)
- SQLite3学習 JSON拡張(json_insert)
- SQLite3学習 JSON拡張(json_replace)
- SQLite3学習 JSON拡張(json_set)
- SQLite3学習 JSON拡張(json_remove)
- SQLite3学習 全文検索(FTS5)
- SQLite3学習 全文検索FTSを日本語で使う方法を調べてみた
- 形態素解析MeCabをインストールする
- SQLite3学習 全文検索FTS5のMeCab用トークナイザを実装する
- SQLite3学習 FTS5+MeCabでクエリ構文
- SQLite3学習 FTS5のテーブル作成と初期化
- SQLite3学習 FTS5の補助関数
- SQLite3学習 FTS5のfts5vocab仮想テーブル
- SQLite3学習 再帰クエリ(WITH RECURSIVE)
- SQLite3学習 R-Treeモジュール
- SQLite3学習 ファイル入出力(SQL集計)
- SQLite3学習 拡張関数(generate_series)
- SQLite3謎 主キーの型をintにするとinsertで値を省略したらNULLになってしまう
- SQLite3学習 入出力関数(fsdir, readfile, writefile, edit)
- SQLite3ビルド コンパイルオプションを付与する方法(CFLAGS等))
- SQLite3 コンパイルオプション確認方法(pragma compile_options)
- SQLite3ビルド ICUを有効にする(SQLITE_ENABLE_ICU)
- SQLite3拡張 ICUを動的ロードする
- SQLite3拡張 ICUでcollateする
- SQLite3拡張 ICUで全文検索する(FTS4)
- SQLite3拡張 SQL関数一覧(pragma function_list)
- SQLite3拡張 仮想テーブルモジュール一覧(pragma module_list)
- SQLite3拡張 プラグマ一覧(pragma pragma_list)
- SQLite3謎 values()構文
- SQLite3学習 インタフェース概要
- SQLite3学習 CLI起動引数(-A)Archive
- SQLite3ビルド SQLITE_HAVE_ZLIBコンパイルオプション付与するも確認できず
- SQLite3学習 CLI(-readonly)
- MeCabユーザ辞書の作り方(Wikipediaの題名を名詞とした)
- SQLite3学習 CLI(-zip)
- SQLite3ドットコマンド(.archive)
- SQLite3ドットコマンド(.auth)断念
- SQLite3ドットコマンド(.backup .restore)
- SQLite3ドットコマンド(.read)
- SQLite3ドットコマンド(.dump)
- SQLite3ドットコマンド(.bail)謎
- SQLite3ドットコマンド(.binary)
- SQLite3ドットコマンド(.cd)
- SQLite3ドットコマンド(.changes)
- SQLite3ドットコマンド(.testcase .check)
- SQLite3ドットコマンド(.clone)
- SQLite3ドットコマンド(.databases)
- SQLite3ドットコマンド(.dbconfig)
- SQLite3ドットコマンド(.dbinfo)
- SQLite3ドットコマンド(.echo)
- SQLite3ドットコマンド(.eqp)
- SQLite3ドットコマンド(.excel)
- SQLite3ドットコマンド(.exit)
- SQLite3ドットコマンド(.expert)
- SQLite3ドットコマンド(.filectrl)
- SQLite3ドットコマンド(.fullschema)
- SQLite3ドットコマンド(.headers)
- SQLite3ドットコマンド(.help)
- SQLite3ドットコマンド(.import)
- SQLite3ドットコマンド(.imposter)
- SQLite3ドットコマンド(.indexes)
- SQLite3ドットコマンド(.limit)
- SQLite3ドットコマンド(.lint)
- SQLite3ドットコマンド(.load)
- SQLite3ドットコマンド(.log)
- SQLite3ドットコマンド(.mode)
- SQLite3ドットコマンド(.nullvalue)
- SQLite3ドットコマンド(.once)
- SQLite3ドットコマンド(.open)
- SQLite3ドットコマンド(.output)
- SQLite3ドットコマンド(.parameter)
- SQLite3ドットコマンド(.print)
- SQLite3ドットコマンド(.progress)
- SQLite3ドットコマンド(.prompt)
- SQLite3ドットコマンド(.quit)
- SQLite3ドットコマンド(.read)
- SQLite3ドットコマンド(.read)
- SQLite3ドットコマンド(.recover)
- SQLite3ドットコマンド(.save)
- SQLite3ドットコマンド(.scanstats)
- SQLite3ドットコマンド(.schema)