不要追求绝对的公平,红尘之中没有公平而言,人活一世,难得糊涂。                                           it is no use doing what you like, you have got to like what you do.

在RFT中运用手动验证点验证自定义类型对象

上一篇 / 下一篇  2007-01-19 11:59:27 / 个人分类:Rational Functional Tester

\4?*kYM t4{6SdD0Rational Functional Tester(以下简称RFT)是一款强大易用的自动化功能测试工具。在使用RFT进行功能测试的过程中,测试结果的验证往往是通过插入验证点 (Verification Point)来完成的。但是RFT的验证点只能验证有限的数据类型,而在实际应用中,对用户自定义类型的验证存在着较大的需求。本文对验证点的验证执行过 程进行剖析,并介绍如何通过自定义ValueManager来实现对用户自定义类型对象的验证。

Yv3UR(}5Q5p0

|&}6`:Cj};y:a {D01验证点简介51Testing软件测试网u;mj`](W)}*?

51Testing软件测试网$_p.w]H'u0U @/\

1.1 验证点的类型

(yY|.kZ5[\ wKoC \i0

h]RS1\yQ8cl So9b0Rational Functional Tester是Rational最新推出的自动化功能测试工具。RFT具有数据驱动(Data-Driven)测试、scrīptAssure等特性,因 而受到广大功能测试人员的青睐。在RFT中,验证点是脚本(scrīpt)中非常重要的组成部分,它完成对被测试程序生成的实际数据和期望数据的比较,并 将比较结果写入日志。一般情况下,测试的结果是通过对验证点的执行而得到的。51Testing软件测试网^5E$}C,By1k.B#^
RFT提供了多种形式的验证点,包括:51Testing软件测试网q+teY7Vq4R)Q.X'n"w

^)}(_3pu m#S t0静态验证点(Static Verification Point):静态验证点是在录制(Record)RFT脚本的过程中通过向导插入的验证点,它在脚本回放(Playback)的过程中自动被验证。

r1OD"hz3dBc0

Ga4wX+M g{t0手动验证点(Manual Verification Point):如果验证点所要验证的内容是由脚本开发人员在脚本中所提供的,则需要建立手动验证点对其进行验证。例如待验证数据来自外部数据源的情况,脚本开发人员需将数据读取后以参数的形式显式传给验证点。51Testing软件测试网(|%o qHpz6r

51Testing软件测试网5?KA7b/I*M? @

动态验证点(Dynamic Verification Point):动态验证点是在脚本首次回放时建立的。验证点一旦建立,其行为就和静态验证点相同了。

'fj9z m W\0

RP9VRs*\0Q0如 果以录制-回放(Record-Playback)模式使用RFT进行图形界面(GUI)的自动化回归测试(Regression Test),较常用的是静态验证点。而由于RFT的数据驱动测试特性以及与其他RUP工具的良好集成,使之也是非图形化界面的功能测试的首选工具之一。在 这些测试用例中,存在着大量的用户自定义类型对象,这些被测试对象并不能在录制过程中被插入对象映射表(ObjectMap)中,也就是不能使用静态验证 点来进行验证,这就需要我们使用手动验证点来比较它们。

8{[dS Y[0

-B~C6Z }"q[A4B01.2 验证点执行过程

AS"rR5d-l*@C051Testing软件测试网$hO%pU7vH!s_Ao

在RFT中,手动验证点有两种声明形式:51Testing软件测试网u,H7^s.H!kJL
? IFtVerificationPoint vpManual (java.lang.String vpName, java.lang.Object actual)
0hCz A(D$q$f |4V PJ0该声明接受两个参数,第一个参数为验证点的名称,第二个参数为被测试对象。可以通过如下方式在脚本中调用该方法:

&Hh}.gfK051Testing软件测试网 d4S h5I@|1|

vpManual("VP1", "The object under test").performTest();

$tpK9HA8zf0

*j} Qw Tb0这条语句的作用就是判断被测试对象和基准线(Baseline)是否一致。这里所说的基准线就是期望数据,它以XML格式被存储在磁盘上,后缀名rftvp。
;Tg d \7^ dmc0当回放脚本时,如果基准线所对应的文件已经在磁盘上存在,则RFT会比较被测试对象和基准线中的数据是否一致,如果一致,则测试结果为成功(Pass),否则为失败(Failed);如果基准线尚不存在,则会以当前被测试对象作为基准线存入磁盘。
e.~ B j0r0?? IFtVerificationPoint vpManual (java.lang.String vpName, java.lang.Object expected, java.lang.Object actual)

_1o9P*u Y+`8yHj0

(t?%e x/rY0手动验证点的另一种声明接受三个参数,第一个参数为验证点的名称,第二个参数则为期望数据,第三个参数为实际数据,也就是被测试对象。可以通过如下方式在脚本中调用该方法:

te2o Dm#N0

~K(k7f| L;p0vpManual("VP1", "Expected object", "The object under test").performTest();

s5qh%Jc(c s051Testing软件测试网,cK;XT I Q9Yz e

当脚本回放时,RFT直接比较期望数据和实际数据,并将比较的结果被写入日志。总的来说,验证点的执行过程如图1所示:

4a,F0\5\J~v D2} e _051Testing软件测试网[tqN$m/lU

d _g7`X8j8S0

_bXKJ}0图1 验证点执行流程图51Testing软件测试网(Ev#{5Mj0Lqt#F
51Testing软件测试网!}y}Jg_HEy&CC

,q-x4\%v.m0不 难看出,在执行验证点的过程中,涉及到了将数据写入磁盘以及从磁盘中恢复数据的操作。将这些数据写入磁盘便于测试人员使用工具查看和编辑。但是这也带来了 一个问题,对于开发者自定义类型的对象,哪些属性需要被写入磁盘,这些属性按照什么顺序写入都是类型相关的,需要脚本开发者自行定义。更重要的,如何比较 自定义类型也是RFT所不能确定的,换句话说,脚本开发者需要告知RFT对象一致的标准。以上这两个任务都需要通过创建ValueManager来完成。51Testing软件测试网FhG2jR3w]F q

0th AFl5Z0ValueManager 可以用类特定(Class-specific)的形式比较和持久化用户自定义的对象。它是IManageValueClass接口的实现。只有拥有自己的 ValueManager的类型才能作为参数传递给vpManual。这些类型被称为基于Value-class的类型。缺省的,只有基本数据类型, String,Vector等少数类型才是基于Value-class的类型那么,如何才能实现我们自己的ValueManager呢?在下面的章节中, 我们将通过一个实例来帮助您建立自定义类型的ValueManager。51Testing软件测试网J]!FdA+H

