逃避不一定躲得过 面对不一定最难受 孤单不一定不快乐 得到不一定能长久 失去不一定不再有 转身不一定最软弱 别急着说别无选择

单元测试概述(2)

上一篇 / 下一篇  2008-11-27 00:03:04 / 天气: 晴朗 / 心情: 郁闷 / 个人分类:个人文章

二 测试代码编写51Testing软件测试网6qS1V OX8R#zg/uw:h
  多数讲述单元测试的文章都是以Java为例,本文以C++为例,后半部分所介绍的单元测试工具也只介绍C++单元测试工具。下面的示例代码的开发环境是VC6.0。51Testing软件测试网#x1I7d/g T
51Testing软件测试网C/x(bK6TF?K4C
产品类:
K"k%Y4BY3WJ0
class CMyClass 
G9a5ziA ma8O3w0
{51Testing软件测试网6C0yZ/vPq^2X%o
public:
luS0\7n2Y?](w0
    int Add(int i, int j);
6w&izQB6BXO0
    CMyClass();
;ie8es0Z_Dy0
    virtual ~CMyClass();
:oC|K3Nw H0
51Testing软件测试网]+k#KQ |;|e
private:
Vy4v MZRj [!j0
    int mAge;      //年龄
2M/S\.v7M%L:H6k0
    CString mPhase; //年龄阶段,如"少年","青年"
#SD!J#yYw"x@|8W7H0
};
0~/|,D`:~-]%Qw0v0

E~5sm0h0{)d3b ze'?0
建立对应的测试类CMyClassTester,为了节约编幅,只列出源文件的代码:
0aX#v"rN0
void CMyClassTester::CaseBegin()51Testing软件测试网/`,kW*C&V8]e)E+tM
{
r@nX[Pd8~3Y0
    //pObj是CMyClassTester类的成员变量,是被测试类的对象的指针,
C\5W_ Cc6s6t0
    //为求简单,所有的测试类都可以用pObj命名被测试对象的指针。
ySYF.NPG0
    pObj = new CMyClass();
3q#Db Bw&v0
}
b6p[C `f&OAz{0

(I~pLR ZW0
void CMyClassTester::CaseEnd()51Testing软件测试网*@x,Y[8j3z/K:U ^
{51Testing软件测试网E} [owu
    delete pObj;
1t;A!a!J}nE M0
}
Vd3h"`5K2d%T"R$ET0
测试类的函数CaseBegin()和CaseEnd()建立和销毁被测试对象,每个测试用例的开头都要调用CaseBegin(),结尾都要调用CaseEnd()。51Testing软件测试网d#U?(U'g)V?I N

T"@3I5p[@s4VJ0
接下来,我们建立示例的产品函数:51Testing软件测试网H"u&M9A)R@
int CMyClass::Add(int i, int j)
0h\/a-Oekj0
{51Testing软件测试网N9\n\[U3E K*?
    return i+j;
6B*rSOy"L;v Q k0
}51Testing软件测试网&c{ts2l
和对应的测试函数:
jTp;\w0
void CMyClassTester::Add_int_int()
&PoTkV0
{
J:obu~&o V0
}51Testing软件测试网:X%n4P.kU/w-F
把参数表作为函数名的一部分,这样当出现重载的被测试函数时,测试函数不会产生命名冲突。下面添加测试用例:51Testing软件测试网|6KnZf*\4K xJ3?
void CMyClassTester::Add_int_int()
}Uh"mq:uFN0
{51Testing软件测试网G"?9RJ-Y-p i`,v
    //第一个测试用例51Testing软件测试网9hN k].o,e
    CaseBegin();{              //1
;tkh:m9i0
    int i = 0;                //251Testing软件测试网wB }8p[j
    int j = 0;                //3
S2iJs&c/b0
    int ret = pObj->Add(i, j); //451Testing软件测试网 w5j[!]#d!PA
    ASSERT(ret == 0);          //5
,FEH4j(~ b(M0
    }CaseEnd();                //6
O&x,],D;V9anv^B0
}
e;_,Br eiH~#F2w0
第1和第6行建立和销毁被测试对象,所加的{}是为了让每个测试用例的代码有一个独立的域,以便多个测试用例使用相同的变量名。51Testing软件测试网'x7@g%z]
第2和第3行是定义输入数据,第4行是调用被测试函数,这些容易理解,不作进一步解释。第5行是预期输出,它的特点是当实际输出与预期输出不同时自动报错,ASSERT是VC的断言宏,也可以使用其他类似功能的宏,使用测试工具进行单元测试时,可以使用该工具定义的断言宏。
tsVG*z5Y%r0

M'@,DwI`0
  示例中的格式显得很不简洁,2、3、4、5行可以合写为一行:ASSERT(pObj->Add(0, 0) == 0);但这种不简洁的格式却是老纳极力推荐的,因为它一目了然,易于建立多个测试用例,并且具有很好的适应性,同时,也是极佳的代码文档,总之,老纳建议:输入数据和预期输出要自成一块。51Testing软件测试网]hL`"^
  建立了第一个测试用例后,应编译并运行测试,以排除语法错误,然后,使用拷贝/修改的办法建立其他测试用例。由于各个测试用例之间的差别往往很小,通常只需修改一两个数据,拷贝/修改是建立多个测试用例的最快捷办法。51Testing软件测试网%h"D;@;[(?#x
51Testing软件测试网"xn e0K)h7{!KI
三 测试用例51Testing软件测试网V CeaB*EL\{
  下面说说测试用例、输入数据及预期输出。输入数据是测试用例的核心,老纳对输入数据的定义是:被测试函数所读取的外部数据及这些数据的初始值。外部数据是对于被测试函数来说的,实际上就是除了局部变量以外的其他数据,老纳把这些数据分为几类:参数、成员变量、全局变量、IO媒体。IO媒体是指文件、数据库或其他储存或传输数据的媒体,例如,被测试函数要从文件或数据库读取数据,那么,文件或数据库中的原始数据也属于输入数据。一个函数无论多复杂,都无非是对这几类数据的读取、计算和写入。预期输出是指:返回值及被测试函数所写入的外部数据的结果值。返回值就不用说了,被测试函数进行了写操作的参数(输出参数)、成员变量、全局变量、IO媒体,它们的预期的结果值都是预期输出。一个测试用例,就是设定输入数据,运行被测试函数,然后判断实际输出是否符合预期。下面举一个与成员变量有关的例子:51Testing软件测试网 G Z%P2r3z,Dwb+gM
产品函数:
6H#g'};c.[a0
void CMyClass::Grow(int years)51Testing软件测试网JO1Jc_ ?+PNTp
{
It U n7Gwo7j0
    mAge += years;
_ hZlQ"F6h j|0
51Testing软件测试网8J;EWZP H4\6EM
    if(mAge < 10)
6l V#R:dAV6x7c P.N0
        mPhase = "儿童";
L@0fNx4kBz:S$x0
    else if(mAge <20)51Testing软件测试网e },oBrh
        mPhase = "少年";
E8? ZA,U4Vp0
    else if(mAge <45)51Testing软件测试网]@g'S-oz0h(r
        mPhase = "青年";51Testing软件测试网&o7~${$M"zp?
    else if(mAge <60)
Ll'~2N9[6d{^0
        mPhase = "中年";
u-M o TUx0
    else51Testing软件测试网"G n$`%J d6i&{"H
        mPhase = "老年";51Testing软件测试网0q7Dad*\w/g5z
}
X9d6j `u7w[%w0
51Testing软件测试网.j^Cv2ND5P
测试函数中的一个测试用例:51Testing软件测试网1_:S j^tK
    CaseBegin();{51Testing软件测试网:Iy6eD'y S
    int years = 1;
]#A%^A0fR0
    pObj->mAge = 8;51Testing软件测试网7@0E+co.Zda.y'y!B T
    pObj->Grow(years);51Testing软件测试网/nU7AZ/U_!?
    ASSERT( pObj->mAge == 9 );
8M/i"j.e(h:h;z0
    ASSERT( pObj->mPhase == "儿童" );
&z'vQe t._#G0
    }CaseEnd();51Testing软件测试网fD\3aoM
在输入数据中对被测试类的成员变量mAge进行赋值,在预期输出中断言成员变量的值。现在可以看到老纳所推荐的格式的好处了吧,这种格式可以适应很复杂的测试。在输入数据部分还可以调用其他成员函数,例如:执行被测试函数前可能需要读取文件中的数据保存到成员变量,或需要连接数据库,老纳把这些操作称为初始化操作。例如,上例中 ASSERT( ...)之前可以加pObj->OpenFile();。为了访问私有成员,可以将测试类定义为产品类的友元类。例如,定义一个宏:51Testing软件测试网2~N EB:lx
#define UNIT_TEST(cls) friend class cls##Tester;51Testing软件测试网,]5Ux_ bS ?4Z
然后在产品类声明中加一行代码:UNIT_TEST(ClassName)。
xKb/[7}+V0

!\3R/b{*ZP0
  下面谈谈测试用例设计。前面已经说了,测试用例的核心是输入数据。预期输出是依据输入数据和程序功能来确定的,也就是说,对于某一程序,输入数据确定了,预期输出也就可以确定了,至于生成/销毁被测试对象和运行测试的语句,是所有测试用例都大同小异的,因此,我们讨论测试用例时,只讨论输入数据。
"d nT.??4T-IB:a0
  前面说过,输入数据包括四类:参数、成员变量、全局变量、IO媒体,这四类数据中,只要所测试的程序需要执行读操作的,就要设定其初始值,其中,前两类比较常用,后两类较少用。显然,把输入数据的所有可能取值都进行测试,是不可能也是无意义的,我们应该用一定的规则选择有代表性的数据作为输入数据,主要有三种:正常输入,边界输入,非法输入,每种输入还可以分类,也就是平常说的等价类法,每类取一个数据作为输入数据,如果测试通过,可以肯定同类的其他输入也是可以通过的。下面举例说明: 51Testing软件测试网8sA*p[/N`0]s.~
  正常输入51Testing软件测试网bta n2fk*@2v
  例如字符串的Trim函数,功能是将字符串前后的空格去除,那么正常的输入可以有四类:前面有空格;后面有空格;前后均有空格;前后均无空格。51Testing软件测试网]cOX"O1T
  边界输入51Testing软件测试网x*}!Ml$|xN |
  上例中空字符串可以看作是边界输入。
s(S YzS8Ms1~!M X0
  再如一个表示年龄的参数,它的有效范围是0-100,那么边界输入有两个:0和100。51Testing软件测试网6O9tbtF u
  非法输入51Testing软件测试网d&RpL0C4{+S{7w(S
  非法输入是正常取值范围以外的数据,或使代码不能完成正常功能的输入,如上例中表示年龄的参数,小于0或大于100都是非法输入,再如一个进行文件操作的函数,非法输入有这么几类:文件不存在;目录不存在;文件正在被其他程序打开;权限错误。
.l3Q0@0[s.VN0
  如果函数使用了外部数据,则正常输入是肯定会有的,而边界输入和非法输入不是所有函数都有。一般情况下,即使没有设计文档,考虑以上三种输入也可以找出函数的基本功能点。实际上,单元测试与代码编写是“一体两面”的关系,编码时对上述三种输入都是必须考虑的,否则代码的健壮性就会成问题。51Testing软件测试网^r+VUx E,F
51Testing软件测试网^7F xd0P#}


TAG: 个人文章

 

评分:0

我来说两句

Open Toolbar