1、概述
在我們給mysql打了patch後,不僅需要測試新增的功能,同時更重要的問題是,需要對原有的功能作回歸――若新增的patch導緻原有其他功能産生bug,就得不償失。
mysql自動測試架構是一個以mysql架構和内部引擎為測試對象的工具。主要執行腳本在釋出路徑的mysql-test目錄下。自動測試架構的主要測試步驟,是通過執行一個case,将該case的輸出結果,與标準的輸出結果作diff。這裡的“标準輸出結果”指代在可信任的mysql版本上的執行結果。
如果某個case的執行結果與标準輸出結果不同,則說明正在執行的這個mysql服務有問題,或許是架構,或許是引擎。 當然若幹case執行正确并不能確定被測試的架構和引擎是沒有問題的,除非能夠證明執行的case已經覆寫了所有的分支(這太難了)。
這裡說到的case,是指一系列的語句,包括sql語句和一些必要的shell語句。在mysql-test/t/目錄下,可以找到很多這樣的case,他們的檔案名以.test為字尾。接下來我們用一個簡單的例子來說明用法。
2、初體驗
再簡單的例子也不如自己寫的簡單。是以我們通過自己作一個case,來體驗一下這個架構的便捷。
a) 在mysql-test/t/目錄下建立檔案 mytest.test, 内容為
1 — source include/have_innodb.inc
2 use test;
3 create table t(c int) engine=innodb ;
4 insert into t values(1);
5 select c from t;
6 drop table t;
從第二行開始是我們熟悉的sql語句,建立一個innodb表,插入一行,執行一個查詢,删除這個表。輸出也是顯而易見的。
b) 在mysql-test/r/目錄下建立檔案 mytest.result, 内容為
1 use test;
2 create table t(c int) engine=innodb ;
3 insert into t values(1);
4 select c from t;
5 c
6 1
7 drop table t;
c) 在mysql-test/ 目錄下執行 ./mtr mytest
===================================================================
test result time (ms)
————————————————————
worker[1] using mtr_build_thread 300, with reserved ports 13000..13009
worker[1] mysql-test-run: warning: running this script as _root_ will cause some tests to be skipped
main.mytest [ skipped ] no innodb support
main.mytest ‘innodb_plugin’ [ pass ] 5
the servers were restarted 0 times
spent 0.005 of 3 seconds executing testcases
completed: all 1 tests were successful.
最後一句話說明,測試通過了。
說明:
1) mysql-test/mtr這個檔案,是一個perl腳本。同目錄下還有 mysql-test-run 和mysql-test-run.pl這兩個檔案,這三個檔案一模一樣。
2) 每個case會啟動一個mysql服務,預設端口為13000。如果這個case涉及到需要啟動多個服務(比如主從),則端口從13000遞增。
3) ./mtr的參數隻需要指明測試case的字首即可,如你看到的。當你執行./mtr testname會自動到t/目錄下搜尋 testname.test檔案來執行。當然你也可以執行./mtr mytest.test, 效果相同。
4) mytest.test第一行是必須的,當你需要使用innodb引擎的時候。可以看到mtr能夠解釋source文法,等效于将目标檔案的内容全部拷貝到目前位置。mysql-test/include目錄下有很多這樣的檔案,他們提供了類似函數的功能,以簡化每個case的代碼。注意souce前面的 –, 大多數的非sql語句都要求加 –。
5) mytest.test最後一行是删除這個建立的表。因為每個case都要求不要受别的case影響,也不要影響别的case,是以自己在case中建立的表要删除。
6) mtr會将mytest.test的執行結果與r/mytest.result作diff。 若完全相同,則表示測試結果正常。注意到我們例子中的mytest.result. 其中不僅包括了這個case的輸出,也包括了輸入的所有内容。實際上有效輸出隻有5、6兩行。如果希望輸出檔案中隻有執行結果,可以在第一行後面加入 — disable_query_log。
3、沖突結果及處理
我們來測試一個錯誤的輸入輸出。就在上文的mytest.test中加入一行,如下:
2 — disable_query_log
3 use test;
4 create table t(c int) engine=innodb ;
5 insert into t values(1);
6 select c from t;
執行 ./mtr mytest,我們知道,他隻會輸出兩行,分别為c和1.
執行結果
。。。。。。
@@ -1,7 +1,2 @@
-use test;
-create table t(c int) engine=innodb ;
-insert into t values(1);
-select c from t;
c
1
-drop table t;
mysqltest: result length mismatch
completed: failed 1/1 tests, 0.00% were successful.
最後一句話說明我們的這個case測試沒有通過。我們知道mtr将執行結果與r/mytest.result檔案作diff,這個結果的前面部分就是diff的結果。
說明:
1) 執行case失敗有很多中可能,比如中間執行了某個非法的語句,這個例子中的失敗,指代的是執行結果與預期結果不同。
2) 目前的執行結果會儲存在r/mytest.reject中
3) 如果mytest.reject中的結果才是正确的結果(錯入出現在mytest.result中),可以用mytest.reject将result覆寫掉,這樣正确的标準測試case就完成了。可以直接使用 ./mtr mytest –record指令生成mytest.result.
4) 注意mtr在作diff的時候是直接文本比較,是以如果你的case中出現了多次執行結果可能不同的情況(比如時間相關),這不是一個好的case。當然處理的辦法是有的,請參加附錄中關于 replace_column的描述。
4、批量執行的一些指令
實際上我們更常用到的是批量執行的指令。
1) ./mtr
就是這麼簡單。會執行所有的case。當然包括剛剛我們加入的mytest.這裡說的”所有的case”,包括t/目錄下所有以.test為字尾的檔案。也包括 suits目錄下的所有以.test為字尾的檔案。
注意隻要任何一個case執行失敗(包括内部執行失敗和與result校驗失敗)都會導緻整個執行計劃退出。是以–force很常用,加入這個參數後,mtr會忽略錯誤并繼續執行下一個case直到所有的case執行結束。
2) ./mtr –suite=funcs_1
suits目錄下有多個目錄,是一些測試的套餐。此指令單獨執行 suits/funcs_1目錄下的所有case。(其他目錄不執行)
t/目錄下的所有檔案組成了預設的套餐main。 是以 ./mtr –suite=main則隻執行t/*.test.
3) ./mtr –do-test=events
執行所有以 events為字首的case(搜尋範圍為t/和所有的suite)。
–do-test的參數支援正規表達式,上訴指令等效于./mtr –do-test=events.*
是以如果想測試所有的包括innodb的case,可以用 ./mtr –do-test=.*innodb.*
5、結束了
好吧。上面說的一些太簡單了,簡單到入門都不夠。需要詳細了解的可以到官網看e文。以下的tips是看官網的一些筆記。看到一點記錄一點的,不成章。
附錄
1、目錄下的mtr檔案即為mysql-test-run的縮寫,檔案内容相同
2、mtr會啟動mysql,有些case下可能重新開機server,為了使用不同的參數啟動
3、調用bin/mysqltest讀case并發送給mysql-server
4、輸入和輸出分别放在不同的檔案中,執行結果與輸出檔案内容作對比
5、輸入檔案都在t目錄下,輸出檔案在r目錄下。對應的輸入輸出檔案僅字尾名不同,分别為 *.test 和 *.result
6、每個testfile是一個測試用例,多個測試用例之間可能有關聯。 一個file中任何一個非預期的失敗都會導緻整個test停止(使用force參數則可繼續執行)。
7、注意如果服務端輸出了未過濾的warning或error,則會也會導緻test退出。
8、t目錄下的*.opt檔案是指在這個測試中,mysql必須以opt檔案的内容作為測試參數啟動。 *.sh檔案是在執行啟動mysql-server之前必須提前執行的腳本。disabled.def中定義了不執行的testfile。
9、r目錄下, 若一個執行輸出結果和testname.result檔案不同,會生成一個testname.reject檔案。 該檔案在下次執行成功之後被删除。
10、 include目錄是一些頭檔案,這些檔案在t/*.test檔案中使用,用source 指令引入
11、 lib目錄是一些庫函數,被mtr腳本調用
12、 有些測試需要用到一些标準資料,存在std_data目錄下。
13、 suite目錄也是一些測試用例,每個目錄下包含一套,./mtr –suite=funcs_1執行suits/funcs_1目錄下的所有case
14、 mtr實際調用mysqltest作測試。 –result-file檔案用于指定預定義的輸出,用于與實際輸出作對比。若同時指定了 –recored參數,則表示這個輸出資料不是用來對比的,而是要求将這個輸出結果寫入到指定的這個檔案中。
15、 mtr所在的目錄路徑中不能有空格
16、 mtr –force參數會跳過某個錯誤繼續執行,以檢視所有的錯誤。
17、 執行./mtr時另外啟動了一個mysql server,預設端口13000
18、 指定執行某個具體case使用 ./mtr testname, 會自動使用t/testname.rest
19、 ./mtr –do-test=events 執行以events開頭的所有case,包括events_grant.test 等
同理 –skip-test=events 将以events打頭的所有case跳過。 這裡支援正則表達是,如./mtr –do-test=.*innodb.*則會執行t/目錄下所有包含innodb子串的case
20、 mtr允許并行執行,端口會從13000開始使用。但需要特别指定不同的—vardir指定不同的日志目錄。但并行執行的case可能導緻寫同一個testname.reject.
21、 使用–parallel=auto可以多線程執行case。
22、 mtr對比結果使用簡單的diff,是以自己編寫的測試case不應該因為執行時間不同而導緻結果不同。當然架構在執行diff之前,允許自定義處理規則對得到的result作處理,來應對一些變化。
23、 ./mtr –record test_name 會将輸出結果存入檔案 r/test_name.result
24、 自己在腳本中建立的庫、表等資訊,要删除,否則會出warnning
25、 mtr預設使用的是mysql-test/var/my.cnf檔案,需要替換該檔案為自定義的配置檔案
26、 若要使用innodb引擎,必須明确在testname.test檔案頭加
– source include/have_innodb.inc
27、 test檔案名由字母數字、下劃線、中劃線組成,但隻能以字母數字打頭
28、 — sleep 10 等待10s 注意前面的—和後面沒有分号
29、 預設情況下, r/testname.result中會包含原語句和執行結果,若不想輸出原語句,需要自t/restname.test檔案頭使用 — disable_query_log
30、 每個單獨的case會重新開機服務并要求之前的資料是清空的。
31、 如果要測試出錯語句,必須在testname.test檔案中,在會出錯的語句之前加入 –error 錯誤号。
比如重複建立表,語句如下
create table t(c int) engine=innodb ;
– error 1050
這樣在testname.result中輸出
error 42s01: table ‘t’ already exists
則能夠正常通過
也可使用 er_table_exists_table (宏定義為1050)
也可使用 –error s42s01 (注意需要加字首s),但s系列的可能一個錯誤号對應多種錯誤。
32、 –enable_info 在testname.test檔案頭增加這個指令,在結果中會多輸出影響行數。
對應的關閉指令為 –disable_info
33、 enable_metadata 可以顯示更多資訊 disable_result_log不輸出執行結果
34、 有些case的輸出結果可能包含時間因素的影響,導緻無法重複驗證。–replace_column 可以解決部分情況。
–replace_column 1 xxxxx
select a, b from t;
輸出結果會是
xxxxx b.value
即将第一列固定替換為xxxxx。 注意,每個replace_column的影響範圍僅局限于下一行的第一個select語句。
35、 mtr –mysqld=–skip-innodb –mysqld=–key_buffer_size=16384 用這種将參數啟動傳遞給mysql server 。 每個選項必須有一個—mysqld打頭,不能連在一起寫,即使引号包含多個也不行。
36、 mysql-test-run.pl –combination=–skip-innodb –combination=–innodb,–innodb-file-per-table
這個指令是參數傳給多個test case, 第一個參數傳 skip-innodb, 第二個參數傳 –innodb, innodb-file-per-table。 若所有啟動參數中combination隻出現一次,則無效。
37、 skip-core-file 強行控制server不要core
38、 如果需要在server啟動前執行一些腳本,可以寫在 t/testname.sh檔案中,由mtr自動執行。
39、 等待語句
let $wait_condition= select c = 3 from t;
–source include/wait_condition.inc
mtr會一直停在 source這行,每隔0.1s檢測,直到上個語句中的$wait_condition傳回值非0
40、 主從測試case
a) testname.test頭部必須包含 souce include/master-slave.inc
b) 主庫的配置寫在testname-master.opt, 從庫的寫在testname-slave.opt
c) 對出從庫的操作統一寫在testname.test中,要對主庫操作時,先執行connection master,之後的語句都是在主庫上操作;同理connection slave;
從庫上執行start slave之後要執行 –source include/wait_for_slave_to_start.inc 等待啟動完成, 執行stop slave之後要執行–source include/wait_for_slave_to_stop.inc 等待停止完成。
41、 case完成後,mtr會檢測server的錯誤日志,如果裡面包含error或warning,則會認為case fail。
a) 如果要忽略整個驗證server日志的過程,可以在檔案頭增加 –nowarnings
b) 如果要指定忽略某些行,允許使用正規表達式,比如 call mtr.add_suppression(“the table ‘t[0-9]*’ is full”); 能夠忽略 the table ‘t12′ is full 這樣的warning
c) 注意上面這個call語句也是輸入語句的一部分,是以會輸出到結果内容中,除非
–disable_query_log
call mtr.add_suppression(“the table ‘t[0-9]*’ is full”);
–enable_query_log
42、 在一個case中重新開機server
–exec echo “wait” > $mysql_tmp_dir/mysqld.1.expect
–shutdown_server 10
–source include/wait_until_disconnected.inc
# do something while server is down
–enable_reconnect
–exec echo “restart” > $mysql_tmp_dir/mysqld.1.expect
–source include/wait_until_connected_again.inc
43、 循環語句文法
let $1= 1000;
while ($1)
{
# execute your statements here
dec $1;
}
44、 mysql_client_test是一個單獨的case,裡面包含了多個case, 但并不是寫成腳本,而是直接調用,可以直接從源碼中看到裡面調用的各個語句。源碼位置tests/mysql_client_test.c
45、 直接執行 ./mtr 會執行t/目錄下的所有case,外加suits目錄
46、 若在不同目錄中有重名的case,則會依次全部執行
47、 mysql-stress-test.pl用于壓力測試,注意預設的my.cnf中的參數,比如innodb_buffer_pool_size隻有128m
48、 case中的語句是以分号結尾的。echo a; select 1 from t limit 1; echo b;是三個語句,在result檔案中的對應輸出是 a \n 1 \n b
49、 testcase中支援的腳本語言函數
http://dev.mysql.com/doc/mysqltest/2.0/en/mysqltest-commands.html
沒有列出的函數可以用 – exec +shell指令實作
50、 設定變量 let $a = xx, 前面可加 –
51、 若是數字,可使用 inc $a / dec $a, 前面可加 –
52、 可以指派為sql傳回值 let $q = `select c from t limit 1`,
可以指派為系統變量 let $q = $path