网络设备自动化测试框架
NATF(Netsork automation test frame)
第1章 概述
随着internet的发展,网络设备功能日渐复杂和多样,测试消耗的资源也越来越庞大,测试本身是无穷的,而智能化的人力资源却是有限的,所以如何将有限的人力精兵投入到复杂的测试中去而避免在细小的功能验证上耗费人力是每一个测试人员的理想。那么要做到这一点行之有效的方法就是细分测试种类,将简单的功能性测试由自动化完成,性能及复杂的组合、异常等等的测试有人力完成,这样一方面可以提高测试资源的利用率,一方面可以扩大测试的覆盖率。形成系统的测试闭环。
近年来,自动化测试被越来越多的重视并实践,当前主流网络设备厂商已经在自动化测试领域取得了很大的成绩,自动化测试覆盖率也逐渐在提高。而各个厂商自动化测试的平台是不同的,测试框架也就肯定是不同的,联想网御主要使用SecureCRT作为测试平台,SecureCRT本身提供了Jscript和VBscript两种脚本语言的支持。所以在SecureCRT的基础之上便诞生了NATF的框架。
网络设备自动化测试框架NATF(Network automation test frame)旨在设计出一种用命令行完成配置的网络设备(如防火墙、路由器、交换机等)的自动化功能性测试框架。整个框架可以分为如下几个部分:
测试床文件:该文件描述了实际物理设备的物理连接、组网及相关配置。
逻辑拓扑文件:因为在实际的测试过程中不同的模块的测试组网不尽相同,而为不同的模块来搭建不同的物理网络环境就显得耗时费力不实际,逻辑拓扑将测试组网跟实际组网环境脱离,多个逻辑拓扑文件共用一个测试床文件,在脚本执行过程中自动完成逻辑设备到物理设备的映射。这样做有两方面的好处:一方面只要完成了测试床环境的搭建,不许改变环境便可以完成多个模块的测试;另一方面为脚本的移植带来极大的方便,只需自行设计测试床而无需动用逻辑拓扑及具体的脚本文件。
测试脚本:原则上每一个测试用例可以用一个测试脚本来实现。而测试脚本的设计既要考虑到测试用例的粒度,既不能过于单一也不能过于繁琐,单一会造成不紧凑的感觉,繁琐又会给定位带来很大的不便。
测试集:同一模块的多个脚本及多个模块的测试集可以组成一个测试集文件。
整个测试框架实质上是一个分层的结构:
在测试的过程之中,测试的文件的解析是由下往上的,先解析测试床文件完成设备基本配置工作,然后解析逻辑拓扑文件完成逻辑设备到物理设备的映射(手动/自动),接着执行单个的测试脚本,最后等所有测试脚本完成之后便完成了测试集的解析执行。测试文件的执行恰好是由上往下的,以测试集文件作为入口文件开始执行,然后分别执行每个独立的脚本文件,在每个脚本文件执行的过程中会完成逻辑拓扑到测试床的映射进而正确的执行脚本。在执行脚本的过程中,每一步都会将执行结果记入日志文件,在测试集执行完毕之后系统会根据*.log的日志文件生成测试结果的*.exl文件并以邮件的方式发送到管理员邮箱中,以便管理员可以及时的看到测试结果并对FAIL项进行定位。
|
在脚本的执行过程中,一般会经过如下几个步骤:
1. 解析测试床文件,完成物理设备的接口地址及静态路由等的基本配置工作,保证物理组网环境正确畅通。
2. 解析测试集文件,按照顺序执行测试集中的测试脚本。
3. 在执行测试脚本之前解析逻辑拓扑文件并完成逻辑设备到物理设备的映射,映射包括逻辑设备到物理设备的映射,逻辑接口到物理接口的映射。完成映射之后通过读取逻辑设备接口信息可以得到实际的接口IP、掩码等。
4. 具体的测试脚本的执行。脚本执行一般有如下几个步骤:首先完成基本的配置,然后是每一步的功能检查,最后清除配置保证测试环境干净。
第2章 NATF的基本组成
2.1 测试床
测试床文件从某一种意义上是实际物理组网拓扑的文件描述。也就是说用文件的方
方式把物理环境进行了描述,进而形成了自动化测试框架中的测试床。测试床文件一般来说相对较复杂且具有典型性,因为它的设计要考虑到涵盖各个模块的组网环境,以求用最简最完备的环境完成环境描述。
而测试床文件的功能却又不仅限于此,测试床文件在完成物理拓扑描述的同时还必须肩负起基本网络配置功能,也就是说在在解析测试床文件的过程中呢必须识别环境中用到了那些设备、设备接口、接口的IP地址及基本路由等。并且在解析过程中自动完成接口IP地址的配置及基本路由的配置。这一部分是跟具体脚本无关的,因为脚本文件中所有的接口都采用了物理的而非实际的。
下面是一个举例的测试床文件(*.tbd),从中可以看出测试床文件的一些设计细节。
[TITLE]1.1.1 防火墙部分NAT模块测试床
[PHY-TOP]
"
F0/0 F0/1 F0/0
R1-------R2-------R3------R4
F0/0 F0/1 F0/0
"
[SESSION]
R1:Router1
R2:Router2
R3:Router3
[PHY-LINK]
R1 F0/0 1.1.1.1 255.255.255.0;R2 F0/0 1.1.1.2 255.255.255.0
R1 F0/1 2.1.1.1 255.255.255.0;R2 F0/1 2.1.1.2 255.255.255.0
R2 F1/0 3.1.1.1 255.255.0.0;R3 F1/0 3.1.1.2 255.255.0.0
R2 F2/0 4.1.1.1 255.255.0.0;R3 F2/0 4.1.1.2 255.255.0.0
[INIT]
R1:
ip route 3.1.1.0 255.255.255.0 1.1.1.2
ip route 4.1.1.0 255.255.255.0 2.1.1.2
R2:
R3:
ip route 1.1.1.0 255.255.255.0 3.1.1.1
ip route 2.1.1.0 255.255.255.0 4.1.1.1
[END]
说明: [TOPNAME]说明了该测试床文件的名称。
[LOGICTOP]描述了物理组网环境。
[SESSION]描述了具体的设备通过secureCRT连接的会话名称
[LINK]描述了具体的物理设备的连接情况及IP情况。
[INIT]描述了每个物理设备需要进行的基本配置。一般包括基本路由、默认防火墙规则等。
测试床文件为*.tbd文件,在执行脚本过程中首先会进行测试床文件的解析,在解析过程中对设备的接口IP、路由等进行配置。完成测试环境的初始化。
2.2 逻辑拓扑
逻辑拓扑是实际物理组网的一个抽象,因为每个模块功能是不同的,所以运用的环境也肯定是不同的,而在实际的测试过程中就不可能为每个模块都搞一套环境,所以将具体的测试环境抽象成为逻辑拓扑跟测试床独立,在测试的过程中进行逻辑到物理的映射,如此在进行脚本移植的过程中只需物理环境根据需要自行设计而逻辑拓扑及脚本则不发生变化,这样免去了很多的重复工作。
下面是一个逻辑拓扑的举例:
[TITLE] 1.1.1 防火墙部分NAT模块SNAT基本功能逻辑拓扑
[LOGIC TOP]
"
PORT1 PORT1
DUT1----------------DUT-------------DUT2
PORT2 PORT2
"
[LOGIC DEVICE]
DUT1 DUT DUT2
[LOGIC LINK]
DUT1 PORT1<--->DUT PORT1
DUT PORT3<--->DUT2 PORT1
[TOP END]
说明:[TITLE] 逻辑拓扑名称
[LOGIC TOP]逻辑拓扑组网。组网中设备名及端口名均为逻辑名。所有脚本
中涉及到的设备及接口名均为该逻辑名。
[LOGIC DEVICE]逻辑设备名,最后需要跟物理设备映射对应。最终获取到
具体物理设备的会话名。以便脚本中通过该会话在SecureCRT中访问到设备。
[LOGIC LINK]逻辑设备之间的连接关系。在逻辑拓扑映射的过程中逻辑连
接要跟物理设备连接进行映射关联,关联之后呢通过访问逻辑接口便可以
访问到物理接口,进一步可以获取物理接口ip地址等信息。
2.3 测试脚本
一般情况下,整个测试体系应当是一体的,在项目开发过程中开发人员需要提交相关的测试交付件,这些交付件一般会包括SRS,SOW,系统测试用例,缺陷文档等等。在项目验收测试过程中由测试人员完成测试点、测试用例的写作,然后以测试用例为蓝本进行测试脚本的写作,然后入库形成基线。测试脚本作为测试交付件,一个测试用例对应一个测试脚本或着多个测试脚本。
从测试的角度来看测试脚本可以这样来理解,测试脚本主要完成三个功能:配置下发、功能检查、结果输出。下面以一个具体的实例来介绍:
# $language = "JScript"
# $interface = "1.0"
/*============引用接口库文件并进行测试床初始化===================*/
eval(Include("D:\\cyl\\Security&×××\\security.js"));
/*===测试脚本头部分:说明测试目的、功能概述并记录日志================*/
HEAD("1.1.1 防火墙部分NAT模块SNAT基本功能测试","CHEYL 7765 2010-1-10");
/*=======基本配置部分:连接设备并完成命令行配置====================*/
sessionConnect(map.mapDevSess("DUT","s"));
setPcp("pcp","source-ip","net",map.mapInt("DUT1","PORT1",true,false),\
map.mapInt("DUT1","PORT1",false,true);
setSnatIp("pcp",map.mapInt("DUT1","PORT1",true,false));
sessionDisconnect();
/*=======检查部分:利用Check接口完成多个step的检查并将结果记录日====*/
//步骤一:
STEP(1,"从DUT1到DUT2能不能ping通");
sessionConnect(map.mapDevSess('DUT1','s'));
Check("ping",map.mapInt("DUT","PORT1",true, false),"5","test");
Check("ping",map.mapInt("DUT2","PORT1",true, false),"5","test");
sessionDisconnect();
//步骤二:
STEP("2","从DUT1到DUT2能不能ping通");
sessionConnect(map.mapDevSess("DUT2","s"));
Check("ping",map.mapInt("DUT1","PORT1",true, false),"5","test");
Check("ping",map.mapInt("DUT","PORT1",true, false),"5","test");
sessionDisconnect();
/*===========配置清除部分:将配置清除为初始化=====================*/
sessionConnect(map.mapDevSess("DUT","s"));
removePcp("pcp");
removeSnat("pcp");
sessionDisconnect();
/*=======================脚本结束==============================*/
从上面的脚本可以看出一个完整的测试脚本有如下几部分组成:
1. 脚本语言及版本说明(作为单独的脚本执行时是必须的否则需要注释掉)
2. 脚本头文件:说明测试目的、功能概述、作者及日期等并记录日志
3. 基本配置部分:连接设备并完成命令行配置。该部分如果多个测试脚本共用可以移到测试集拓扑文件映射过程中。
4. 检查部分:利用Check接口完成多个step的检查并将结果记录日志。
5. 配置清除部分:将配置清除为初始化。目的是为了防止相关配置对后续功能检查的影响。相关配置清除之后脚本执行也就结束了。
6. 除了以上5点之外最重要的就是记录日志:在脚本执行的过程之中会将相关步骤、脚本的执行结果记录在日志中,如果检查失败还会记录失败信息便于定位。
2.4 测试集
同一个模块的相关测试脚本完成后便可以生成一个测试集文件。不同模块的测试集文件也可以放在一起生成一个总的测试集文件。这样做的目的便于管理。下面就是一个测试集文件的实例:
# $language = "JScript"
# $interface = "1.0"
/*======引用接口库文件并进行测试床初始化================*/
eval(Include("D:\\cyl\\Security&×××\\BaseLIB.js"));
/*=======读取测试床文件并完成设备初始化=================*/
MODULE_TEST_SUIT_START();
/*========测试集文件:包含各个脚本文件并执行============*/
eval(Include("D:\\cyl\\Security&×××\\script1.js"));
eval(Include("D:\\cyl\\Security&×××\\script2.js"));
MODULE_TEST_SUIT_END()
分析上述测试集文件可以看出包含如下几个步骤:
1. 脚本语言及版本说明。
2. 引用接口库文件。库文件中包含了各个模块的接口。因为其规模较大,可以按模块划分后综合,这样便于问题定位及管理。
3. 读取测试床文件并完成设备初始化。整个测试集执行过程中只进行一次测试床文件解析及物理组网初始化。
4. 测试集文件:包含各个脚本文件并顺序执行。
5. 测试集执行结束完成测试结果的汇总。这个过程会有一个很重要的功能,那就是生成测试结果。以邮件的方式发送到管理员邮箱之中。具体日志格式及邮件格式请参见脚本日志设计章节。
以上几部分分别较为详细的介绍了NATF自动化测试框架的各个组成部件。但是并没有涉及到具体的实现。下面将较为详细的介绍一下具体的实现及相关的脚本的写作规范。
第3章 脚本接口设计规范
因为我司普遍采用了SecureCRT作为测试平台,而SecureCRT只提供了Jscript、VBscript及PerlScript三种脚本语言。而没有提供业界用的较多的tcl&tk脚本语言,所以只能选择Jscript语言。我们的网络设备的测试都是基于命令行的,所以自动化实现主要是完成配置命令的下发与功能的检查两部分。
要完成模块配置的下发,就需要将命令行进行包装,结合SecureCRT简单的脚本功能开发出适合我司设备的接口库,然后用这些接口库文件编写脚本实现自动化测试。
目前接口库的接口设计主要有三类:set类、get类和chk类三种。每个接口均为一个函数,主要包括两个方面:1.接口说明,需要说明函数功能,参数、作者、日期、返回值及应用举例等。2.函数体:主要实现相关功能。下面分别进行说明。
3.1 set/remove类
Set类接口主要完成配置的下发。
举例如下:
//*********************************************************************
//函数功能:通过执行“interface Ge0/0/0 ”配置设备接口
//函数参数:arg1--删除的接口名
// arg2--厂商标识(s为思科,h为华为,默认为网御)
//作 者 :cheyl 2009-12-20
//返回值 :配置正确返回true,否则为false
//举 例: setInt("Ge0/0/0")
//*********************************************************************
function setInt(arg)
{
var viewTemp = new view();
viewTemp.viewCfg();
crt.screen.send("interface " + arg + "\r");
if(crt.screen.waitforstring("\(config-if\)#"))
{
return true;
}
return false;
}
Remove类主要完成配置的删除,一般情况下跟set类是成对出现的,共同完成某个命令的下发与删除。
Remove类举例如下:
//*********************************************************************
//函数功能:通过执行undo interface Ge0/0/0 删除设备逻辑接口,物理接口不可删
//函数接口:arg2---厂商标识(s为思科,h为华为,默认为网御)
//作 者 :cheyl 2009-12-20
//返回值 :配置正确返回true,否则为false
//举 例: removeInt("Ge0/0/0")
//*********************************************************************
function removeInt(arg1,arg2)
{
var viewTemp = new view();
viewTemp.viewCfg();
switch(arg2)
{
case "s":
crt.screen.send("no interface " + arg1 + "\r\n");
crt.screen.WaitForString("#");
break;
case "h":
crt.screen.send("undo interface " + arg1 + "\r\n");
crt.screen.WaitForString("#");
break;
default:
crt.screen.send("undo interface " + arg1 + "\r\n");
crt.screen.WaitForString("#");
break;
}
if(crt.screen.waitforstring("\(config\)#"))
{
return true;
}
return false;
}
3.2 get类接口
get类接口主要完成show信息的获取,get是chk的基础,chk一般会通过get类得到
信息经过比较,处理后以便确定模块的功能的正确性。
//************************************************************************
//函数功能:通过执行“show int f0/0 ”来获取某个接口的相关ip信息
//函数接口:arg1---接口
// arg2---需要获取的信息(ip、mask、state、mac、speed、mtu、duplex、
// input、output)
// arg3---厂商选择
//作 者:cheyl 2009-12-20
//返回值 :配置正确返回true,否则为false
//举 例: getIntInfo("f0/0","output","s");
//************************************************************************
function getIntInfo(arg1,arg2,arg3)
{
var rowTemp,result,resultTemp;
var viewTemp = new view();
var regTempIp = RegExp("(\\d{1,3}\.){3}\\d{1,3}");
var regTempMask = RegExp("/\\d*");
var regTempState = RegExp("\\bdown\\b|\\bup\\b");
var regTempMac = RegExp("(\\w{4}\.){3}");
var regTempSpeed = RegExp("10{1,4}");
var regTempMtu = RegExp("\\d+");
var regTempDuplex = RegExp("half|full","i");
var regTempInOutStatistcs = RegExp("\\d*");
var regTempConfig = RegExp("config");
switch(arg3)
{
case "s":
resultTemp = getIntInfoCisco();
break;
case "h":
resultTemp = getIntInfoH3c();
break;
default:
resultTemp = getIntInfoLeadsec();
break;
}
function getIntInfoCisco()
{
var rowTemp = crt.screen.CurrentRow;
var result = crt.screen.get(rowTemp, 1, rowTemp, 80);
var resultTemp;
if(regTempConfig.exec(result))
{
crt.screen.send("end"+"\r");
crt.screen.WaitForString("#");
}
crt.screen.send("show interface " + arg1 + "\r\r\r\r");
crt.screen.WaitForString("#");
var row = crt.screen.CurrentRow;
//arg1 = {ip、mask、state、mac、speed、mtu、duplex、input、output}
//regexp = regTempIpMask,regTempState,regTempMac,regTempSpeed,regTempMtu,regTempDuplex,regTempInOutStatistcs
switch(arg2)
{
case "ip":
result = crt.screen.get(row-24, 1, row-24, 80);