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....
  • 相关阅读:
    streamsets 集成 cratedb 测试
    streamsets k8s 部署试用
    streamsets rest api 转换 graphql
    StreamSets sdc rpc 测试
    StreamSets 相关文章
    StreamSets 多线程 Pipelines
    StreamSets SDC RPC Pipelines说明
    StreamSets 管理 SDC Edge上的pipeline
    StreamSets 部署 Pipelines 到 SDC Edge
    StreamSets 设计Edge pipeline
  • 原文地址:https://www.cnblogs.com/iplus/p/4464625.html
Copyright © 2011-2022 走看看