《Redis內(nèi)核單元測試框架》要點(diǎn):
本文介紹了Redis內(nèi)核單元測試框架,希望對您有用。如果有疑問,可以聯(lián)系我們。
在修改Redis內(nèi)核之后,第一步我們必要做的就是添加或者對應(yīng)的單元測試用例來進(jìn)行基本的單元測試.本文將對Redis內(nèi)核單元測試框架進(jìn)行基本的解析,并對如何編寫測試用例進(jìn)行基本的講解.
單元測試框架流程
Redis單元測試框架是基于tcl sh腳本實(shí)現(xiàn)的,其啟動的方式為runtest [options].
每一類的測試case寫在單獨(dú)的測試文件中,測試文件列表寫入到test_server中all_tests列表中.
在啟動測試時(shí),會以server模式啟動一個(gè)測試服務(wù)器,再啟動多個(gè)測試客戶端與之通信.由測試服務(wù)器會給空閑的測試服務(wù)端發(fā)送測試任務(wù),參數(shù)為測試用例所在腳本文件名,由測試客戶端執(zhí)行對應(yīng)的測試用例.詳細(xì)的流程圖如下:
1. processOptions
對選項(xiàng)進(jìn)行解析,其中默認(rèn)的模式是server模式,進(jìn)入test_server_main函數(shù); 若帶了client選項(xiàng)則進(jìn)入test_client_main函數(shù).
2. test_server_main
內(nèi)部維護(hù)了一系列當(dāng)前的測試客戶端狀態(tài)列表;
accept_test_clients 創(chuàng)建一個(gè)socket fd,偵聽來自測試客戶端的消息;
依照傳入的參數(shù),以runtest --client的方式啟動n個(gè)測試client;
3. test_client_main
啟動測試客戶端,往測試服務(wù)器的fd上發(fā)送ready消息,開啟客戶端與服務(wù)端的交互流程;
4. 客戶端與服務(wù)端的交互
客戶端啟動后,往測試服務(wù)器fd上發(fā)送ready消息;
服務(wù)端收到客戶端ready或done事件后,檢查所有的測試集,若還有測試任務(wù)未完成,則使用signal_idle_client辦法往測試客戶端發(fā)送測試任務(wù),即"run 測試用例腳本文件名"消息;
客戶端收到run消息后,調(diào)用execute_tests $data辦法執(zhí)行測試用例腳本文件;
客戶端執(zhí)行完測試case腳本后,往服務(wù)端發(fā)送done事件.再次進(jìn)入第2步;
測試用例編寫
1. 增加測試用例
新建一個(gè)測試用例文件,好比dummy.tcl,將之加入到test_helper.tcl的all_tests列表里
set ::all_tests {
unit/auth ...
unit/dummy ...}
這樣啟動測試的時(shí)候,會自動執(zhí)行unit/dummy.tcl里面的測試用例;
2. 測試用例文件
每個(gè)測試用例文件里面可以包括多個(gè)start_server的部分,每個(gè)start_server都會啟動一個(gè)redis實(shí)例.
每個(gè)start_server內(nèi)部包括多個(gè)test函數(shù)模塊,每個(gè)test函數(shù)對應(yīng)一個(gè)測試用例.
例子: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實(shí)例
啟動單個(gè)實(shí)例
使用start_server可以啟動一個(gè)redis實(shí)例. 啟動的時(shí)候接受三種類型的參數(shù):
1. config: redis server的配置文件名,文件放到tests/assets目錄下;
2. override: 覆蓋配置文件中的某個(gè)具體配置;
3. tags: 該server的標(biāo)示,一般用于log輸出;
啟動一個(gè)redis實(shí)例的例子可以見上一節(jié)的auth.tcl.
啟動多個(gè)實(shí)例
在進(jìn)行主從同步測試,集群測試的時(shí)候,必要同時(shí)起多個(gè)redis實(shí)例,直接在一個(gè)test_server內(nèi)部,再執(zhí)行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. 執(zhí)行redis命令
測試case中執(zhí)行redis命令用r函數(shù).(s函數(shù)與r函數(shù)類似,只是s函數(shù)會從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
}
當(dāng)同時(shí)啟動多個(gè)redis實(shí)例時(shí),使用r函數(shù)的第一個(gè)參數(shù),標(biāo)示具體在哪個(gè)實(shí)例上執(zhí)行對應(yīng)的命令.0為當(dāng)前redis實(shí)例,-1為上一個(gè)啟動的redis實(shí)例,以此類推.例如:
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. 結(jié)果判斷
結(jié)果判斷有幾種方式:
assert類:詳見support/test.tcl
fail "comment": 失敗
test函數(shù)最后一個(gè)參數(shù),支持正則表達(dá)式.其匹配的對象是最后一條redis命令返回的結(jié)果.例如:
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. 同步等待函數(shù)
wait_for_condition {maxtries delay e else elsescript}函數(shù)可以同步等待指定條件被滿足.例:
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. 隨機(jī)生成數(shù)據(jù)
start_write_load {host port seconds}函數(shù)可以不停的往實(shí)例中寫入數(shù)據(jù).
8. 一個(gè)稍復(fù)雜的例子
下面是一個(gè)主從同步的例子,作為這一節(jié)的結(jié)束和測試.
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}
}
}
}
}
}
}
總結(jié)
Redis內(nèi)核自動化測試框架可以同時(shí)啟動多個(gè)測試客戶端進(jìn)行測試,其測試用例編寫簡便,測試效率高,使用起來非常便利.
該測試框架也可以很便利的改造成其它基于socket通信的服務(wù)的自動化測試框架.
簡約,高效,這便是我對它的印象.
維易PHP培訓(xùn)學(xué)院每天發(fā)布《Redis內(nèi)核單元測試框架》等實(shí)戰(zhàn)技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養(yǎng)人才。
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/9616.html