TCL/EXPECT自动化测试脚本实例

原贴:http://dev.21tx.com/2007/02/22/10148.html

从今天开始,陆续把我所写的一些自动测试脚本贴上来,希望对初学者有所帮助。由于目前没有找好合适的 服务器存放代码,所以代码先贴在blog的正文中,以后再提供完整的代码 下载

我的自动化测试脚本运行在debian Linux下,使用/usr/bin/expect进行解释执行。为了简化处理,把一些常用的功能编写成函数,放在commonLib.exp文件中,其它脚本文件可以使用source commonLib.exp命令引用这些函数。
下面的函数完成telenet到目标机器并login。从其实现上大家可以看到tcl/expect编写测试脚本的简洁。
这个函数带有三个参数,分别是目标机器的IP地址ipaddr,登录用户名user和登录密码,telenet的端口号采用默认的23端口。
函数中使用了三个全局变量,g_prompt,g_usrPrompt和g_pwdPrompt,分别表示登录后的命令提示符,提示用户名输入的提示符,以及提示密码输入的提示符,这三个全局变量定义在global.exp中。之所以采用全局变量,是因为这些值使用比较广泛,但在不同设备中都不相同。使用全局变量可以方便修改。

代码如下:

#************************************************
# telnet login routine
#
# @PARAMS
# ipaddr - remote device ip address
# user   - user name to login in
# passwd - login passWord
#
# @RETURN
# spawn_id if login success, otherwise 0
#************************************************
proc login {ipaddr user passwd} {
    global g_prompt g_usrPrompt g_pwdPrompt

    spawn telnet $ipaddr
    expect {
        "$g_usrPrompt" {
            exp_send "$user/r/n"
            exp_continue
        }
        "$g_pwdPrompt" {
            exp_send "$passwd/r/n"
            exp_continue
        }
        -ex "$g_prompt" {
            dbgLog "Login Successful/n"
            return $spawn_id
        }
        timeout {
            send_user "timeout"
            return 0
        }
    }
}


现在介绍一下测试主程序: test.exp。
为了方便加入新的测试项目,主程序采用了一种灵活的机制,它根据需要通过source命令调用相应的子测试程序。这样一来,每个测试点都可以单独放到一个文件中,然后被主程序引用。
先看一下代码:

#! /usr/bin/expect --

# $Id$
# Usage:
#    ./test [-uuser] [-ppassward] [-iip_address]  test_001 ...
# or ./test [-uuser] [-ppassward] [-iip_address] [-ccommand_file] cmd
# or ./test [-uuser] [-ppassward] [-iip_address] [-sscript_file] script

source global.exp
source commonLib.exp

# initialize variables
set cmdFile ""
set tList $argv
set execScript ""

# process options
set endOptIndex -1
foreach arg $argv {
    if {![string match "-/[a-zA-Z]*" $arg]} {
        break
    }

    # inc end option index
    incr endOptIndex

    # get option flag and option value
    set optFlg [string range $arg 1 1]
    set optVal [string range $arg 2 end]
    dbgLog "$optFlg $optVal"
    if {$optVal == ""} {
        dbgLog "option value is null: -$optFlg"
        return -1
    }

    switch $optFlg {
        "u" {
            set g_user $optVal
            dbgLog "user: $g_user"
        }
        "p" {
            set g_passwd $optVal
            dbgLog "pass Word: $g_passwd"
        }
        "i" {
            set g_devip $optVal
            dbgLog "devip: $g_devip"
        }
        "c" {
            set cmdFile $optVal
            dbgLog "cmdFile: $cmdFile"
        }
        "s" {
            set execScript $optVal
            dbgLog "execScript: $execScript"
        }
        default {
            puts "unknown option: -$optFlg"
            return -1
        }
    } ;# end switch
} ;# end foreach

# remove options from list
if {$endOptIndex != -1} {
    set tList [lreplace $argv 0 $endOptIndex]
}