51Testing软件测试网;lrUH&B%KK

2. 实例分析--如何实现ValueManager51Testing软件测试网@JC3i!P Q!u

51Testing软件测试网[ B8f9T&TF/Q sR-E;D

2.1需求的提出

-wWm%EF Fm0

z o2F ji5B9p9I0假设我们需要验证一个计算图形重心的算法。该算法的输出结果为一个自定义类型MyPoint,它表示的一个平面上的点。它具有两个属性x,y,分别表示其横纵座标。MyPoint的实现如下,除了构造函数,它还提供了x和y的getter和setter方法。

w8]6CV,`ZV0

X1b ?uN S-};I:]"y051Testing软件测试网 u_ZU&EE%O{3H
public class MyPoint {
(h%]%Uq"R-b"D&J051Testing软件测试网$N9|5v r4y
int x;51Testing软件测试网 CJ']7WPP
int y;51Testing软件测试网$?9eg$Vq
51Testing软件测试网;L7] Hd{Y3b
public MyPoint(int x, int y) {51Testing软件测试网P;R-z['rqU
this.x = x;51Testing软件测试网lg\8fE&w;dOCI"i
this.y = y;
e2gJ LZ j1Qq-z0}51Testing软件测试网nA1\'J3v/A

1W@lM6i&{0public int getX() {51Testing软件测试网-\;pVca| |'~#O0T
return x;51Testing软件测试网HFpNPI
}

8e/h"b+G't6s6M051Testing软件测试网,M2Wg|SA#X;f|

public void setX(int x) {
QqSp4D M\6z0this.x = x;51Testing软件测试网Q-J?SLr
}

p3Nqf&T/vx0

7|M*K}T7w)G0public int getY() {
B0b2Vx!z|o8U0return y;51Testing软件测试网D+{I`Z'Z_9]rJ
}

