UML软件工程组织

 

 

TCL/EXPECT自动化测试脚本实例
 
作者:Easwy   文章来源:个人网站
 

从今天开始,陆续把我所写的一些自动测试脚本贴上来,希望对初学者有所帮助。由于目前没有找好合适的服务器存放代码,所以代码先贴在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 "password: $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"
}

 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号