dbgLog "tList is: $tList"

# create log dir
if { ![file exist "log"] || ![file isdirectory "log"] } {
    puts "please create directory /"log/""
    return -1
}

# read current time
set clicks [clock clicks]
set tstr [clock format $clicks -format "%y%m%d%I%M%S"]

# open log file
log_file "log/vLog$tstr.log"

# open brief log file
set g_bLogFd [open "log/bLog.log" w]

# start testing
foreach tItem $tList {
    switch $tItem {
        "sys_001" { ;# test group sys_001
            source snmp.exp
        }
        "cmd" { ;# exec cmd file
            source tCmd.exp
        }
        "script" { ;# exec script file
            if {$execScript == ""} {
                puts "Please specify script name using -s option"
                return -1
            }
            source $execScript
        }

        default {
            puts "do you want to test /"$tItem/"/?"
        }
    }
}

close $g_bLogFd


在程序开始,通过source导入两个文件,其中global.exp中主要存放了一些全局变量的定义,因为这些全局变量对每台测试设备可能各不相同,所以把它们提取出来。commonLib.exp文件中存放着一些通用子程序,可供各测试程序调用。我们前面介绍过的login子程序,就放在此文件中。
接下来,分析命令行参数,首先提取出所有的选项参数,目前支持的命令行选项包括:
-u :此选项用来更改登录的用户名
-p :此选项用来更改登录的密码
-i :此选项用来更改telnet的IP地址
-c :此选项用来指明批处理文件的文件名,用法在后面描述
-s :此选项用来指明脚本文件的文件名,用法在后面描述

最后,命令行参数中所有非选项的部分,都被做为测试项,分别对这些测试项进行测试。
例如测试项test_001,会使用source命令调用snmp.exp脚本,进行snmp community方面的测试。
可以根据需要自行添加测试项目。

有两个特别的测试项名称,分别为cmd和script。
cmd测试项,会调用cmd.exp脚本,这个脚本在后面介绍,它的主要功能是执行一个文本文件里的所有命令。文本文件名由-c选项提供。
script测试项,它会调用source命令,执行$execScript脚本。可以使用-s选项为$execScript变量赋值。

这个测试脚本提供了两种日志,一种是详细的日志(vLog*),包括了telnet的所有交互过程;另外一种是简单的日志,只包含程序中使用errLog输出的信息。日志文件被放在子目录log中,其文件名中包含了脚本执行的时间,方便查找。
本脚本中使用dbgLog,以及以后将用到的errLog,都是定义在commonLib.exp文件中的子函数,代码如下:

#************************************************
# debug output routine
#
# @PARAMS
# arg - variable length arguments
#************************************************
proc dbgLog arg {
    global g_dbgFlag

    if {$g_dbgFlag} {
        puts $arg
    }
}

#************************************************
# error output routine
#
# @PARAMS
# arg - variable length arguments
#************************************************
proc errLog arg {
    global g_bLogFd
    global g_dbgFlag

    if {$g_dbgFlag} {
        puts $arg
    }

    if { $g_bLogFd != 0 } {
        puts $g_bLogFd $arg
    }
}

下面是global.exp文件的内容,只是定义一些全局变量,供其它文件使用。

# $Id$

# global variables
set g_dbgFlag   1                ;# Debug flag
set g_bLogFd    0                ;# Error Log FD
set g_devip     "192.168.1.222" ;# Default device IP address
set g_prompt    "$"              ;# CLI prompt
set g_user      "root"           ;# login account name
set g_passwd    "root"           ;# login passWord
set g_usrPrompt "*ogin:"         ;# login prompt
set g_pwdPrompt "*assWord:"      ;# login password prompt


在测试过程中,在具体测试某一个功能点时,往往需要为此进行大量的配置。为了简化测试过程,我们可以把所有的配置命令放在一个文本文件中,然后使用测试脚本来执行这些命令。这样就不需要再手工进行配置了,费时费力。
基于如上考虑,编写了下面的脚本tCmd.exp。这个脚本被我们前面介绍过的test.exp脚本调用。

