Shell腳本測試總結

本來出自:百度技術 的BLOG

 

1. 腳本測試的苦難

因爲腳本使用的自由度很大,對於程序員限制很少,功能實現的隨意性給測試帶來了不少困難。首先,很多Shell腳本編寫不規範,沒有同意的Shell腳本編程規範,其次,腳本參數配置與程序邏輯混雜,區分不清晰。往往腳本作者同時承擔多個開發任務,由於開發週期以及複雜的線上環境等原因,與其他腳本接口的溝通難以面面俱到,導致RD單元測試進行得很不充分。

2. 我們應該如何入手
首先,代碼走查結合動態單步跟蹤以及觀察日誌與文件輸出,網絡、CPU狀態。
然後,撰寫測試樁與驅動,白盒測試保證代碼邏輯中循環和分支都能夠走到,黑盒測試保證函數和功能腳本接口正確,輸入輸出符合設計預期。
對於異常處理,特別是變量的檢查需要特別關注,變量在使用前都需要進行檢查,是否爲空?或者爲0?對於文件名和路徑必須檢查,確認文件是否存在,路徑是否可達之後再進行後續操作。
另外,需要考慮所依賴的其他功能腳本以及二進制工具,這些功能性單元應該如何使用,調用後的返回會有哪些情況,對於正常和異常結果,腳本是否能夠捕捉到並且作出正確的判斷。

3. 靜態測試 && 動態測試
1) 新舊版本代碼對比
可以基於icafe平臺的codereview功能查看新舊版本的diff代碼行,對比升級點,及時與RD溝通確認,避免遺漏,保證測試的全面性。代碼對比的方式可能侷限性比較大,適用於兩個連續版本間代碼結構無大的改動的情況,很多情況下,新版本的腳本會與之前的版本完全不同,Shell腳本與C語言模塊有一個很大的區別就是,IM 模塊C 代碼的前後版本實現的承接關係很明顯,但是Shell腳本不一定,可能後來的RD會將之前版本的腳本完全推翻。代碼結構完全不同,因此在這種情況下,我們應該直接進入代碼走查環節。

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中讀取命令

“-n”可用於測試shell腳本是否存在語法錯誤,但不會實際執行命令。在shell腳本編寫完成之後,實際執行之前,首先使用“-n”選項來測試腳本是否存在語法錯誤是一個很好的習慣。因爲某些shell腳本在執行時會對系統環境產生影響,比如生成或移動文件等,如果在實際執行才發現語法錯誤,您不得不手工做一些系統環境的恢復工作才能繼續測試這個腳本。

“-c”選項使shell解釋器從一個字符串中而不是從一個文件中讀取並執行shell命令。當需要臨時測試一小段腳本的執行結果時,可以使用這個選項,如下所示:
sh -c 'a=1;b=2;let c=$a+$b;echo "c=$c"'

"-x"選項可用來跟蹤腳本的執行,是調試shell腳本的強有力工具。“-x”選項使shell在執行腳本的過程中把它實際執行的每一個命令行顯示出來,並且在行首顯示一個"+"號。 "+"號後面顯示的是經過了變量替換之後的命令行的內容,有助於分析實際執行的是什麼命令。 “-x”選項使用起來簡單方便,可以輕鬆對付大多數的shell調試任務,應把其當作首選的調試手段。

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 &“
【執行】
[[email protected] bin]$ ssh localhost "cat bin &"
cat: bin: Is a directory
[[email protected] bin]$ echo $?

【問題】:命令執行錯誤,返回值爲0 
【解決】將遠程命令放在前臺執行:
[[email protected] bin]$ ssh localhost "cat bin" 
cat: bin: Is a directory
[[email protected] bin]$ echo $?
1

 

8) 變量使用前使用unset清理
【腳本內容】:一般是針對腳本的配置文件 
ps_retras[0]="[email protected]"
ps_retras[1]="[email protected]"
【問題】:如果OP修改ps_retras數組的配置,可能無法生效
【解決】使用unset進行清理 
unset
功能說明:刪除變量或函數。 
語法:unset [-fv][變量或函數名稱] 
參數: 
-f 僅刪除函數。 
-v 僅刪除變量。 
例如:unset ps_retras
ps_retras[0]="[email protected]"
ps_retras[1]=“[email protected]

 

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 時顯示行號還有函數名稱的目的了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章