您的位置:首页 > 移动开发 > Objective-C

【Nunit入门系列讲座 4】NUnit断言- 对象识别断言

2011-11-17 01:07 302 查看
作者:shinoy

 时间:2011/11/16 版权所有,侵权必究。

出处:http://blog.csdn.net/snowshinoy

本节示例代码下载: 示例代码

         上节我们初步认识了NUnit断言的作用以及如何在测试中通过断言构成测试。其实NUnit的断言系统功能庞大而丰富,不是简简单单一篇教程就能尽述的,在后续的我们会一点点深入学习,一旦掌握了NUnit的断言系统,也就可以说是入门了。一些朋友会发现,上节介绍的数值相等/不等断言,并不能很好地适应现在软件测试的需求。在过去的面向过程软件中,没有类和对象的概念,数值类断言基本可以胜任。但是现代软件设计中,面向对象已经是一种非常普遍的设计方法,在不同功能模块之间传递的不再是单纯的数值参数,很多时候,对象才是模块之间信息交互的载体。本节中我们就来了解一组适合于对象的断言,以及体会下这类断言在面向对象软件测试中是如何应用的。

 一、背景知识:类和对象

        相信大家一定经常听到这2个概念,这是面向对象软件的基本概念。这个概念很多书都有介绍,有些介绍的很清楚,有些介绍的不清不楚。其实这个理解可深可浅,我在这里简单解释下,关于这2个概念的理解和领会,需要大家在使用的过程中,逐渐加深。如果用一个比喻来描述类和对象的话,类就如同生物界的DNA编码,不同种类的生物,有不同的DNA编码,同一类的生物,有着相同的DNA编码(我们不考虑基因突变和一些个体的差异)。而对象就是具体的一个生物个体,他的特征是由DNA编码定义好的。比如人这个种类(class),每个人(instance)都具有相同的DNA编码,这就决定了作为人,都是有人的一些特征的,不具备这些特征,那也就不属于人类这个类别的了。回到软件来看,我们定义一个类,就是如同定义了一段DNA编码,而new这个类的一个对象,就如同根据DNA孵化一个真实的动物(也包括人类)。这样,我们就可以理解到类是对象的模板,而对象是类的实例这个说法的意思了。

        换个视角,从程序本身来考虑,类定义是保存在程序文件中的一段代码,这个在程序没有运行的时候就是存在的。而对象是根据这段代码以及程序的对象构造语句,在内存中创建的一段空间,这段空间保存了这个对象相关的所有信息,包括属性值,以及成员函数。也就是说,程序本体中保存了类定义,而只有程序运行的时候,对象才会被生成,没有运行的程序是没有对象的,对象是存在于内存中的。可以从下图体会下这种布局:



 二、对象识别断言(Identity Asserts)及其应用

         在面向对象的白盒测试中,NUnit提供了针对对象的一组断言,我们来了解一下它们。

Assert.AreSame( object expected, object actual );
Assert.AreSame( object expected, object actual, string message );
Assert.AreSame( object expected, object actual, string message,
params object[] parms );

Assert.AreNotSame( object expected, object actual );
Assert.AreNotSame( object expected, object actual, string message );
Assert.AreNotSame( object expected, object actual, string message,
params object[] parms );

