天天看點

MySQL自動測試架構介紹

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