Dcz K(Cg&l051Testing软件测试网Qr0|1P%{']B\

public void setY(int y) {51Testing软件测试网 D-l6W {,ur
this.y = y;51Testing软件测试网#M1`n}'p
}

oMz;g&W Z\ I.hB u0

X i;j$P8|)a0}51Testing软件测试网!o X(NYqR$m

*s.y"i"UuS0我们的测试人员需要判断该算法得到的重心点是否正确,可以使用了如下脚本:

%\t:k#Z;zt0

K\:f]c051Testing软件测试网I.W,? H @/Q
MyPoint point = getCenterOfGravity(polygon);51Testing软件测试网 ^2W_!cF-_V
vpManual("VP_CG", point).performTest();51Testing软件测试网s9~3{atUi{T

51Testing软件测试网 V(L/FyKooH

上面的语句将在第一次回放脚本时将得到的重心坐标写入磁盘;测试人员可以使用验证点编辑器查看得到的基准线是否正确;在此后的回放中(通常是回归测试中),上述代码的工作就是比较当次回归测试得到的重心对象与基准线是否一致。

H.g]Hc051Testing软件测试网-zr-{5R/csi?

但 是,如果直接调用该语句,RFT就会抛出异常(Unsupported type, value class required),说明MyPoint不是基于Value-class的类型。要使MyPoint成为Value-class,必须为其实现相应的 ValueManager,并部署到RFT中。

w `a+lt5ESE'B051Testing软件测试网0u~Qdg:EB

2.2其他验证自定义类型对象的方法

hmz1jB\(\9]k0

| Y8J*j*w+Mq0在介绍如何实现ValueManager之前,首先让我们来看一下其他的解决办法。51Testing软件测试网;FUm~L\-N

!B!H4s\0z,`M0第一种方法,我们可以逐一比较自定义类型的属性来验证被测试对象是否符合要求。如下例所示:51Testing软件测试网o,N$dld l3~

51Testing软件测试网?-rCS D"Mvi1f6y

51Testing软件测试网Js oh1k cR
MyPoint point = getCenterOfGravity(polygon);51Testing软件测试网&TKM%\J l4L+k,`8w
vpManual("VP_CG1", new Integer(point.getX())).performTest();
5e%{8omh]0vpManual("VP_CG2", new Integer(point.getY())).performTest();

)V[0`(y*~_y.k i7e&G D0

%xe dSrV0使用logTestResult方法也可以达到同样的目的:

/[n'P&h$NN*^e0

G8axb ])XTdpk;}051Testing软件测试网/]_^ wM2gwC
MyPoint point = getCenterOfGravity(polygon);
%ROx B4o#e,]0boolean flag = (point.getX() == 6 && point.getY()==8);
7n` VcyAK"[.cC0logTestResult("This is not a VP", flag);51Testing软件测试网 JV1o4H!zRY,WP

a M2{9w3qw0这 两种方式都有其局限性,第一种方式会使验证点的数量增加,特别是当自定义类的属性很多的时候;并且该方法也不利于重用,如果多个脚本都需要验证 MyPoint,则脚本开发的工作会大大增加;第二种方式虽然不增加验证点数目,但是由于logTestResult只记录比较结果,使日志中的信息不 足。特别是在测试用例失败的时候,这不利于测试人员定位问题所在。51Testing软件测试网'Uax5B8M
因而,为自定义类型实现ValueManager较以上两种方法更好。

H*EV2J4oAzTB0

^ U8?7i _#`0

0J7c!}Y z{0

[b7X3t&~`-X8Y0图3 使用ValueManager则可以通过验证点编辑器查看和编辑验证点数据,利于问题定位
xtt@1^ BQ S0?51Testing软件测试网 Uvvw+m|P

qDwZSn'ngHK0图4 使用logTestResult方法只能记录结果,不能记录数据,信息不足
Q?_.f l!r;t0
\!g/s0t ?|'P7\|02.3创建ValueManager51Testing软件测试网-B$_a!Ta.VC)u

Ox-["GL r7vr0现 在,我们准备创建MyPoint的ValueManager--我们命名为MyPointValue了,如上文所述,它是 IManageValueClass接口的一个实现,该接口有7个方法:persistIn, persistInNamed, persistOut, compare, getCanonicalName, getClassName和createValue。这些接口分别完成哪些事情呢?

;y:v1F-o.Jc~ z zn0

c/D;v f aKC @:t0我们再来看看,执行验证点的过程中 ValueManager是怎么工作的。如图1所示,验证点执行过程中必须的步骤可以归为三类:读取数据--从基准线中读取数据;写入数据--将期望数据 写入日志,将实际数据写入日志,写入基准线;比较--比较期望数据和实际数据。以上三类动作都需要ValueManager的参与。下面以MyPoint 为例:

y f!N"GJ&[*vO+J!A051Testing软件测试网s+}*DS.[T-^ f)X

如图5,在从基准线中读取数据的过程中,如果脚本发现基准线存在,RFT会读取基准线数据。如果RFT在注册了的 ValueManager表中找到了相应的ValueManager--对MyPoint来说,就是找到了MyPointValue--就会创建该 ValueManager的一个实例,然后调用该实例的persistIn方法。persistIn方法包含了如何读取MyPoint内容的逻辑,其返回 值就是一个MyPoint对象,这样就将存储在磁盘上XML格式的MyPoint对象恢复出来,供RFT继续使用了。

#CD)_0ha hx051Testing软件测试网)M$l5`F4Qj3@ L

图5 从基准线中读取数据51Testing软件测试网,^5]"tSK

51Testing软件测试网(`nw.L,Klg


-R!f({#~Z/a p/Yf'ro7M%{B051Testing软件测试网tA @,^!JPv)V i
如 图6,在写入数据的过程中,无论是写入期望数据,写入实际数据还是写基准线,都是通过persistOut方法完成的。同样的,RFT首先找到 MyPoint的ValueManger,将其实例化,然后调用MyPointValue的persistOut方法将其写入磁盘。51Testing软件测试网 m!h5c#D-_kp

1?8O e:MR N0图6 写入基准线51Testing软件测试网`C9q@;L |J.J2W$O!eH
51Testing软件测试网&C]l#r2U

4Dm;Vx4q9K8F2l0
K#G\)Be(Lcb c0如图7,在比较两个MyPoint对象的时候,RFT还是先找到MyPoint的ValueManger,将其实例化,然后调用MyPointValue的compare方法,得到一个分值,根据分值和用户设置判断期望数据和实际数据是否一致。

[:[%iKNdS7f051Testing软件测试网-{B]Q,M} C

图7 比较期望数据与实际数据

n+b wE V3P0

P.n%d0fj;c8J2RhI r0
`8w/F#_7`j|2o0理解了IManageValueClass接口各方法的作用,我们就可以来实现这个ValueManager--MyPointValue了。如上文所述,有7个方法需要实现。其中最关键的是4个方法:
5F V%`/l.SL2`0

;A}{#?9f@X051Testing软件测试网"uh*F.@$As

持久化输出方法

"vs4}W7R@ Q Q051Testing软件测试网h zT6Y?

public void persistOut(Object obj, IPersistOut persistout,
W)r7e2T%j\2d,i!X'A+E0?IAuxiliaryDataManager auxdatamanager)51Testing软件测试网| LJXK$iX
51Testing软件测试网)_S5J4A8]:JI
51Testing软件测试网L nLM }$lrp;b
该方法通过persistout将对象obj的属性写入到磁盘上。obj就是要写入的对象,persistout是负责写操作的接口,由RFT传入,auxdatamanager是用于命名相关文件的接口,由RFT传入,通常不是使用到这个参数。
F k&H.RI:mC5Y1G0?以MyPoint为例,下面的代码段将MyPoint的属性x,y依次记录到磁盘上:

}#L xh/Y*{0

)p,D)? X*It a6qU0public void persistOut(Object obj, IPersistOut persistout,51Testing软件测试网5T-d pzu:I
IAuxiliaryDataManager auxdatamanager) {51Testing软件测试网7W&h(TC@2X @9B[L an
if (obj instanceof MyPoint) {
b`V l)s6f [yJO4t0MyPoint point = (MyPoint)obj;51Testing软件测试网w'acZ*gv.`V#r

:YKM9v4YS0y0// persistout是负责写的接口,51Testing软件测试网b"|r ]7c.Qp8X;z
//write方法接受的第一个参数是要写入的属性的名称
O,a0j-{4u.d0//第二个参数是要写入的属性值
dJ,U/Uk0Lw0persistout.write("X", point.getX());
3JS.` i'Q2n/o\0persistout.write("Y", point.getY());51Testing软件测试网&dG ^6h6|d.U
}51Testing软件测试网;I }gcl B
}51Testing软件测试网SkP4@2Z$yx~
51Testing软件测试网 eOB Ie zW i$o
51Testing软件测试网(p%KqG$\B?l
对于测试开发人员来说,这里写入了哪些属性,将来哪些属性才能够被恢复出来。51Testing软件测试网(z"~dj
持久化恢复方法51Testing软件测试网s{h`wAQIC
RFT 提供两个方法读入持久化了的对象。一种接受IPersistIn类型的参数,另一种接受IPersistInNamed类型的参数。二者的不同在于,前者 是根据对象属性的存储顺序进行读取的,而后者是按照对象的属性名称进行读取。以MyPointValue为例, 方法一的实现如下:51Testing软件测试网;Fn1UY,jV5l \ T;S

51Testing软件测试网F!J,^b.J~

public Object persistIn(IPersistIn persistin, IAuxiliaryDataManager auxdatamanager) {
_.N$p B(N"eKb0// 在persistOut方法中,是按照x,y的先后顺序写入磁盘的,51Testing软件测试网5HN N!W6F
// 那么在该方法中就需要按照x,y的顺序将其读取出来read的参数就是写入磁盘的序号。51Testing软件测试网_s kC#mY@ fr
int x = ((Integer)persistin.read(0)).intValue();
_r5W4F4b2YG0int y = ((Integer)persistin.read(1)).intValue();51Testing软件测试网HN0n4H?0E yq
return new MyPoint(x, y);51Testing软件测试网1k$E!}2CN#A VX1n"I
}

(Ije`ZV,Z'E9om9v0

j$adl0\m:vg}0方法二的实现如下:51Testing软件测试网*kW%^\` IR

51Testing软件测试网makJ7FeEs(`

public Object persistIn(IPersistInNamed persistinnamed, IAuxiliaryDataManager auxdatamanager) {
*kWc uN@%h_U(Y$z0// 该方法根据persistOut时写入的属性名称读取属性,read的参数是属性名称51Testing软件测试网Ywm~l9my8Nw
int x = ((Integer)persistinnamed.read("X")).intValue();51Testing软件测试网g u$p1Jz#e+F]6h
int y = ((Integer)persistinnamed.read("Y")).intValue();51Testing软件测试网D7U:x,zg0JnB2n4[#c
return new MyPoint(x, y);51Testing软件测试网X A6@6~ F4r4|2z2f$j_{F
}51Testing软件测试网-^&h;hiyTq/b

"F Hf;y,W2[t!Z0?比较方法51Testing软件测试网*^R4cm)UK#s

51Testing软件测试网aH'SNz:MFN#By F

public int compare(Object obj1, Object obj2, ICompareValueClass comparedvalueclass)51Testing软件测试网zi@'\D+?

51Testing软件测试网m'AF} np

compare方法用于确定两个对象一致与否,其前两个参数为要比较的对象,返回值则为一个0-100之间的整数,返回值越大则被比较的对象越相似。51Testing软件测试网y&CsT8i*{};Q
以MyPointValue为例,其比较原则是,如果二者相等则返回100(表示不同),否则返回0(表示相同)。

;RzcN+WSiTV051Testing软件测试网\"[:Mu-A$}%s'^ A|K

public int compare(Object obj1, Object obj2, ICompareValueClass comparedvalueclass) {51Testing软件测试网4K9RVL]~4VP
if ((obj1 instanceof MyPoint) && (obj2 instanceof MyPoint)) {51Testing软件测试网%c9l0Jw9P:`/V
MyPoint point1 = (MyPoint)obj1;
"|z,h`V2B0MyPoint point2 = (MyPoint)obj2;51Testing软件测试网3d+^ \?3Ngn \
return (point1.getX()==point2.getX() && point1.getY()==point2.getY()) ? 100 : 0;
`C:m5dv$[+i/k}0} else {51Testing软件测试网J%n!Nq)WEZ
return 0;51Testing软件测试网U&l [ y_7W2n?
}51Testing软件测试网 Q CDW}['Z
}

/R\Ks0T#xl-R0

4a!g,RT[*LIH9|(O0?其他方法51Testing软件测试网L`)R)T"_U
除上述四个方法,还有3个方法也需要实现,分别是

rR0{8b2Wj0

%PX/{0c{Md+C A+p8i0public String getCanonicalName() {51Testing软件测试网hq-[#U&` l4d@:@
return "MyPoint";51Testing软件测试网)F_)xG?d,}
}51Testing软件测试网 GN4e/qru ^'_&MQ
51Testing软件测试网&qE)MS6]#PS%P?

4QhXv]]0上面的方法返回该Value-class的平台无关的规范名称51Testing软件测试网 b`9S+u@:l[X+tOf D

51Testing软件测试网y t,@-_J(OKl0~d&w |

public String getClassName() {51Testing软件测试网D Fv+H0k#g
return "com.rational.ft.sample.MyPoint";51Testing软件测试网/{!n.sLD
}
q vd-_ @I*@.JG{051Testing软件测试网yK&h\(etl

UV Cj9qSV0上面的方法返回ValueManager支持的Value-class的名称51Testing软件测试网0I9Q%C;@c:@%y!S-x

51Testing软件测试网&~8H7@6e@F

public Object createValue(Object sourceToCopy) {
6ET/^n*Z2})L0return null;
:lC0E"zV(v,xD0}

V._ R1V'p"b@051Testing软件测试网)uH:V!i0Uo

上面的方法返回被测试对象的一个拷贝,可以返回null,较少会被用到。

'`F NU"hY X L8Q051Testing软件测试网O E{NFZ0iL6O

2.4 部署ValueManager51Testing软件测试网P Z['hO)Dso

51Testing软件测试网j/Y1|KZ j"E6q\Fw

上面的7个方法实现了,MyPoint的ValueManager也就完成了。要在工程中使用ValueManger,还必须将ValueManager部署到RFT中。这也是注册ValueManger的过程。
Ep0s1~'b*z0首先我们需要将ValueManager的实现导出为jar文件,例如vmsample.jar。51Testing软件测试网*Q7w#P+H morS

51Testing软件测试网5@koD g-o|T v5@$N |

第 二步,我们还需要创建RFT自定义文件。RFT自定义文件是后缀名为RFTCUST的XML文件,用于定义开发者扩展的proxy, valuemanger等。RFT自定义文件内容如下,不难看出,其中ComponentModel元素中Obj一段是用于定义我们所创建的 ValueManger的。ValueClass是要被验证的自定义类型;Manager是该自定义类型的ValueManger。

XwU Y_:V.p0

7QP#S }B(W n1p0
CKD1k:V+?1N+r0<ConfigFile L=".ConfigFile">
M,}j"h \jUz0<Section L=".ConfigFileSection">51Testing软件测试网 b!B]+Jfq,o j
<Name>valueManagers</Name>
8MT,qC0jZV n3k0<Val L=".ValueManagerManager">

o?Q ~J p.{0

8Nukh6h0<ComponentModel L=".ComponentModel">
m:c S+`g!ikv0<Name>Java</Name>51Testing软件测试网*L)NN5rZ:l(xBch
<Obj L=".ValueManager">51Testing软件测试网PPh7Fp)A4F:W8_
<Id>.MyPoint</Id>
,?y9@5];i0<ValueClass>com.rational.ft.sample.MyPoint</ValueClass>
)b`wTo;EE T0<Manager>com.rational.ft.sample.valuemanager.MyPointValue</Manager>
iW3G.uPk!N0</Obj>
jF]0?Z,Y0</ComponentModel>51Testing软件测试网z6?3]dO n!d
</Val>51Testing软件测试网_9|3[p_w2Q B N
</Section>51Testing软件测试网\*@cTD*p
</ConfigFile>51Testing软件测试网#epqt2W \/R@

"Mt|%O+SZ/iKV0将. jar文件和RFTCUST建立好后,把这两个文件都放到C:\Documents and Settings\All Users\Application Data\ibm\RFT\customization目录下,ValueManager即被部署到RFT上了。(有可能需要重新启动计算机

r} Yss^ hx051Testing软件测试网#c Y#~9FDv

结论
\#Z,I6v`?;J ]YY3_L0验证点是脚本的重要组成部分。对自定义类型的验证又是测试中所不可避免的。通过开发ValueManager来扩展RFT对自定义类型验证的支持,较之其它方法可重用性好,并使信息能够在日志中一目了然,而且便于修改期望数据。这一特性使RFT的应用更加自由。51Testing软件测试网b*o?*FZ


TAG:

 

评分:0

我来说两句

Open Toolbar