zoukankan      html  css  js  c++  java
  • 如何进行shell脚本正确性测试

    博客已迁移,新地址




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

    在实际工作中,需要对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

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

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


    代码:

    #!/bin/env python
    #-*- coding:utf-8 -*-
    #@Author: wklken
    #@Mail: wklken@yeah.net ,lingyue.wkl@taobao.com
    #@Date: 20120706
    #@Version: 0.1  sh -n, check for static syntax
    #          0.2  sh -vx, color the output log which i care about
    #          0.2.1 rebuild all functions , lines  200+ -> 120
    #          0.2.2 refine the re pattern.
    #          0.2.3 add sh params support. fix bug and add re patterns
    #          0.2.5 add warn/error pattern and collect the result
    #          0.2.6 use decorator to refine print, refine collect method
    
    #@Desc: Quick test shell script.The target is hacking into it and get all the status i need.
    #TODO: need to keep source code in 200 lines! refine!
    
    import sys,os
    import commands
    import re
    
    #color defined
    COLOR_NONE = "C_NONE"
    COLOR_GREEN = "C_G"
    COLOR_RED = "C_R"
    COLOR_YELLOW = "C_Y"
    COLOR_PURPLE = "C_P"
    COLOR_BLUE = "C_B"
    
    COLOR_MAP = {COLOR_NONE : "\033[m",
                 COLOR_GREEN : "\033[01;32m",
                 COLOR_RED : "\033[01;31m",
                 COLOR_YELLOW : "\033[01;33m",
                 COLOR_PURPLE : "\033[01;35m",
                 COLOR_BLUE : "\033[01;34m",
                 None:"\033[m" }
    
    #the command used defined
    SH_N = "sh -n "
    SH_X = "sh -vx "
    LOG_BEGIN = "export PS4='+${BASH_SOURCE}|${LINENO}|${FUNCNAME[0]} -> ';"
    LOG_BEGIN = ""
    
    #the type of output log line
    LINE_TYPE_CMD = "CMD"
    LINE_TYPE_EXC = "EXC"
    LINE_TYPE_CMT = "CMT"
    
    CMD_Y = COLOR_MAP.get(COLOR_YELLOW) + "CMD: " + COLOR_MAP.get(COLOR_NONE)
    
    #----------pattern used to match begin -----------------------------
    #0. special
    PATTERN_ADDSIGN = re.compile("(^\++)")
    
    #1. execute command log match pattern
    exc_mark_pattern = [(r"([\[\]])", COLOR_YELLOW), #for condition testing   must be the first one
                        (r"(([12]\d{3})(1[12]|0[1-9])(0[1-9]|1\d|2\d|3[01]))",COLOR_PURPLE), #date yyyyMMDD
                        (r"(tbsc-dev)", COLOR_RED),  # path: tbsc-dev
                        (r"([a-zA-Z_][a-zA-Z0-9_]*=[\s|\"\"]*)$",COLOR_RED),   # params=None
                        (r"(exit\s+-?\d*|return\s+-?\d*)",COLOR_BLUE), #exit status
                        (r"(\s(\-[acbdefgnorsuwxzL]|\-(lt|le|gt|ge|eq|ne))\s)", COLOR_YELLOW),
                        (r"((\s(=|==|<=|>=|\+=|<|>|'!='|\&\&)\s)|'!')", COLOR_YELLOW),
                        (r"(\s(\-input|\-output|\-i|\-o)\s)", COLOR_YELLOW),
                        ]
    EXC_MARK_PATTERN = [(re.compile(s),color) for s,color in exc_mark_pattern]
    
    #2. error/warn result log match pattern
    # 100% error
    error_mark_pattern = [(r"(No such file or directory|command not found|unknown option|invalid option)",COLOR_RED), #result -> file not found
                        (r"(unary operator expected)",COLOR_RED), # test failed
                        (r"(Permission denied)",COLOR_RED),
                        (r"(syntax error|unexpected|read error)",COLOR_RED),
                        (r"(java.io.FileNotFoundException|org.apache.hadoop.mapred.InvalidInputException|java.lang.IllegalMonitorStateException)", COLOR_RED),#javaerror
                        ]
    ERROR_MARK_PATTERN = [(re.compile(s),color) for s,color in error_mark_pattern]
    
    # may be not error ,just warn,notice
    warn_mark_pattern = []
    WARN_MARK_PATTERN = [(re.compile(s),color) for s,color in warn_mark_pattern]
    
    #3. command log match pattern
    cmd_mark_pattern = error_mark_pattern + warn_mark_pattern + \
                        [
                        (r"(line \d+)", COLOR_RED), #error report the line No
                        (r"(\$(\{\w+\}))", COLOR_PURPLE),
                        (r"(\.\.)",COLOR_PURPLE), #相对路径
                        (r"((?<!-)\b(\w+)\b=)", COLOR_YELLOW),
                        (r"(\$(\w+))", COLOR_PURPLE), #变量名
                        (r"(\w+\.sh\s*)", COLOR_GREEN), #*.sh
                        (r"(`)", COLOR_GREEN),  # ``
                        (r"(\s?\w+\s*\(\))", COLOR_GREEN), #function()
                        (r"(\{\s*$|^\}\s*$)", COLOR_GREEN), # function {}
                        (r"(^export\s|^source\s)", COLOR_YELLOW),
                        (r"(\|)", COLOR_GREEN),
                        (r"(<<|>>|<|>)", COLOR_YELLOW),
                        ]
    CMD_MARK_PATTERN = [(re.compile(s),color) for s,color in cmd_mark_pattern]
    #----------pattern used to match end -----------------------------
    
    #static params defined
    error_lines = []
    
    #functions begin
    def str_coloring(str_info, color=COLOR_NONE):
        """color str"""
        return COLOR_MAP.get(color, COLOR_MAP.get(None)) + str_info + COLOR_MAP.get(COLOR_NONE)
    
    def print_symbol(str_info):
        """print the symbol"""
        print "-"*20 + str_info + "-"*20
    
    def wrap_print_func(arg):
        """wrap func, print begin and end sign"""
        def  newfunc(func):
            def newfunc_withparams(*args, **kwargs):
                print_symbol(arg+" BEGIN")
                func(*args, **kwargs)
                print_symbol(arg+" END")
            return newfunc_withparams
        return newfunc
    
    @wrap_print_func("STATIC SYNTAX")
    def static_syntax_check(file_path):
        """Check the static syntax"""
        cmd = SH_N + file_path
        result = commands.getoutput(cmd)
        if result:
            print "script syntax check:"+str_coloring(" FAILED", COLOR_RED)
            print str_coloring(result,COLOR_RED)
        else:
            print "script syntax check:"+str_coloring(" PASS", COLOR_GREEN)
    
    def pre_handler(result):
        """pre handle the result lines """
        pass
    
    @wrap_print_func("PROCESS LOG CHECK")
    def dynamic_log_process(file_path, params):
        """Process the log of sh script"""
        cmd = LOG_BEGIN + SH_X + file_path + " " + params
        result = commands.getoutput(cmd)
        pre_handler(result)
        process_line(result)
    
    def cmd_type(line):
        """return the type of line,and can do something with it"""
        if line.startswith("+"):
            return LINE_TYPE_EXC,line
        elif line.lstrip().startswith("#"):
            return LINE_TYPE_CMT,line
        else:
            #return LINE_TYPE_CMD, CMD_Y + line
            return LINE_TYPE_CMD,line
    
    def mark_sign_by_pattern(line, line_type=LINE_TYPE_EXC):
        """mark the str by pattern"""
        #can't use in py2.4,ni mei a
        #use_pattern = EXC_MARK_PATTERN if line_type == LINE_TYPE_EXC else CMD_MARK_PATTERN
        if line_type == LINE_TYPE_EXC:
            use_pattern = EXC_MARK_PATTERN
        else:
            use_pattern = CMD_MARK_PATTERN
        native_line = line
        for pt,color in use_pattern:
            m = pt.findall(line)
            if m:
                line = pt.sub( COLOR_MAP.get(color)+r"\1"+COLOR_MAP.get(COLOR_NONE), line)
        for pt,color in ERROR_MARK_PATTERN:
            e = pt.findall(native_line)
            if e:
                error_lines.append(line)
        return line
    
    def process_line(result):
        """format each line.With the pattern"""
        lines = result.split("\n")
        for line in lines:
            line_type, line = cmd_type(line)
    
            if line_type == LINE_TYPE_EXC:
                result = mark_sign_by_pattern(line, line_type)
                print PATTERN_ADDSIGN.sub(COLOR_MAP.get(COLOR_GREEN)+r"\1"+COLOR_MAP.get(COLOR_NONE),result)
            elif line_type == LINE_TYPE_CMD:
                print mark_sign_by_pattern(line, line_type)
            elif line_type == LINE_TYPE_CMT:
                print line
    
    @wrap_print_func("RESULT COLLECT")
    def warn_error_collect(collect_list, collect_type="ERROR"):
        print str_coloring("RESULT TYPE: " + collect_type, COLOR_GREEN)
        if len(collect_list):
            print str_coloring(collect_type+" FOUND: ", COLOR_RED) + str_coloring(str(len(collect_list)), COLOR_YELLOW) 
            for line in collect_list:
                print line
        else:
            print str_coloring("NO " + collect_type + " FOUND", COLOR_GREEN)
    
    args = sys.argv[1:]
    sh_name = args[0]
    params = " ".join(args[1:])
    
    static_syntax_check(sh_name)
    
    dynamic_log_process(sh_name, params)
    
    warn_error_collect(error_lines, "ERROR")


    好了,就这些

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


    The end!


    wklken

    Gighub: https://github.com/wklken

    Blog: http://wklken.me/

    2012-09-15


    转载请注明出处,谢谢!


    Meet so Meet. C plusplus I-PLUS....
  • 相关阅读:
    Qt通用方法及类库9
    设计模式原则(7)--Composition&AggregationPrinciple(CARP)--合成&聚合复用原则
    设计模式原则(6)--Open-Closed Principle(OCP)--开闭原则
    鼠标悬停出现页面
    设计模式(23)--Visitor--访问者模式--行为型
    设计模式(22)--Template Method(模板方法模式)--行为型
    设计模式(21)--Strategy(策略模式)--行为型
    设计模式(20)--State(状态模式)--行为型
    设计模式(19)--Observer(观察者模式)--行为型
    设计模式(18)--Memento(备忘录模式)--行为型
  • 原文地址:https://www.cnblogs.com/iplus/p/4464625.html
Copyright © 2011-2022 走看看