分享

NUNIT

 barbarossia 2006-11-08

 

NUNIT

1.       NUNIT简介

NUnit是一个为Net准备的自动化单元测试框架,它的作用就是帮助你方便的完成单元测试工作,同鼎鼎有名的JUnit一样,都是xUnit家族的成员。它的下载地址是:http://www.

NUnit Framework(NUnit 单元测试框架)简介
.NET
引进了一个新的程序开发的概念 ─ Attributes(属性),解决了这个烦人的问题。Attributes让你可以在你的程序代码之上再加入metadata(元数据,描述程序代码的 资料)。一般来说Attributes不会影响到主要程序代码的执行,其功能是在你所写程序代码之上添加了额外的信息。Attributes主要使用在 documenting your code(注释你的程序代码),但是Attributes也可以用来提供有关Assembly的额外信息,其它的程序就算没有见过这个Assembly 也可以使用这些信息。这基本上就是NUnit 2.1所作的事。在NUnit 2.1里面,有一个Test Runner Application(负责执行Unit Tests的程序),这个Test Runner会扫描你已经compile(编译)好的程序代码,并且从Attribute里面知道哪些classestest classes,哪些methods是需要执行的test methods. 然后,Test Runner使用.NETReflection技术(在.NET Framework中提供了System.Reflection命名空间,这样就使得我们可以方便的获得.NET组件的信息。当你想获得正在使用的组件的 详细信息,或者在运行期间查询一个组件信息的时候,这个功能将变的十分有用)来执行这些test methods。因为这个原因,你就不再需要让你的test classes继承自所谓的common base class。你唯一需要作的事,就是使用正确的Attribute来描述你的test classestest methodsNUnit提供了许多不同的attributes,让你可以自由的写你想要的unit tests。这些attributes可以用来定义test fixtures(见下一段解释)test methods,以及setupteardownmethods(预备及善后工作的methods)。除此之外,还有其它的attributes可以 来设定预期发生的exceptions,或者要求Test Runner跳过某些test method不执行。

 

 TestFixture Attribute简介
  
  TestFixture attribute
主要是用在class上,其作用是标志该class含有需要执行的test methods。当你在一个class的定义里加上这个attributeTest Runner就会检查该class,看看这个class是否含有test methods。底下这段程序代码示范了如何使用TestFixture Attribute
  
  namespace UnitTestingExamples
  {
  
  using System;
   using NUnit.Framework;
  
  [TestFixture]
   public class SomeTests
   {
   }
  }

  使用TextFixture Attributeclass需要符合另一项唯一附加的限制,就是需要有一个publicdefault constructor(或者是没有定义任何的constructor,这其实是相同的意思)

 

TestFixtureSetUp TestFixtureTearDown简介     
  
这两个主 要用在TestFixture里面,其作用是提供一组函数执行任何测试运行之前(TestFixtureSetUP)和最后一个测试执行后 TestFixtureTearDown)。每一个TestFixture只能有一个TestFixtureSetUp方法和 TestFixtureTearDown方法。如果一个以上的TestFixtureSetUpTestFixtureTearDown方法,可以通过 编译但是不会执行。注意一个TestFixture可以拥有一个TestFixtureSetUp和一个SetUp,也可以拥有一个 TestFixtureTearDown和一个TearDown方法。
    TestFixtureSetUp
TestFixtureTearDown 被用在不方便使用SetUpTearDown方法。   
  
一般情况使用 SetUp TearDown attributes

