1. 腳本測試的苦難
由於腳本使用的自由度很大,對於程序員限制不多,功能實現的隨意性給測試帶來了很多困難。首先,不少Shell腳本編寫不規範,沒有贊成的Shell腳本編程規範,其次,腳本參數配置與程序邏輯混雜,區分不清晰。每每腳本做者同時承擔多個開發任務,因爲開發週期以及複雜的線上環境等緣由,與其餘腳本接口的溝通難以面面俱到,致使RD單元測試進行得很不充分。html
2. 咱們應該如何入手
首先,代碼走查結合動態單步跟蹤以及觀察日誌與文件輸出,網絡、CPU狀態。
而後,撰寫測試樁與驅動,白盒測試保證代碼邏輯中循環和分支都可以走到,黑盒測試保證函數和功能腳本接口正確,輸入輸出符合設計預期。
對於異常處理,特別是變量的檢查須要特別關注,變量在使用前都須要進行檢查,是否爲空?或者爲0?對於文件名和路徑必須檢查,確認文件是否存在,路徑是否可達以後再進行後續操做。
另外,須要考慮所依賴的其餘功能腳本以及二進制工具,這些功能性單元應該如何使用,調用後的返回會有哪些狀況,對於正常和異常結果,腳本是否可以捕捉到而且做出正確的判斷。程序員
3. 靜態測試 && 動態測試
1) 新舊版本代碼對比
能夠基於icafe平臺的codereview功能查看新舊版本的diff代碼行,對比升級點,及時與RD溝通確認,避免遺漏,保證測試的全面性。代碼對比的方式可能侷限性比較大,適用於兩個連續版本間代碼結構無大的改動的狀況,不少狀況下,新版本的腳本會與以前的版本徹底不一樣,Shell腳本與C語言模塊有一個很大的區別就是,IM 模塊C 代碼的先後版本實現的承接關係很明顯,可是Shell腳本不必定,可能後來的RD會將以前版本的腳本徹底推翻。代碼結構徹底不一樣,所以在這種狀況下,咱們應該直接進入代碼走查環節。shell
2) 代碼走查
全面、深刻、細緻地關注腳本分支、循環邏輯正確性。
例如:retrbs重啓腳本,在重啓PS平臺全部retrbs以後,須要清理PS平臺retras cache,新增的啓動方式升級分紅兩種啓動方式,normal與continue模式,實際在codereview時發現normal方式重啓完成後清理cache,continue方式重啓完成後直接退出,這確定是有問題的,由於按正常邏輯來講,無論那種啓動方式,在重啓完成以後都須要清理cache。編程
3) 搭建環境
搭建環境須要瞭解腳本的運行場景,運行頻率,環境依賴以及與其配合的上下文腳本及程序:
腳本執行時所處的目錄和配置文件
對應的產品模塊功能
數據的週期性更新
server間的ssh認證
網絡通訊端口檢查
腳本中的使用的工具
腳本硬件要求
好比說:腳本在什麼目錄下執行,天天幾點鐘執行,執行的時候須要什麼數據以及工具提早準備好,等等。數組
4. 如何調試Shell腳本
1) 檢查語法錯誤:
通常來講咱們能夠經過修改shell腳本的源代碼,令其輸出相關的調試信息來定位錯誤,那有沒有不修改源代碼來調試shell腳本的方法呢?答案就是使用shell的執行選,下面是一些經常使用選項的用法:
-n 只讀取shell腳本,但不實際執行
-x 進入跟蹤方式,顯示所執行的每一條命令
-c "string" 從strings中讀取命令bash
「-n」可用於測試shell腳本是否存在語法錯誤,但不會實際執行命令。在shell腳本編寫完成以後,實際執行以前,首先使用「-n」選項來測試腳本是否存在語法錯誤是一個很好的習慣。由於某些shell腳本在執行時會對系統環境產生影響,好比生成或移動文件等,若是在實際執行才發現語法錯誤,您不得不手工作一些系統環境的恢復工做才能繼續測試這個腳本。網絡
「-c」選項使shell解釋器從一個字符串中而不是從一個文件中讀取並執行shell命令。當須要臨時測試一小段腳本的執行結果時,可使用這個選項,以下所示:
sh -c 'a=1;b=2;let c=$a+$b;echo "c=$c"'ssh
"-x"選項可用來跟蹤腳本的執行,是調試shell腳本的強有力工具。「-x」選項使shell在執行腳本的過程當中把它實際執行的每個命令行顯示出來,而且在行首顯示一個"+"號。 "+"號後面顯示的是通過了變量替換以後的命令行的內容,有助於分析實際執行的是什麼命令。 「-x」選項使用起來簡單方便,能夠輕鬆對付大多數的shell調試任務,應把其看成首選的調試手段。ide
2) 調試工具-bashdb
使用shell調試器bashdb,這是一個相似於GDB的調試工具,能夠完成對shell腳本的斷點設置,單步執行,變量觀察等許多功能。函數
使用bashdb進行debug的經常使用命令
1.列出代碼和查詢代碼類:
l 列出當前行如下的10行
- 列出正在執行的代碼行的前面10行
. 回到正在執行的代碼行
w 列出正在執行的代碼行先後的代碼
/pat/ 向後搜索pat
?pat?向前搜索pat
2.Debug控制類:
h 幫助
help 命令 獲得命令的具體信息
q 退出bashdb
x 算數表達式 計算算數表達式的值,並顯示出來
!!空格Shell命令 參數 執行shell命令
使用bashdb進行debug的經常使用命令(cont.)
控制腳本執行類:
n 執行下一條語句,遇到函數,不進入函數裏面執行,將函數看成黑盒
s n 單步執行n次,遇到函數進入函數裏面
b 行號n 在行號n處設置斷點
del 行號n 撤銷行號n處的斷點
c 行號n 一直執行到行號n處
R 從新啓動
Finish 執行到程序最後
cond n expr 條件斷點
5. 腳本測試的基本流程
1.靜態代碼檢查
2.單元測試1:針對每一個功能函數撰寫驅動和樁,驗證全部分支
• 確認每一個配置項以及設計的文件目錄是否在使用前進行檢查
• 確認全部的變量沒有向外傳播的危險
• 確認所產出的臨時文件沒有泄露,腳本本身會負責處理掉臨時文件
3.單元測試2:對於單個功能腳本sh -x XXX.sh 跟蹤腳本執行狀況
4.集成測試1:對於全部腳本使用sh -x XXX.sh 跟蹤腳本執行狀況
5.集成測試2:模擬腳本生產環境,週期性連續屢次執行所有功能腳本,監控腳本性能以及日誌、臨時文件等狀態。
6. 腳本測試中遇到的問題和解決方案
1) 判斷一個數組是否爲空:
【腳本內容】:
if [ -z ${pg_readyDatalist[@]} ]
then
…………
fi
【問題】:不可如此判斷,超過一個元素時,語法錯誤
【sh -x 執行】:
+ '[' -z model gtrindex ']'
retrbs_restart.sh: line 366: [: model: binary operator expected
【緣由】:
-z 只能判斷一個變量是否爲空
判斷一個list是否爲空,應該:
【解決】判斷list元素個數是否爲0
例如: if [ ${#ps_retrbs[@]} -eq 0 ]
2) If語句判斷
【腳本內容】:
if [ -f ./$i]
then
echo "test"
fi
【問題】: .$i] 的「]」前面沒有空格,形成語法錯誤
【sh -x 執行】:./test.sh: line 3: [: missing `]
【緣由】: If語句的條件判斷「[ ]」,「[」以後和「]」以前必須有空格
【解決】加上空格
3) 字符串判斷
【腳本內容】:
if [ "$1" = "continue" ] then
echo 「succ」
fi
【問題】:$1爲空,打印「succ」
【sh -x 執行】:succ
【緣由】: $1爲空會形成語法錯誤,返回0,繼續執行if代碼塊中的邏輯,致使判斷錯誤
【解決】修改爲 if [ "a$1" = "acontinue" ]
4) 變量傳播
【腳本內容】:
func(){
for((i=0;i<$RETRY_TIMES;i++))
do
NOTICE "delBlacklist」
done
}
for (( i=0; i<pggroup_size; i++))
do
func()
done
【問題】:「i」的值自增以後會傳遞到外層調用腳本,致使外層調用腳本的循環跳過或死循環
【解決】避免使用i,j,k等常見的循環控制變量,使用自定義的變量名,如retry_count等
在shell函數中定義的變量加上local關鍵字
5) 命令鏈接
問題一:
【腳本內容】:
cd to_del; rm -rf *
【問題】:若是cd 目錄失敗,rm -rf * 會錯誤地刪除當前目錄下的全部文件
【解決】使用 && 鏈接 cd失敗將不會繼續執行後面的命令
問題二:
【腳本內容】:
for data in ${datalist{@}}
do
runRemoteCmd ${host} "cd ${data_path}.new && [[ -f ${data_flag} ]]" || suc=0 && break
done
【問題】:這裏的 || && 是同一個優先級
那麼就是說 && 後面的語句 break不管什麼狀況下都不可能被執行到
【解決】拆成兩條語句,單獨判斷suc
6) 文件泄露
【腳本內容】:
local status=$( mySsh ${remote_host} "{ ${command%%;}; }&>/tmp/$$ && echo 0 || echo 1" )
【問題】:上述代碼將遠程執行命令行的輸出結果導入到一個以pid命名的臨時文件中,在腳本關閉的時候沒有清除,每一次執行將建立一個新文件,極可能致使文件泄露問題。
【解決】注意清理腳本生成的臨時文件
7) ssh 遠程執行後臺命令不靠譜
【腳本內容】:
ssh hostname "cat bin &「
【執行】
[work@www.baidu.com bin]$ ssh localhost "cat bin &"
cat: bin: Is a directory
[work@www.baidu.com bin]$ echo $?
0
【問題】:命令執行錯誤,返回值爲0
【解決】將遠程命令放在前臺執行:
[work@www.baidu.com bin]$ ssh localhost "cat bin"
cat: bin: Is a directory
[work@www.baidu.com bin]$ echo $?
1
8) 變量使用前使用unset清理
【腳本內容】:通常是針對腳本的配置文件
ps_retras[0]="work@www.baidu.com"
ps_retras[1]="work@www.baidu.com"
【問題】:若是OP修改ps_retras數組的配置,可能沒法生效
【解決】使用unset進行清理
unset
功能說明:刪除變量或函數。
語法:unset [-fv][變量或函數名稱]
參數:
-f 僅刪除函數。
-v 僅刪除變量。
例如:unset ps_retras
ps_retras[0]="work@www.baidu.com"
ps_retras[1]=「work@www.baidu.com「
7. shell 內置變量
1) $FUNCNAME
函數的名字,相似於C語言中的內置宏__func__,但宏__func__ 只能表明當前所在的函數名,而$FUNCNAME的功能更強大,它是一個數組變量,其中包含了整個調用鏈上全部的函數的名字,故變量${FUNCNAME [0]}表明shell腳本當前正在執行的函數的名字,而變量${FUNCNAME[1]}則表明調用函數${FUNCNAME[0]}的函數的名字,依此類推。
2) $BASH_SOURCE
shell腳本源文件名,與FUNCNAME相對應
3) $BASH_LINENO
表明shell腳本的當前行號,相似於C語言中的內置宏__LINE__,與FUNCNAME相關聯
BASH_LINENO[$i] 指示的是 FUNCNAME[$i + 1]被調用的位置
4) $PS4
第四級提示符變量$PS4 , $PS4的值將被顯示在「-x」選項輸出的每一條命令的前面。在Bash Shell中,缺省的$PS4的值是"+"號。(如今知道爲何使用"-x"選項時,輸出的命令前面有一個"+"號了吧 )
經過修改$PS4的值,就能夠達到sh –x 時顯示行號還有函數名稱的目的了。
(做者:zliang)