《Redis內核單元測試框架》要點:
本文介紹了Redis內核單元測試框架,希望對您有用。如果有疑問,可以聯系我們。
在修改Redis內核之后,第一步我們必要做的就是添加或者對應的單元測試用例來進行基本的單元測試.本文將對Redis內核單元測試框架進行基本的解析,并對如何編寫測試用例進行基本的講解.
單元測試框架流程
Redis單元測試框架是基于tcl sh腳本實現的,其啟動的方式為runtest [options].
每一類的測試case寫在單獨的測試文件中,測試文件列表寫入到test_server中all_tests列表中.
在啟動測試時,會以server模式啟動一個測試服務器,再啟動多個測試客戶端與之通信.由測試服務器會給空閑的測試服務端發送測試任務,參數為測試用例所在腳本文件名,由測試客戶端執行對應的測試用例.詳細的流程圖如下:
1. processOptions
對選項進行解析,其中默認的模式是server模式,進入test_server_main函數; 若帶了client選項則進入test_client_main函數.
2. test_server_main
內部維護了一系列當前的測試客戶端狀態列表;
accept_test_clients 創建一個socket fd,偵聽來自測試客戶端的消息;
依照傳入的參數,以runtest --client的方式啟動n個測試client;
3. test_client_main
啟動測試客戶端,往測試服務器的fd上發送ready消息,開啟客戶端與服務端的交互流程;
4. 客戶端與服務端的交互
客戶端啟動后,往測試服務器fd上發送ready消息;
服務端收到客戶端ready或done事件后,檢查所有的測試集,若還有測試任務未完成,則使用signal_idle_client辦法往測試客戶端發送測試任務,即"run 測試用例腳本文件名"消息;
客戶端收到run消息后,調用execute_tests $data辦法執行測試用例腳本文件;
客戶端執行完測試case腳本后,往服務端發送done事件.再次進入第2步;
測試用例編寫
1. 增加測試用例
新建一個測試用例文件,好比dummy.tcl,將之加入到test_helper.tcl的all_tests列表里
set ::all_tests {
unit/auth ...
unit/dummy ...}
這樣啟動測試的時候,會自動執行unit/dummy.tcl里面的測試用例;
2. 測試用例文件
每個測試用例文件里面可以包括多個start_server的部分,每個start_server都會啟動一個redis實例.
每個start_server內部包括多個test函數模塊,每個test函數對應一個測試用例.
例子:auth.tcl
start_server {tags {"auth"}} {
test {AUTH fails if there is no password configured server side} {
catch {r auth foo} err
set _ $err
} {ERR*no password*}
}
start_server {tags {"auth"} overrides {requirepass foobar}} {
test {AUTH fails when a wrong password is given} {
catch {r auth wrong!} err
set _ $err
} {ERR*invalid password}
test {Arbitrary command gives an error when AUTH is required} {
catch {r set foo bar} err
set _ $err
} {NOAUTH*}
test {AUTH succeeds when the right password is given} {
r auth foobar
} {OK}
test {Once AUTH succeeded we can actually send commands to the server} {
r set foo 100
r incr foo
} {101}
}
3. 啟動redis實例
啟動單個實例
使用start_server可以啟動一個redis實例. 啟動的時候接受三種類型的參數:
1. config: redis server的配置文件名,文件放到tests/assets目錄下;
2. override: 覆蓋配置文件中的某個具體配置;
3. tags: 該server的標示,一般用于log輸出;
啟動一個redis實例的例子可以見上一節的auth.tcl.
啟動多個實例
在進行主從同步測試,集群測試的時候,必要同時起多個redis實例,直接在一個test_server內部,再執行test_server即可.
例子:
start_server {tags {"repl"}} {
start_server {} {
test {First server should have role slave after SLAVEOF} {
r -1 slaveof [srv 0 host] [srv 0 port]
after 1000
s -1 role
} {slave}
}
}
4. 執行redis命令
測試case中執行redis命令用r函數.(s函數與r函數類似,只是s函數會從info中提取返回值)
proc r {args} { set level 0
if {[string is integer [lindex $args 0]]} { set level [lindex $args 0] set args [lrange $args 1 end]
}
[srv $level "client"] {*}$args
}
當同時啟動多個redis實例時,使用r函數的第一個參數,標示具體在哪個實例上執行對應的命令.0為當前redis實例,-1為上一個啟動的redis實例,以此類推.例如:
start_server {tags {"repl"}} {
r set mykey foo
start_server {} {
test {Second server should have role master at first} {
s role
} {master}
test {SLAVEOF should start with link status "down"} {
r slaveof [srv -1 host] [srv -1 port]
s master_link_status
} {down}
}
}
5. 結果判斷
結果判斷有幾種方式:
assert類:詳見support/test.tcl
fail "comment": 失敗
test函數最后一個參數,支持正則表達式.其匹配的對象是最后一條redis命令返回的結果.例如:
test {AUTH fails when a wrong password is given} {catch {r auth wrong!} err
set _ $err
} {ERR*invalid password}test {Arbitrary command gives an error when AUTH is required} {
catch {r set foo bar} err
set _ $err
} {NOAUTH*}
6. 同步等待函數
wait_for_condition {maxtries delay e else elsescript}函數可以同步等待指定條件被滿足.例:
test "Fixed AOF: Keyspace should contain values that were parseable" { set client [redis [dict get $srv host] [dict get $srv port]]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
fail "Loading DB is taking too much time."
}
assert_equal "hello" [$client get foo]
assert_equal "" [$client get bar]
}
7. 隨機生成數據
start_write_load {host port seconds}函數可以不停的往實例中寫入數據.
8. 一個稍復雜的例子
下面是一個主從同步的例子,作為這一節的結束和測試.
foreach dl {no yes} {
start_server {tags {"repl"}} { set master [srv 0 client]
$master config set repl-diskless-sync $dl set master_host [srv 0 host] set master_port [srv 0 port] set slaves {} set load_handle0 [start_write_load $master_host $master_port 3] set load_handle1 [start_write_load $master_host $master_port 5] set load_handle2 [start_write_load $master_host $master_port 20] set load_handle3 [start_write_load $master_host $master_port 8] set load_handle4 [start_write_load $master_host $master_port 4]
start_server {} { lappend slaves [srv 0 client]
start_server {} { lappend slaves [srv 0 client]
start_server {} { lappend slaves [srv 0 client]
test "Connect multiple slaves at the same time (issue #141), diskless=$dl" { # Send SALVEOF commands to slaves
[lindex $slaves 0] slaveof $master_host $master_port
[lindex $slaves 1] slaveof $master_host $master_port
[lindex $slaves 2] slaveof $master_host $master_port # Wait for all the three slaves to reach the "online" # state from the POV of the master.
set retry 500
while {$retry} { set info [r -3 info] if {[string match {*slave0:*state=online*slave1:*state=online*slave2:*state=online*} $info]} { break
} else { incr retry -1
after 100
}
} if {$retry == 0} { error "assertion:Slaves not correctly synchronized"
} # Wait that slaves acknowledge they are online so # we are sure that DBSIZE and DEBUG DIGEST will not # fail because of timing issues.
wait_for_condition 500 100 {
[lindex [[lindex $slaves 0] role] 3] eq {connected} &&
[lindex [[lindex $slaves 1] role] 3] eq {connected} &&
[lindex [[lindex $slaves 2] role] 3] eq {connected}
} else {
fail "Slaves still not connected after some time"
} # Stop the write load
stop_write_load $load_handle0
stop_write_load $load_handle1
stop_write_load $load_handle2
stop_write_load $load_handle3
stop_write_load $load_handle4 # Make sure that slaves and master have same # number of keys
wait_for_condition 500 100 {
[$master dbsize] == [[lindex $slaves 0] dbsize] &&
[$master dbsize] == [[lindex $slaves 1] dbsize] &&
[$master dbsize] == [[lindex $slaves 2] dbsize]
} else {
fail "Different number of keys between masted and slave after too long time."
} # Check digests
set digest [$master debug digest] set digest0 [[lindex $slaves 0] debug digest] set digest1 [[lindex $slaves 1] debug digest] set digest2 [[lindex $slaves 2] debug digest]
assert {$digest ne 0000000000000000000000000000000000000000}
assert {$digest eq $digest0}
assert {$digest eq $digest1}
assert {$digest eq $digest2}
}
}
}
}
}
}
總結
Redis內核自動化測試框架可以同時啟動多個測試客戶端進行測試,其測試用例編寫簡便,測試效率高,使用起來非常便利.
該測試框架也可以很便利的改造成其它基于socket通信的服務的自動化測試框架.
簡約,高效,這便是我對它的印象.
維易PHP培訓學院每天發布《Redis內核單元測試框架》等實戰技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養人才。
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/9616.html