底下这段程序代码示范了如何使用TestFixtureSetUp/TestFixtureTearDown
  
  namespace UnitTestingExamples
  {
  
   using System;
   using NUnit.Framework;
  
  [TestFixture]
  public class SomeTests
  {
  [TestFixtureSetUp]
  public void RunBeforeAllTests() 
  {  
  Console.WriteLine( “TestFixtureSetUp” );   
  }   
  

````[TestFixtureTearDown]  
  public void RunAfterAllTests()   
  {   
  Console.WriteLine( “TestFixtureTearDown” );   
  }   

```  [SetUp]   
  public void RunBeforeEachTest()   
  {   
  Console.WriteLine( “SetUp” );   
  }
  
  [TearDown]   
  public void RunAfterEachTest()   
  {   
  Console.WriteLine( “TearDown” );   
  }
  
  [Test]   
  public void Test1()   
  {  
  Console.WriteLine( “Test1” );   
  }   

 [Test]   
  public void Test2()   
  {  
  Console.WriteLine( “Test2” );   
  }   

  }   
  }      
 
  
程序的输出将是下面的结果::
  
  TestFixtureSetUp
  
  SetUp
  
  Test1
  
  TearDown
  
  SetUp
  
  Test2
  
  TearDown
  
  TestFixtureTearDown
  
  
如果Test2单独执行输出的结果将是:
  
  TestFixtureSetUp
  
  SetUp
  
  Test2
  
  TearDown
  
  TestFixtureTearDown

 

Test Attribute简介
  
  Test attribute
主要用来标示在text fixture中的method,表示这个method需要被Test Runner application所执行。有Test attributemethod必须是public的,并且必须return void,也没有任何传入的参数。如果没有符合这些规定,在Test Runner GUI之中是不会列出这个method的,而且在执行Unit Test的时候也不会执行这个method。上面的程序代码示范了使用这个attribute的方法。

 

 SetUp Teardown Attributes简介
  
  
在写Unit Tests的时候,有时你会需要在执行每一个test method之前(或之后)先作一些预备或善后工作。当然,你可以写一个privatemethod,然后在每一个test method的一开头或最末端呼叫这个特别的method。或者,你可以使用我们要介绍的SetUpTeardown Attributes来达到相同的目的。如同这两个Attributes的名字的意思,有Setup Attributemethod会在该TextFixture中的每一个test method被执行之前先被Test Runner所执行,而有Teardown Attributemethod则会在每一个test method被执行之后被Test Runner所执行。一般来说,Setup AttributeTeardown Attribute被用来预备一些必须的objects(对象),例如database connection、等等。上面的程序代码示范了使用这个attribute的方法。

 

 ExpectedException Attributes简介
  
  
有的时候,你希望你的程序在某些特殊的条件下会产生一些特定的exception。要用Unit Test来测试程序是否如预期的产生exception,你可以用一个try..catch的程序区段来catch(捕捉)这个exception,然后 再设一个boolean的值来证明exception的确发生了。这个方法固然可行,但是太花费功夫。事实上,你应该使用这个 ExpectedException attribute来标示某个method应该产生哪一个exception,如同下面的范例所示:
  
  namespace UnitTestingExamples
  
  {
  
   using System;
  
   using NUnit.Framework;
  
  [TestFixture]
   public class SomeTests
   {
  
   [Test]
  
   [ExpectedException(typeof(InvalidOperationException))]
   public void Test1()
  
   {
  
   // Do something that throws an InvalidOperationException
   }
  
  }
  
  }

 

  如果上面的程序被执行的时候,如果一旦exception发生,而且这个exceptiontype(类型信息) InvalidOperationException 的话,这个test就会顺利通过验证。如果你预期你的程序代码会产生多个exception的话,你也可以一次使用多个 ExpectedException attribute。但是,一个test method应该只测试一件事情,一次测试多个功能是不好的做法,你应该尽量避免之。另外,这个attributes并不会检查inheirtance 关系,也就是说,如果你的程序代码产生的exception是继承自InvalidOperationException subclass(子类化)的话,这个test执行的时候将不会通过验证。简而言之,当你使用这个attribute的时候,你要明确的指明所预期的 exception是哪个type(类型信息)的。

 

 Ignore Attributes简介
  
  
这个attribute你大概不会经常用的,但是一旦需要的时候,这个 attribute是很方便使用的。你可以使用这个attribute来标示某个test method,叫Test Runner在执行的时候,略过这个method不要执行。使用这个Ignore attribute的方法如下:

 

 namespace UnitTestingExamples
  {
   using System;
   using NUnit.Framework;
  
   [TestFixture]
   public class SomeTests
   {
   [Test]
   [Ignore("We‘re skipping this one for now.")]
   public void TestOne()
   {
   // Do something...
   }
   }
  }
  
  
如果你想要暂时性的comment out一个test method的话,你应该考虑使用这个attribute。这个attribute让你保留你的test method,在Test Runner的执行结果里面,也会提醒你这个被略过的test method的存在。

 

 NUnit Assert Class简介
  
  
除了以上所提到的这些用来标示测试程序所在的attributes之外, NUnit还有一个重要的class你应该要知道如何使用。这个class就是Assert classAssert class提供了一系列的static methods,让你可以用来验证主要程序的结果与你所预期的是否一样。Assert class代替了旧的Assertion class,下面是这个类的方法:
  
  Assert.IsTrue( bool );
  
  Assert.IsFalse( bool );
  
  Assert.IsNull( bool );
  
  Assert.IsNotNull( bool );
  
  Assert.AreSame( object, object )
  
  Assert.AreEqual( object, object );
  
  Assert.AreEqual( int, int );
  
  Assert.AreEqual( float, float, float );
  
  Assert.AreEqual( double, double, double );
  
  Assert.Fail();
  
  
使用这个类的示例如下:
  
  namespace UnitTestingExamples
  {
   using System;
   using NUnit.Framework;
  
   [TestFixture]
   public class SomeTests
   {
   [Test]
  
   public void TestEventLengthString()
  
   {
  
   // Should return true
  
   bool bResult1 = Class1.CheckPalindrome("ABCCBA");
  
   Assert.IsTrue(bResult1);
  
   // Should return false
  
   bool bResult2 = Class1.CheckPalindrome("ABCDBA");
  
   Assert.IsFalse(bResult2);
  
   }
  
   [Test]
  
   public void TestOddLengthString()
  
   {
  
   //should return true;
  
   Assert.IsTrue(Class1.CheckPalindrome("ABCDCBA"));
  
   // Should return false
  
   Assert.IsFalse(Class1.CheckPalindrome("ABCDEBA"));
  
   }
  
  }
  }

 

好,现在我们已经讨论过写Unit Tests的基本步骤及方法,现在让我们来看看如何执行你所写的Unit Tests。事实上非常简单。NUnit里面有两个已经写好的Test Runner applications:一个是窗口GUI程序,一个是console XML(命令列)程序。你可以自由选择你所喜欢的方式,基本上是没有什么差别的。
  
如果你要使用窗口GUITest Runner app,你只需要执行该程序,然后告诉它你要执行的test method所在的assembly位置。这个包含有你所写test methodsassembly是那一个class library(或是executable*.dll*.exe) assembly,其中含有前面谈到的Test Fixtures。当你告诉Test Runner你的assembly所在的位置,Test Runner会自动load这个asembly,然后把所有的classtest methods都列在窗口的左栏。当你按下’Run’按键时,你就会自动执行所有列出来的test methods。你也可以double click其中的一个test class,或是一个test method之上,这样会自动只执行该class或是该method
  
底下是窗口GUI Test Runner执行时的样子:
  

2.       基本用法

a.       目前项目中的用法

1     初始化

[SetUp]

         public void InitializeOperands()

         {

              pub=new PUB();

                                   

}

可以对需要初始化才能使用的类,进行初始化。例如,与数据库相关的操作。

 

2    简单测试

[Test]

         public void ReqToRes_Test()

         {

              string reqfile="REQ-CKI-CIQU-123456789-20060317.csv";

              string resfile=null;

              bool returnVal=pub.ReQToRes(reqfile,ref resfile);

              Assert.IsFalse(returnVal);

             

}

 

3   对抛出的异常做测试

[Test]

         [ExpectedException(typeof(Exception))]

         public void Check_bgdh_test()

         {

              string b="125698300";

              ciq.Check_bgdh(b);

             

}

 

b.       未使用的用法

 [TestFixtureSetUp]

[TestFixtureTearDown]

当整个测试框架都需要引用某个类是,可以在 [TestFixtureSetUp]中将它初始化,然后在[TestFixtureTearDown]中将它释放。

 

c.       不建议使用:

[Ignore("We‘re skipping this one for now.")],因为这样可能会忽略这个被测函数。

 

3.       解决方案

a.新生成一个解决方案

b.添加测试项目

c.设置属性

 

d.NUNITGUI环境下调试

通过【运行】按钮,在NUNITGUI下调试

 

 

e.注意:这种方法在调试过程中如果发生错误,必须在原来的项目中修改,然后再将源文件复制到NUNIT项目中再进行测试。否则的话,容易发生混淆。

 

4.       其它的解决方案

a.       在原有的解决方案上新增加一个项目

b.       添加一个测试类

c.       导入所需要的引用

包括测试类:NUNIT.FRAMEWORD和需要被测试的项目

 

d.       添加引用:

e.       接下来就可以测试了。

 

f.这种测试方法比较适用于测试人员。

 

5.       测试用例

a.  条件判断

1:对参数为NULL值进行测试

         [Test]

         public void CheckFileName_Test_null()

         {

              string reqfile=null;

              string asktype=null;

              bool returnVal=pub.CheckFileName(ref reqfile,ref asktype);

              Console.WriteLine(asktype);

              Assert.IsFalse(returnVal);

         }

2:对参数为空字符串进行测试

         [Test]

         public void CheckFileName_Test_empty()

         {

              string reqfile="";

              string asktype="DCL";

              bool returnVal=pub.CheckFileName(ref reqfile,ref asktype);

              Console.WriteLine(asktype);

              Assert.IsFalse(returnVal);

         }

3:对不合法的参数进行测试(报关单号不足9位)

         [Test]

         public void CheckFileName_Test1()

         {

              string reqfile="REQ-CKI-DCL-12345679-20060317.csv";

              string asktype= "DCL";

              bool returnVal=pub.CheckFileName(ref reqfile,ref asktype);

              Console.WriteLine(asktype);

              Assert.IsFalse(returnVal);

//            Assert.IsTrue(returnVal);

         }

4:对不合法的参数进行测试2(日期长度不符合)

[Test]

         public void CheckFileName_Test2()

         {

              string reqfile="REQ-CKI-DCL-123456789-2006031.csv";

              string asktype= "DCL";

              bool returnVal=pub.CheckFileName(ref reqfile,ref asktype);

              Console.WriteLine(asktype);

              Assert.IsFalse(returnVal);

         }

 

b.  数据库操作

         [Test]

         public void getConnectionSQl_test()

         {

              bool returnVal=pub.GetConnectionSQL();

              Assert.IsTrue(returnVal);

         }

对数据库操作的测试,需要对数据库中数据进行修改,才可以对不同的数据进行测试。

 

6.对今后的项目提出的一些建议和改善:

a.       需求分析

首先,将项目需求进行分析,从中搞清楚输入和输出,流程图,错误信息等。最好将它文档化。

 

例:FTP处理主要分为几个功能

1.  显示FTP目录下需要处理的文件名:public string[] getFileList(string mask)

2.  将选中的文件下载到本地:public void download(string remFileName,string path)

输入:需要下载的文件名:string remFileName,路径:string path

输出:在本地生成一个同名的文件

3.  将生成的回执文件上传:public void upload(string fileName)

将上传的回执文件改名:public void renameRemoteFile(string oldFileName,string newFileName)

4.  将请求文件在FTP上删除:public void deleteRemoteFile(string fileName)

 

b.       函数细化

例:将选中的文件下载到本地:public void download(string remFileName,string path)

其中对下载作了3个重载函数,以适应于不同的需要。

a. public void download(string remFileName,string path)

下载到本地路径

b. public void download(string remFileName,string path,string locFileName)

:下载到本地路径,支持改名

c. public void download(string remFileName,//远程文件名

                        string locFileName,//本地文件名

                        Boolean resume)//是否续传

:下载到本地路径,支持改名,支持续传

其中前两个函数是对第三个函数的变种,都是调用第三个函数,只是限制了某些功能。这样可以增加函数的灵活性。

其中DOWNLOAD函数使用了如下几个独立的子函数来完成的

 

1.  LOGIN

if(!logined)

                   {   

                       login();

                           

     }

 

2.  生成SOCKET连接

Socket cSocket = createDataSocket();

 

3.  发送下载命令

sendCommand("RETR " + remFileName);

 

4.  接受数据

while(true) //开始下载

                   {

                       bytes = cSocket.Receive(buffer, buffer.Length, 0);

                       output.Write(buffer,0,bytes);

                       if(bytes <= 0)

                       {

                            break;

                       }

     }//while

 

5.  判断是否下载完成

if (!isDownComplete())

                  {

                       throw new Exception(replay);

         }   

 

c.       异常处理

1.  自定义异常

a. 实现自己的异常类  MyImportantException

1: using System;
2:
3: public class MyImportantException:Exception
4: {
5:  public MyImportantException()
6:   :base() {}
7:
8:  public MyImportantException(string message)
9:   :base(message) {}
10:
11:  public MyImportantException(string message, Exception inner)
12:   :base(message,inner) {}
13: }

2.  异常的分类处理:引用上面一个例子,FTP下载是一个公用的函数,而通过对它的细分,可以将它分为几个较小的独立的私有函数。所以自定义异常就可以在这里发挥作用,每个独立的私有函数都可以抛出各自定义的异常,而在FTP下载这个公有函数中将捕获被抛出的异常,并对它们进行处理或者抛给它的上一级函数。

 

d. 通过对函数的细化,分解出一个个独立的、只完成特定功能的函数。并且通过结构化的异常处理可以使测试简单化。

 

7.  总结

a.如果将UNIT作为测试用途的化,它可以起到自动化的作用,减轻测试人员的工作量。如果发生功能改变或BUG修正,需要修改源代码时,利用自动化测试可以大量减轻测试人员工作量。

建议:提供功能函数的接口给测试人员,这样在不改变函数名的情况下,仅修改函数内部的逻辑。而测试人员仅需要增加或修改测试用例,就可以保持新版本的测试。

b.如果将UNIT作为测试驱动的话,就使得程序员对测试给予足够的重视,从而可以编写较高质量的程序。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多