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)