# $Id$

# This file is used to execute specific commands list in a file

proc execCmdFile {cmdFile} {
    global g_dbgFlag g_prompt

    # enable debug
    set g_dbgFlag 1

    # login
    set spawn_id [login $g_devip $g_user $g_passwd]
    if {$spawn_id == 0} {
        errLog "login $g_devip failed"
        return 0
    }

    # open cmdFile
    set cmdFd [open $cmdFile r]

    while true {
        # get a line
        if {![getLine $cmdFd line]} {
            dbgLog "reached eof"
            break
        }

        # split the line
        set ln [split $line ","]
        set cmd [string trim [lindex $ln 0]]
        set out [string trim [lindex $ln 1]]

        if {$cmd == ""} continue
        if {$out == ""} set out $g_prompt

        # send cmd line
        exp_send "$cmd/n"
        dbgLog "send $cmd"

        # expect output
        dbgLog "expect $out"
        expect {
            timeout {
                errLog "TIMEOUT: while exec /"$cmd/""
                continue
            }
            -ex "$out" {
                continue
            }
        } ;# end expect
    }

    # close cmdFile
    close $cmdFd
}

# if no cmdFile, use default
if {$cmdFile == ""} {
    set cmdFile "cmdFile.txt"
}

execCmdFile $cmdFile

有了这个脚本,我们可以使用"./test.exp -cinterface.txt cmd"来执行interface.txt中的命令。

代码见下,比较简单,就不再分析了。调用实例见前面的文章。

#************************************************
# get a line from file, skip blank lines and
# comment lines, return the reading line in
# parameter 'line'.
#
# @PARAMS
# fd     - file fd
# line   - var used to return the line
#
# @RETURN
# return 1 if read successfully, otherwise 0
#************************************************
proc getLine {fd line} {
    upvar $line ln

    # read a line from fd
    while {[set lineLen [gets $fd ln]] >= 0} {
        # blank line
        if { $lineLen == 0 } continue
 
        # trim whitespace
        set ln [string trim $ln]
        if { [string length $ln] == 0 } continue

        # skip comment
        if { [string index $ln 0] == "#" } continue

        # success
        return 1
    }

    return 0
}

下面通过一个测试SNMP community最大长度的脚本,介绍一下net-snmp工具。

net-snmp是一组基于命令行的snmp manager工具,可以在命令行下进行snmp get, snmp set, snmp walk等操作,支持snmp v1/v2c/v3。原来的名字叫做ucd-snmp,也已经被移植到Windows NT上。
它的主页在http://net-snmp.sourceforge.net/

由于它可以在命令行下进行SNMP操作,所以可以和TCL/expect很好的结合,完成自动化测试的功能。
下面的脚本(snmp.exp),不断的增加SNMP community,长度从1到256,每增加一个community,就调用snmp-get来进行SNMP get操作,如果get成功,说明此community有效;反之,就说明community已经超出了设备支持的最大长度。
这个脚本使用前面讲到的test.exp调用,调用方法是:
./test.exp -ssnmp.exp script
对它稍加修改,也可以直接在命令行中调用,此处不再赘述。

代码如下:
# $Id$

proc snmpCommTest {comm} {
    global g_devip

    spawn snmpget -c $comm -v 2c -r 2 $g_devip system.sysUpTime.0
    expect {
        "system.sysUpTime.0*" {
            return 1
        }
        "*Timeout*" {
            return 0
        }
    }

    return 1
}

set spawn_id [login $g_devip $g_user $g_passwd]
if {$spawn_id == 0} {
    errLog "login error/n"
    return 0
}

set cmdCommAdd "create snmp community %s rw/n"
set cmdCommDel "delete snmp community %s/n"
set cmdHostAdd "create snmp host ip 192.168.1.2 community %s/n"
set cmdHostDel "delete snmp host ip 192.168.1.2 community %s/n"
set comm ""