Assert.Contains( object anObject, IList collection );
Assert.Contains( object anObject, IList collection,
string message );
Assert.Contains( object anObject, IList collection,
string message, params object[] parms );
        Assert.AreSame用来判断2个对象是否相同。这里要特别注意,2个对象相等,并不是指2个对象的属性完全相同即为相等。而是指2个对象指向内存中同一区域,即2个变量expect和actual指向同一个内存中的实例。结合上面的图,可以看到其实a,b2个对象内容完全相同,但是他们是内存中的2个不同实例,也就是2个不同对象。这样的2个对象不能称为相等。只有像a,c那样,指向内存中的同一实例,才可以成为a,c2个对象相同。

        Assert.AreNotSame用来判断2个对象是否是不同对象,也就是指向不同的内存实例。即使一个对象是有另外一个对象拷贝过来的,只要他们是在内存中的不同区域,也就是不等的。

        Assert.Contains是一个很有用的断言。他判断一个对象是否在一个已知的对象队列中。实际测试中,我们往往关心的不是2个对象是否相等,而是关心应该生成或者传递的对象是否成功生成以及传递了,这个时候,这样的断言极大的提高了我们的判定效率,这点我们将会在后面的例子中体会到。

       了解了这3组断言的含义后,让我们在一个实际测试的过程中,来学习和理解他们的使用。个人一直觉得泛泛的解释断言系统将会枯燥而不易掌握,所以我们通常会通过一个实际模块的测试,在应用中带大家学习新断言的用法并体会一些软件设计和测试中的思想及技巧。

        这次我们将会测试一个模拟汽车生产工厂的软件模块,代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FactoryNS
{
public enum color
{
Red = 0,
White,
Gray,
Unknown
}

public class Car
{
public color color;
public int price = 0;

public virtual void Run()
{
Console.WriteLine("could not run");
}

public virtual void Stop()
{
Console.WriteLine("no need to stop");
}
}

public class RedCar:Car
{
public RedCar(int price)
{
this.color = color.Red;
this.price = price;
}

public RedCar()
{
this.color = color.Red;
this.price = 120;
}

public override void Run()
{
Console.WriteLine("Red Car start to run");
}
public override void Stop()
{
Console.WriteLine("Red Car stop");
}
}

public class WhiteCar:Car
{
public WhiteCar()
{
this.color = color.White;
this.price = 100;
}

public WhiteCar(int price)
{
this.color = color.White;
this.price = price;
}

public override void Run()
{
Console.WriteLine("White Car start to run");
}
public override void Stop()
{
Console.WriteLine("White Car stop");
}
}

public class GrayCar:Car
{
public GrayCar()
{
this.color = color.Gray;
this.price = 90;
}

public GrayCar(int price)
{
this.color = color.Gray;
this.price = price;
}

public override void Run()
{
Console.WriteLine("Gray Car start to run");
}
public override void Stop()
{
Console.WriteLine("Gray Car stop");
}
}

// factory to produce cars according to orders
public class CarFactory
{
public List<Car> wareHouse;
public Car lastCar;

// order procedure by color
public void order(color color)
{
switch (color)
{
case color.Red:
lastCar = new RedCar();
this.wareHouse.Add(lastCar);
break;

case color.White:
lastCar = new WhiteCar();
this.wareHouse.Add(lastCar);
break;

case color.Gray:
lastCar = new GrayCar();
this.wareHouse.Add(lastCar);
break;

default:
break;
}
}
}
}


        CarFactory 就是一个轿车生产的工厂类,他提供给客户3种类型的轿车-红色,白色及灰色。每种类型车辆的价格不同(所以不能提供给客户错误的种类,不然问题就大了)。客户可以通过CarFactory的order方法来下订单,并告诉工厂所需要的汽车类型。然后通过工厂类的lastCar属性,可以拿到自己的汽车。而工厂接受到订单(order方法被调用)后,就会根据客户的需求来生产车辆,并放到仓库中(wareHouse属性)。每辆车都有Run和Stop方法,客户拿到车(通过lastCar属性)后,就可以开开停停,试试车子是不是好的,或者是不是自己想要的(不同颜色车子的Run和Stop会输出不同信息)。

        我们来想下,针对这个模块,我们应该做些什么测试。首先,要保证客户都能订到自己想要的车,并且不能出现几个客户订到了同一部车的情况(一房多卖的事情我们还是不干的)。那么我们就通过这样的方法来做这个测试:我们模拟2个客户订2部同样颜色的车子,然后比较这2部车是不是同一部车。可以通过如下的测试代码来完成

