如何进行shell脚本正确性测试

来源:互联网 发布:网络电视看cctv5的apk 编辑:程序博客网 时间:2024/06/05 19:32

转自:http://blog.csdn.net/wklken/article/details/7983054

转载请注明出处:http://blog.csdn.net/wklken

----------------------------------------------------------------------------------------

在实际工作中,需要对shell脚本进行正确性测试。

如何用最快最有效的方式进行测试?


很多开发的习惯是,二话不说,写完/拿到,就跑一把,看看输入,输出,想要的操作是否完成,也就过了。

其实这是十分不严谨的,若是未经过QA,风险还是相当大的。


以下即shell脚本测试流程,仅供参考

1.代码走读:

    写完,或者拿到一个shell脚本,不必急于运行,虽然实践是检验整理的唯一标准,但是,在读代码这个过程中,可以规避很多低级的bug.

    读什么?

    A.代码逻辑,这个脚本用来做什么,主要分为多少步,分别做了什么事情?

        用于检查是否有遗漏逻辑,或有悖于需求。

    B.具体语法,变量,判断语句

        语法方面的东西,变量是否定义,判断语句逻辑是否正确,是否考虑各种异常,错误是否退出,返回正确状态值等。


2.语法检测:

    shell的语法还是相当让人无语的,很多很容易疏忽遗漏的地方

    命令格式: sh -n ***.sh 

    

    若是没有异常输出,证明脚本没有明显的语法问题。


3.运行跟踪:

   实践是检验整理的唯一标准,跑一把。

   不过,可不是直接运行然后去看最终结果,这样会遗漏掉很多中间过程。

   命令格式: sh -vx ***.sh

   得到效果如下:


我们可以看到

每行代码原始命令(无+的):[这是-v的效果]

代码执行时的情况(带+),包括运算结果,逻辑判断结果,变量赋值等等[-x的效果]

而我们所要关注的就是这些信息,主要是变量值和逻辑判断结果。


4.覆盖分支:

   直接跑,只能覆盖到主体流程,对于其他控制流分支的代码是无法覆盖到的。

   对于关键性的,重点的逻辑,我们需要制造条件,使运行脚本可以进入对应分支


5.其他:

   A.关于bashdb:

      可以尝试下,但是感觉投入产出比不高

   B.关于单元测试:

      实际工作中,由于项目压力比较大,单元测试的成本还是相当高的,所以目前为止没有。


6.有没有更好的方式?

    好吧,单步跟踪,脚本短的还好,日志信息不会太多,要是多了,存在调用其他脚本等等.....

    日志量达到几千行,这是很轻易的事情。

    跟踪过的童鞋有同感,展现不够友好,惨白惨白一片,一千行下来,看的眼花。

    很容易遗漏(LZ被坑了好多回,你看,或不看......错误信息明明就在那里,就是视而不见)


    So.进行了一层优化,对日志进行处理,使用正则,标注我关心的信息

    效果图对比:

    

处理后:(对错误,关键信息进行颜色标记,在linux终端可以显示)

   

      

     脚本是用python实现的,位置:https://github.com/wklken/pytools/tree/master/shell

       思想是:执行,抓到所有日志,用正则进行匹配,打上颜色,然后输出

     欢迎一起优化,使之功能更完善


代码:

[python] view plaincopyprint?
  1. #!/bin/env python  
  2. #-*- coding:utf-8 -*-  
  3. #@Author: wklken  
  4. #@Mail: wklken@yeah.net ,lingyue.wkl@taobao.com  
  5. #@Date: 20120706  
  6. #@Version: 0.1  sh -n, check for static syntax  
  7. #          0.2  sh -vx, color the output log which i care about  
  8. #          0.2.1 rebuild all functions , lines  200+ -> 120  
  9. #          0.2.2 refine the re pattern.  
  10. #          0.2.3 add sh params support. fix bug and add re patterns  
  11. #          0.2.5 add warn/error pattern and collect the result  
  12. #          0.2.6 use decorator to refine print, refine collect method  
  13.   
  14. #@Desc: Quick test shell script.The target is hacking into it and get all the status i need.  
  15. #TODO: need to keep source code in 200 lines! refine!  
  16.   
  17. import sys,os  
  18. import commands  
  19. import re  
  20.   
  21. #color defined  
  22. COLOR_NONE = "C_NONE"  
  23. COLOR_GREEN = "C_G"  
  24. COLOR_RED = "C_R"  
  25. COLOR_YELLOW = "C_Y"  
  26. COLOR_PURPLE = "C_P"  
  27. COLOR_BLUE = "C_B"  
  28.   
  29. COLOR_MAP = {COLOR_NONE : "\033[m",  
  30.              COLOR_GREEN : "\033[01;32m",  
  31.              COLOR_RED : "\033[01;31m",  
  32.              COLOR_YELLOW : "\033[01;33m",  
  33.              COLOR_PURPLE : "\033[01;35m",  
  34.              COLOR_BLUE : "\033[01;34m",  
  35.              None:"\033[m" }  
  36.   
  37. #the command used defined  
  38. SH_N = "sh -n "  
  39. SH_X = "sh -vx "  
  40. LOG_BEGIN = "export PS4='+${BASH_SOURCE}|${LINENO}|${FUNCNAME[0]} -> ';"  
  41. LOG_BEGIN = ""  
  42.   
  43. #the type of output log line  
  44. LINE_TYPE_CMD = "CMD"  
  45. LINE_TYPE_EXC = "EXC"  
  46. LINE_TYPE_CMT = "CMT"  
  47.   
  48. CMD_Y = COLOR_MAP.get(COLOR_YELLOW) + "CMD: " + COLOR_MAP.get(COLOR_NONE)  
  49.   
  50. #----------pattern used to match begin -----------------------------  
  51. #0. special  
  52. PATTERN_ADDSIGN = re.compile("(^\++)")  
  53.   
  54. #1. execute command log match pattern  
  55. exc_mark_pattern = [(r"([\[\]])", COLOR_YELLOW), #for condition testing   must be the first one  
  56.                     (r"(([12]\d{3})(1[12]|0[1-9])(0[1-9]|1\d|2\d|3[01]))",COLOR_PURPLE), #date yyyyMMDD  
  57.                     (r"(tbsc-dev)", COLOR_RED),  # path: tbsc-dev  
  58.                     (r"([a-zA-Z_][a-zA-Z0-9_]*=[\s|\"\"]*)$",COLOR_RED),   # params=None  
  59.                     (r"(exit\s+-?\d*|return\s+-?\d*)",COLOR_BLUE), #exit status  
  60.                     (r"(\s(\-[acbdefgnorsuwxzL]|\-(lt|le|gt|ge|eq|ne))\s)", COLOR_YELLOW),  
  61.                     (r"((\s(=|==|<=|>=|\+=|<|>|'!='|\&\&)\s)|'!')", COLOR_YELLOW),  
  62.                     (r"(\s(\-input|\-output|\-i|\-o)\s)", COLOR_YELLOW),  
  63.                     ]  
  64. EXC_MARK_PATTERN = [(re.compile(s),color) for s,color in exc_mark_pattern]  
  65.   
  66. #2. error/warn result log match pattern  
  67. # 100% error  
  68. error_mark_pattern = [(r"(No such file or directory|command not found|unknown option|invalid option)",COLOR_RED), #result -> file not found  
  69.                     (r"(unary operator expected)",COLOR_RED), # test failed  
  70.                     (r"(Permission denied)",COLOR_RED),  
  71.                     (r"(syntax error|unexpected|read error)",COLOR_RED),  
  72.                     (r"(java.io.FileNotFoundException|org.apache.hadoop.mapred.InvalidInputException|java.lang.IllegalMonitorStateException)", COLOR_RED),#javaerror  
  73.                     ]  
  74. ERROR_MARK_PATTERN = [(re.compile(s),color) for s,color in error_mark_pattern]  
  75.   
  76. # may be not error ,just warn,notice  
  77. warn_mark_pattern = []  
  78. WARN_MARK_PATTERN = [(re.compile(s),color) for s,color in warn_mark_pattern]  
  79.   
  80. #3. command log match pattern  
  81. cmd_mark_pattern = error_mark_pattern + warn_mark_pattern + \  
  82.                     [  
  83.                     (r"(line \d+)", COLOR_RED), #error report the line No  
  84.                     (r"(\$(\{\w+\}))", COLOR_PURPLE),  
  85.                     (r"(\.\.)",COLOR_PURPLE), #相对路径  
  86.                     (r"((?<!-)\b(\w+)\b=)", COLOR_YELLOW),  
  87.                     (r"(\$(\w+))", COLOR_PURPLE), #变量名  
  88.                     (r"(\w+\.sh\s*)", COLOR_GREEN), #*.sh  
  89.                     (r"(`)", COLOR_GREEN),  # ``  
  90.                     (r"(\s?\w+\s*\(\))", COLOR_GREEN), #function()  
  91.                     (r"(\{\s*$|^\}\s*$)", COLOR_GREEN), # function {}  
  92.                     (r"(^export\s|^source\s)", COLOR_YELLOW),  
  93.                     (r"(\|)", COLOR_GREEN),  
  94.                     (r"(<<|>>|<|>)", COLOR_YELLOW),  
  95.                     ]  
  96. CMD_MARK_PATTERN = [(re.compile(s),color) for s,color in cmd_mark_pattern]  
  97. #----------pattern used to match end -----------------------------  
  98.   
  99. #static params defined  
  100. error_lines = []  
  101.   
  102. #functions begin  
  103. def str_coloring(str_info, color=COLOR_NONE):  
  104.     """color str"""  
  105.     return COLOR_MAP.get(color, COLOR_MAP.get(None)) + str_info + COLOR_MAP.get(COLOR_NONE)  
  106.   
  107. def print_symbol(str_info):  
  108.     """print the symbol"""  
  109.     print "-"*20 + str_info + "-"*20  
  110.   
  111. def wrap_print_func(arg):  
  112.     """wrap func, print begin and end sign"""  
  113.     def  newfunc(func):  
  114.         def newfunc_withparams(*args, **kwargs):  
  115.             print_symbol(arg+" BEGIN")  
  116.             func(*args, **kwargs)  
  117.             print_symbol(arg+" END")  
  118.         return newfunc_withparams  
  119.     return newfunc  
  120.  
  121. @wrap_print_func("STATIC SYNTAX")  
  122. def static_syntax_check(file_path):  
  123.     """Check the static syntax"""  
  124.     cmd = SH_N + file_path  
  125.     result = commands.getoutput(cmd)  
  126.     if result:  
  127.         print "script syntax check:"+str_coloring(" FAILED", COLOR_RED)  
  128.         print str_coloring(result,COLOR_RED)  
  129.     else:  
  130.         print "script syntax check:"+str_coloring(" PASS", COLOR_GREEN)  
  131.   
  132. def pre_handler(result):  
  133.     """pre handle the result lines """  
  134.     pass  
  135.  
  136. @wrap_print_func("PROCESS LOG CHECK")  
  137. def dynamic_log_process(file_path, params):  
  138.     """Process the log of sh script"""  
  139.     cmd = LOG_BEGIN + SH_X + file_path + " " + params  
  140.     result = commands.getoutput(cmd)  
  141.     pre_handler(result)  
  142.     process_line(result)  
  143.   
  144. def cmd_type(line):  
  145.     """return the type of line,and can do something with it"""  
  146.     if line.startswith("+"):  
  147.         return LINE_TYPE_EXC,line  
  148.     elif line.lstrip().startswith("#"):  
  149.         return LINE_TYPE_CMT,line  
  150.     else:  
  151.         #return LINE_TYPE_CMD, CMD_Y + line  
  152.         return LINE_TYPE_CMD,line  
  153.   
  154. def mark_sign_by_pattern(line, line_type=LINE_TYPE_EXC):  
  155.     """mark the str by pattern"""  
  156.     #can't use in py2.4,ni mei a  
  157.     #use_pattern = EXC_MARK_PATTERN if line_type == LINE_TYPE_EXC else CMD_MARK_PATTERN  
  158.     if line_type == LINE_TYPE_EXC:  
  159.         use_pattern = EXC_MARK_PATTERN  
  160.     else:  
  161.         use_pattern = CMD_MARK_PATTERN  
  162.     native_line = line  
  163.     for pt,color in use_pattern:  
  164.         m = pt.findall(line)  
  165.         if m:  
  166.             line = pt.sub( COLOR_MAP.get(color)+r"\1"+COLOR_MAP.get(COLOR_NONE), line)  
  167.     for pt,color in ERROR_MARK_PATTERN:  
  168.         e = pt.findall(native_line)  
  169.         if e:  
  170.             error_lines.append(line)  
  171.     return line  
  172.   
  173. def process_line(result):  
  174.     """format each line.With the pattern"""  
  175.     lines = result.split("\n")  
  176.     for line in lines:  
  177.         line_type, line = cmd_type(line)  
  178.   
  179.         if line_type == LINE_TYPE_EXC:  
  180.             result = mark_sign_by_pattern(line, line_type)  
  181.             print PATTERN_ADDSIGN.sub(COLOR_MAP.get(COLOR_GREEN)+r"\1"+COLOR_MAP.get(COLOR_NONE),result)  
  182.         elif line_type == LINE_TYPE_CMD:  
  183.             print mark_sign_by_pattern(line, line_type)  
  184.         elif line_type == LINE_TYPE_CMT:  
  185.             print line  
  186.  
  187. @wrap_print_func("RESULT COLLECT")  
  188. def warn_error_collect(collect_list, collect_type="ERROR"):  
  189.     print str_coloring("RESULT TYPE: " + collect_type, COLOR_GREEN)  
  190.     if len(collect_list):  
  191.         print str_coloring(collect_type+" FOUND: ", COLOR_RED) + str_coloring(str(len(collect_list)), COLOR_YELLOW)   
  192.         for line in collect_list:  
  193.             print line  
  194.     else:  
  195.         print str_coloring("NO " + collect_type + " FOUND", COLOR_GREEN)  
  196.   
  197. args = sys.argv[1:]  
  198. sh_name = args[0]  
  199. params = " ".join(args[1:])  
  200.   
  201. static_syntax_check(sh_name)  
  202.   
  203. dynamic_log_process(sh_name, params)  
  204.   
  205. warn_error_collect(error_lines, "ERROR")  


好了,就这些

工具的实现是为了提高效率,节约时间。


The end!


wklken

Gighub: https://github.com/wklken

Blog: http://wklken.sinaapp.com/

2012-09-15


转载请注明出处,谢谢!