for {set i 1} {$i < 256} {incr i} {
    set comm "a$comm"
    set cmd [format $cmdCommAdd $comm]
    exp_send $cmd
    expect {
        "Error*" {
            errLog "create comm len $i error"
            continue
        }
        timeout {
            errLog "create comm len $i timeout"
            continue
        }
        "Entry Created"
    }
    set cmd [format $cmdHostAdd $comm]
    exp_send $cmd
    expect {
        "Error*" {
            errLog "create host error"
            continue
        }
        timeout {
            errLog "create host timeout"
            continue
        }
        "Entry Created"
    }

    set rc [snmpCommTest $comm]
    if {$rc == 0} {
        errLog "community len $i failed"
    }

    set cmd [format $cmdHostDel $comm]
    exp_send $cmd
    expect "Entry Deleted"
    set cmd [format $cmdCommDel $comm]
    exp_send $cmd
    expect "Entry Deleted"
}

前面提到过使用nemesis构造报文进行测试的思路,今天介绍一个这样的脚本。
这个脚本的功能是构造并发送不同源MAC地址的报文,通过这样一个脚本,我们就可以测试交换机每端口最大能学习到的MAC地址的数目。

简单说一下nemesis,它运行在Linux上,也可以在Windows上运行。在Linux上,需要拥有root权限才能构造报文。
它可以用来构造arp, enternet, ip, icmp, igmp, dns, tcp, ospf, rip等类型的报文。实际上,用户可以使用一个文件做为它所构造的报文的内容,从这个角度上讲,它可以用来构造任何类型的报文。
另外,由于它是基于命令行的一组工具,所以能够非常好的和TCL/EXPECT结合使用,完成自动化测试。

这个脚本仍旧由前面介绍的test.exp脚本调用,调用方式是:
./test.exp -ssrc_mac_attack.exp script

此脚本文件的内容(src_mac_attack.exp)如下:

# $Id$
# Construct different source MAC address packets, and send them to switch.

proc src_mac_attack {mac} {
    set rc [exec echo "src MAC attack packet $mac" /
        | nemesis ethernet -M 00:01:02:03:04:05 -H $mac -T 0x0800 -P -]
    return $rc
}


for {set i 1} {$i < 256} {incr i} {
    set mac [constructMac $i]
    src_mac_attack $mac
}

脚本很简单,只有一个循环,不断生成新MAC,然后构造报文发送。
简单介绍一下proc src_mac_attack,这个函数中,使用TCL的exec命令来执行linux下的命令。在这里执行的linux命令就是:
  echo "src MAC attack packet $mac" /
          | nemesis ethernet -M 00:01:02:03:04:05 -H $mac -T 0x0800 -P -
其中,echo命令的输出通过管道,被送给nemesis命令做输入,echo命令显示的内容将做为所构造的以太报文的内容;
nemesis ethernet命令表明所构造的是以太报文,-M、-H、-T分别指明报文的目的地址、源地址,报文类型。“-P -”则指明报文的内容由标准输入获得,在此例中就是echo命令的输出。

脚本中调用了另外一个自定义函数,这个函数放在commonLib.exp中,用来生成MAC地址(最多可生成65535个不重复的MAC地址),函数内容如下,比较简单,不再赘述:

#************************************************
# Construct MAC address
#
# @PARAMS
#    rawMac --- raw MAC address, integer
#
# @RETURN
#    the MAC address string
#************************************************
proc constructMac {rawMac} {
    set mac "00:00:00:00"
    set j [expr "($rawMac >> 8) & 0xFF"]
    set k [format "%x" $j]
    set mac "$mac:$k"
    set j [expr "$rawMac & 0xFF"]
    set k [format "%x" $j]
    set mac "$mac:$k"

    dbgLog "rawMac = $rawMac, mac = $mac"
    return $mac
}

 

上一篇

你可能感兴趣的:(bash和正则)