//same color cars ordering would work correctly
[Test]
public void SameColorOrderTest()
{

factory.order(color.Red);
Car myCar1 = factory.lastCar;
factory.order(color.Red);

Car myCar2 = factory.lastCar;

Assert.AreNotSame(myCar1, myCar2);
}


        这里我们就用了Assert.AreNotSame断言来判断我们订的2辆车是不是同一部,2辆车没有任何区别,颜色,价格(对象属性都相等),但是他们不应该是同一部车(对象不应该相等)。

        其次,我们还想测试一下这个工厂的意外情况处理能力,在程序中就是是否针对某些特殊情况作了考虑,这个也是一个程序是否优秀的标志之一。一个考虑周全的程序和一个考虑不周全的程序,在面对复杂的用户的时候,可能就是质的差别。对于这个汽车工厂来说,有种情况就是客户只下了订单,但是忘记告诉我们颜色了。我们希望这种时候,CarFactory能够生产给客户灰色的车辆,因为他比较大众些。大家可能已经注意到了,在CarFactory的模块设计中并没有很好的考虑到这种情况。我们下面就来设计这样的测试,并看最终是否能通过测试发现这里设计上的不足。这次我们模拟客户,先订一辆白色的,然后再下一个没有颜色的订单,看最终CarFactory我们的是什么样的车。理论上,他应该也给我们一辆不同于前一个订单的车,并且是我们希望的灰色。测试代码如下:

//order with no color designated will be accepted correctly
[Test]
public void NoColorOrderTest()
{
factory.order(color.White);
Car myCar1 = factory.lastCar;

factory.order(color.Unknown);
Car myCar2 = factory.lastCar;

Assert.AreNotSame(myCar1, myCar2);
Assert.AreEqual(color.Gray, myCar2.color);
}


     下面我们来看下最终的测试结果



        可以看到,第一个同样色车辆订单的测试通过了,CarFactory确实给我们生产了不同的车辆。而第二个未注明颜色订单的测试失败了,为什么失败了呢?我们来看下错误信息告诉了我们些什么。

CarFactoryTestSet.CarFactoryTest.NoColorOrderTest:

  Expected: not same as <FactoryNS.WhiteCar>

  But was:  <FactoryNS.WhiteCar>

        我们可以看到,原本我们期望得到的是一辆不同于前一订单的灰色新车,但是实际上,CarFactory并没有为我们生产这辆车。所以我们通过lastCar拿到的还是前面生产的那辆白色轿车。更改CarFactory的order函数如下

public void order(color color)
{
switch (color)
{
case color.Red:
lastCar = new RedCar();
this.wareHouse.Add(lastCar);
break;

case color.White:
lastCar = new WhiteCar();
this.wareHouse.Add(lastCar);
break;

case color.Gray:
lastCar = new GrayCar();
this.wareHouse.Add(lastCar);
break;

default:
lastCar = new GrayCar();
this.wareHouse.Add(lastCar);
break;
}
}
重新编译CarFactory模块,重新运行测试看下结果。



        至此,我们通过对象识别断言,完成了对CarFactory模块主要功能的测试。但是等等,CarFactory还有一个功能,就是生产完了车辆后会将车放入仓库(wareHouse)中。下面我们就来针对这个功能,编写测试。我们生产完几辆车后,来检查一下,前面生产的车,是不是都放在了仓库中。对了,很多朋友一定也想到了,我们要用Assert.Contains这个断言来完成这样的功能。测试代码如下:

//check if all cars ordered are in warehouse
[Test]
public void warehouseTest()
{
factory.order(color.White);
Car myCar1 = factory.lastCar;

factory.order(color.Red);
Car myCar2 = factory.lastCar;

factory.order(color.Gray);
Car myCar3 = factory.lastCar;

Assert.Contains(myCar1, factory.wareHouse);
Assert.Contains(myCar2, factory.wareHouse);
Assert.Contains(myCar3, factory.wareHouse);
}
         然后运行该测试,看下结果如何



       可以看到,这个功能是通过测试的,如果我们故意把灰色车辆的入库功能写错,测试会如何呢,将CarFactory中灰色车辆订单的处理改成如下

case color.Gray:
lastCar = new GrayCar();
break;
重新编译功能模块,然后运行测试



        从测试的错误信息中,我们可以看出,入库的只有红色和白色的车辆。

 三、结语

         到这里我们已经掌握了NUnit中的对象识别断言,希望大家能从中体会到NUnit断言的强大及面向对象环境下白盒测试的新的要求和特点。还是那句老话,如果觉得不错,请继续关注本系列课程,如果对课程有不理解的问题,可以发邮件给我28345697@qq.com,谢谢。

白盒测试QQ交流群:




 Rss订阅IQuickTest(关于如何订阅?

GoogleReader订阅地址: http://feeds.feedburner.com/iquicktest
        
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息