如何進行shell指令碼正確性測試

來源:互聯網
上載者:User

部落格已遷移,新地址

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

在實際工作中,需要對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,osimport commandsimport re#color definedCOLOR_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 definedSH_N = "sh -n "SH_X = "sh -vx "LOG_BEGIN = "export PS4='+${BASH_SOURCE}|${LINENO}|${FUNCNAME[0]} -> ';"LOG_BEGIN = ""#the type of output log lineLINE_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. specialPATTERN_ADDSIGN = re.compile("(^\++)")#1. execute command log match patternexc_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% errorerror_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,noticewarn_mark_pattern = []WARN_MARK_PATTERN = [(re.compile(s),color) for s,color in warn_mark_pattern]#3. command log match patterncmd_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 definederror_lines = []#functions begindef 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 + "-"*20def 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,linedef 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 linedef 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

轉載請註明出處,謝謝!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.