转载:http://tech.sina.com.cn/s/2009-07-17/1129988785.shtml

单元测试基础知识

单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。

执行单元测试,是为了证明某段代码的行为确实和开发者所期望的一致。

当编写项目的时刻,如果我们假设底层的代码是正确无误的,那么先是高层代码中使用了底层代码;然后这些高层代码又被更高层的代码所使用,如此往复。当基本的底层代码不再可靠时,那么必需的改动就无法只局限在底层。虽然你可以修正底层的问题,但是这些对底层代码的修改必然会影响到高层代码。于是,一个对底层代码的修正,可能会导致对几乎所有代码的一连串改动,从而使修改越来越多,也越来越复杂。从而使整个项目也以失败告终。

而单元测试的核心内涵:这个简单有效的技术就是为了令代码变得更加完美。

NUnit介绍

NUnit 是一个免费开源的产品,它提供了一套测试框架和一个测试运行程序(test runner)。

注意:test tunner 知道如何寻找具有 [TestFixture] 属性的类和类中的 [Test] 方法。

如何安装 NUnit

在官网下载NUnit,当前最新的版是2.4.8,我下的是NUnit-2.4.8-net-2.0.zip。

NUnit第一个演示

我们用Visual Studio 2008新建一个NUnit项目:

 

为了便于演示,我们把默认的Program.cs改成Calculator.cs,在Calculator类里,我们实现简单的加减乘除四个方法。完整代码如下:

using System;

namespace NUnitTest
{
    public class Calculator
    {
        /// <summary>
        /// 加法
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public int Add(int a,int b)
        {
            return a + b;
        }

        /// <summary>
        /// 减法
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public int Minus(int a, int b)
        {
            return a - b;
        }

        /// <summary>
        /// 乘法
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public int Multiply(int a, int b)
        {
            return a * b;
        }

        /// <summary>
        /// 除法
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public int Divide(int a, int b)
        {
            return a / b;
        }

        static void Main(string[] args)
        {
            Calculator cal = new Calculator();
            int result = cal.Add(2,3);
            Console.WriteLine(result);

            Console.ReadKey(true);
        }
    }
}

         如果没有单元测试,我们普通的测试方法就像是Main方法一样,这样的测试是一个很邪恶的测试方法,花时间且很难得到我们
想要的结果。

那么,我们应该如何来用NUnit做单元测试呢?

我们再新建一个项目:

 

为这个NUnitTestTest引用“NUnitTest项目”和“nunit.framewor类库”。我们再新建一个测试类,命名为“CalculatorTest.cs”。并键入如下代码:

using System;
using NUnit.Framework;
using NUnitTest;

namespace NUnitTestTest
{
    [TestFixture]
    public class CalculatorTest
    {
        [Test]
        public void TestAdd()
        {
            Calculator cal = new Calculator();
            int expected = 5;
            int actual = cal.Add(2, 3);
            Assert.AreEqual(expected, actual);
        }
    }
}

 

这就是一个简单的单元测试方法了。首先我们使用using NUnit.Framework和using NUnitTest,因为接下来的代码需要用到这两个命名空间。在这里,我们要注意几点,NUnit测试用的类前面一定要加上[TestFixture],以表示这是NUnit测试类;测试方法一定是public的,且没有返回值。这里的TestFixture和Test都是NUnit的Attribute,下表给出了NUnit常用的Attribute:
 

 

          Assert.AreEqual是断言,在测试框架中,断言是单元测试的核心,我们在测试中要对其程序断言。如果某个断言失败,方法的调用不会返回值,并且会报告一个错误。如果一个测试包含多个断言,那些紧跟失败断言的那些断言都不会执行,因此每个测试方法最好只有一个断言。 NUnit.Framework.Assert有23个重载方法,大部分的情况它都有考虑到,当然,不排除需要自己写一个复杂的断言方法。

上面的代码中,int expected = 5;是指我们期望程序执行的结果是5,int actual = cal.Add(2, 3);则执行Calculator.Add方法得到实际的值。

顺便说一下,CalculatorTest(类名)还有TestAdd(方法名)并不是一定要这样写,你可以自由的命名你的名称,不过为了让你的代码可读性更好,请遵循一个命名规范,这个规范可以是公司定的也可以是网上主流的命名规则。

对Add()方法的单元测试代码已经完成了,接下来我们运行下载解压后文件夹中的nunit.exe,程序界面如图:

 

 

打开对话"File"/"Open Project..."对话框,或者按"Ctrl + O",把第二个单元测试项目NUnitTestTest生成的NUnitTestTest.dll加载进来:

 

我们点右边的"Run"按钮执行单元测试:

 

 

太棒了,绿色!通过!Keep the bar green to keep the code clean.

一个简单的单元测试过程就是这样的。

我们再为除法写一个单元测试方法:

[Test]

public void TestDivide()

{

Calculator cal = new Calculator();

int expected = 5;

int actual = cal.Divide(25, 5);

Assert.AreEqual(expected, actual);

}

重新生成NUnitTestTest项目,NUnit会自动把TestDivide方法加进去。

 

再点"Run",通过测试。大家都知道除法中除数不能为0,如果这里除数是0呢?会有什么样的结果?

[Test]

public void TestDivide()

{

Calculator cal = new Calculator();

int expected = 5;

int actual = cal.Divide(25, 0);

Assert.AreEqual(expected, actual);

}

生成项目并重新运行单元测试:

 

 

测试没有通过 “NUnitTestTest.CalculatorTest.TestDivide:System.DivideByZeroException : 试图除以零。”这时,我们要返回到Calculator类中修改Divide方法使之除数为0时返回其它的值。

NUnit第一个简单示例就先到这里,在NUnit的官网也有简单教程,大家可以看看。

在单元测试中,我们在做正面的测试的同时也要做一些反面测试,这样才能让我们的代码更健壮。

在Visual Studio 2008 中打开上一章的示例,Calculator类有4个最简单的方法:加、减、乘、除。CalculatorTest类中的四个方法是Calculator类四个方法的单元测试。

[TestFixture]

public class CalculatorTest

...{

[Test]

public void TestAdd()

...{

Calculator cal = new Calculator();

int expected = 5;

int actual = cal.Add(2, 3);

Assert.AreEqual(expected, actual);

}

[Test]

public void TestMinus()

...{

Calculator cal = new Calculator();

int expected = 5;

int actual = cal.Minus(10, 5);

Assert.AreEqual(expected, actual);

}

[Test]

public void TestMultiply()

...{

Calculator cal = new Calculator();

int expected = 5;

int actual = cal.Multiply(1, 5);

Assert.AreEqual(expected, actual);

}

[Test]

public void TestDivide()

...{

Calculator cal = new Calculator();

int expected = 5;

int actual = cal.Divide(25, 5);

Assert.AreEqual(expected, actual);

}

}

这里一定要注意,TestAdd()、TestMinus()、TestMultiply()和TestDivide()方法没有任何关系,也就是说单元测试中,所有的测试方法都是独立的。各个方法之间没有依赖性,删除任何一个单元测试方法,对其它的测试不会有任何影响。

上一章中,我们已经介绍了[TestFixture]和[Test],现在我们为这个类新增一个方法。

[SetUp]

public void InitMethod()

{

Console.WriteLine("Initialization method");

}

重新生成项目,再运行NUnit,选中"CalculatorTest"进行单元测试:

 

切换到NUnit的"Console.Out"中,我们看到"Initialization method"出现了4次,如果只选中一个测试方法:

 

我们看到,这时只出现一次的"Initialization method"。[SetUp]的意思就是指在运行每个测试方法前执行它。相应的,有开始必然有结束,[TearDown]是指在每个测试方法结束后运行。

我们再新增一个方法:

[TearDown]

public void FinalizeMethod()

{

Console.WriteLine("Finalize method");

}

再来看运行NUnit的结果:

 

知道了[SetUp]和[TearDown]后,我们就可以改写这个单元测试类了。

请[TestFixture]

public class CalculatorTest

...{

private Calculator cal;

private int a, b, expected, actual;

[SetUp]

public void InitMethod()

...{

cal = new Calculator();

a = 10;

b = 2;

}

[Test]

public void TestAdd()

...{

expected = 12;

actual = cal.Add(a, b);

Assert.AreEqual(expected, actual);

}

[Test]

public void TestMinus()

...{

expected = 8;

actual = cal.Minus(a, b);

Assert.AreEqual(expected, actual);

}

[Test]

public void TestMultiply()

...{

expected = 20;

actual = cal.Multiply(a, b);

Assert.AreEqual(expected, actual);

}

[Test]

public void TestDivide()

...{

expected = 5;

actual = cal.Divide(a, b);

Assert.AreEqual(expected, actual);

}

}

因为运行每个测试方法之前,都会运行InitMethod()方法,所以每次都会初始化使第一个操作数为10,第二个操作数为2。在[SetUp]中初始化了的资源,我们就可以在[TearDown]里销毁释放。

这里也许有人会问,如果我的项目很大,每个测试方法都需要连接数据库,在每个方法执行的时候进行连接再释放,这样是不是太耗资源太慢了,能不能在一个单元测试类实例化的时候就运行一个指定的方法呢?

这是可以的。在NUnit中,我们使用[TestFixtureSetUp]和[TestFixtureTearDown]就可以实现这样的功能。[TestFixtureSetUp]是指在这个测试类的整个生命周期中,它在所有的测试方法之前运行一次,而[TestFixtureTearDown]是在所有的测试方法都结束时运行。

这里要注意的,[TestFixtureSetUp]与构造函数是不一样的,它标识的方法迟于构造函数运行。我们再对这个测试类进行重构:

[TestFixture]

public class CalculatorTest

...{

private Calculator cal;

private int a, b, expected, actual;

public CalculatorTest()

...{

Console.WriteLine("执行构造函数");

}

[TestFixtureSetUp]

public void InitClass()

...{

Console.WriteLine("执行TestFixtureSetUp");

cal = new Calculator();

a = 10;

b = 2;

}

[TestFixtureTearDown]

public void FinalizeClass()

...{

Console.WriteLine("执行TestFixtureTearDown");

}

[SetUp]

public void InitMethod()

...{

Console.WriteLine("执行SetUp");

}

[TearDown]

public void FinalizeMethod()

...{

Console.WriteLine("执行TearDown");

a = 10;

b = 2;

}

[Test]

public void TestAdd()

...{

Console.WriteLine("TestAdd() Begin");

expected = 12;

actual = cal.Add(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestAdd() End");

}

[Test]

public void TestMinus()

...{

Console.WriteLine("TestMinus() Begin");

expected = 8;

actual = cal.Minus(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestMinus() End");

}

[Test]

public void TestMultiply()

...{

Console.WriteLine("TestMultiply() Begin");

expected = 20;

actual = cal.Multiply(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestMultiply() End");

}

[Test]

public void TestDivide()

...{

Console.WriteLine("TestDivide() Begin");

expected = 5;

actual = cal.Divide(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestDivide() End");

}

}

在NUnit中,我们可以很清楚地看到这个类的执行顺序:

 

假如我们的测试项目中有使用到数据库,就可以把数据库连接写在[TestFixtureSetUp]中,把释放的代码写在[TestFixtureTearDown]中。

我相信现在大家对NUnit的这4个属性都应该有一个直观的认识了吧。都是4个很简单的属性,但是在使用中用处却是非常大的。

接下来再为大家介绍几个常用的属性。

现在的测试中,我们有4个测试方法,但是如果我们想让其中的一个测试方法不在NUnit中显示,怎么办呢?不是注释,大家不要想歪了,注释大家都知道。要想让一个测试方法不在NUnit中显示,也不运行,我们应该使用[Ignore]属性。看看把TestAdd()添加[Ignore]属性后会是什么样子:

[Test]

[Ignore]

public void TestAdd()

{

Console.WriteLine("TestAdd() Begin");

expected = 12;

actual = cal.Add(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestAdd() End");

}

 

现在有了一个新的颜色了——黄色。它是指被忽略的方法。当然,你在项目中出现最多的肯定是绿色。在NUnit中我们可以用[Ignore]的重载方法[Ignore("忽略原因")]来定义忽略原因。

NUnit有一个与[Ignore]类似的属性[Explicit],它是指只有在NUnit中被明确的指定时才运行,否则不运行。有点拗口,我们来看例子。改写TestMinus方法:

[Test,Explicit]

public void TestMinus()

{

Console.WriteLine("TestMinus() Begin");

expected = 8;

actual = cal.Minus(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestMinus() End");

}

这里,

[Test,Explicit]

[Test]

[Explicit]

是完全一样的。

我们看它的截图:

 

"TestMinus"是灰色的,运行的Cases有2个,一个被忽略。而当我们选中TestMinus时:

 

这个测试运行了。

再给大家介绍一个分类属性[Category(string name)],利用这个分类属性,我们可以为每个方法定义类别。

[Test, Ignore("Ignore"), Category("Category A")]

public void TestAdd()

...{

Console.WriteLine("TestAdd() Begin");

expected = 12;

actual = cal.Add(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestAdd() End");

}

[Test, Category("Category B")]

[Explicit]

public void TestMinus()

...{

Console.WriteLine("TestMinus() Begin");

expected = 8;

actual = cal.Minus(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestMinus() End");

}

[Test, Category("Category A")]

public void TestMultiply()

...{

Console.WriteLine("TestMultiply() Begin");

expected = 20;

actual = cal.Multiply(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestMultiply() End");

}

[Test, Category("Category B")]

public void TestDivide()

...{

Console.WriteLine("TestDivide() Begin");

expected = 5;

actual = cal.Divide(a, b);

Assert.AreEqual(expected, actual);

Console.WriteLine("TestDivide() End");

}

重新生成项目,在NUnit中,我们可以看到:

 

这里有我们定义的两个分类,我们选中"Category A",切换回"Tests"点"Run",我们看:

 

只测试了我们设置的"Category A"的一个方法,另一个方法是因为我们设置了[Ignore]所以没有执行测试。

好,到这里,我们已经把NUnit主要的属性学完了,接下来的章节我们将从实例出发学习NUnit。

一步一步学NUnit的更多相关文章

  1. 一步一步学ROP之linux_x64篇

    一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防 ...

  2. 一步一步学ROP之linux_x86篇

    一步一步学ROP之linux_x86篇 作者:蒸米@阿里聚安全 ​ 一.序 ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过 ...

  3. (转载)一步一步学Linq to sql系列文章

    现在Linq to sql的资料还不是很多,本人水平有限,如果有错或者误导请指出,谢谢. 一步一步学Linq to sql(一):预备知识 一步一步学Linq to sql(二):DataContex ...

  4. 一步一步学ZedBoard & Zynq(四):基于AXI Lite 总线的从设备IP设计

    本帖最后由 xinxincaijq 于 2013-1-9 10:27 编辑 一步一步学ZedBoard & Zynq(四):基于AXI Lite 总线的从设备IP设计 转自博客:http:// ...

  5. 一步一步学android控件(之十五) —— DegitalClock & AnalogClock

    原本计划DigitalClock和AnalogClock单独各一篇来写,但是想想,两个控件的作用都一样,就和在一起写一篇了. DegitalClock和AnalogClock控件主要用于显示当前时间信 ...

  6. 一步一步学Remoting系列文章

    转自:http://www.cnblogs.com/lovecherry/archive/2005/05/24/161437.html (原创)一步一步学Remoting之一:从简单开始(原创)一步一 ...

  7. 一步一步学android控件(之十六)—— CheckBox

    根据使用场景不同,有时候使用系统默认的CheckBox样式就可以了,但是有时候就需要自定义CheckBox的样式.今天主要学习如何自定义CheckBox样式.在CheckBox状态改变时有时需要做一些 ...

  8. 一步一步学Python(2) 连接多台主机执行脚本

    最近在客户现场,每日都需要巡检大量主机系统的备库信息.如果一台台执行,时间浪费的就太冤枉了. 参考同事之前写的一个python脚本,配合各主机上写好的shell检查脚本,实现一次操作得到所有巡检结果. ...

  9. 【DG】[三思笔记]一步一步学DataGuard

    [DG][三思笔记]一步一步学DataGuard 它有无数个名字,有人叫它dg,有人叫它数据卫士,有人叫它data guard,在oracle的各项特性中它有着举足轻理的地位,它就是(掌声)..... ...

  10. 一步一步从原理跟我学邮件收取及发送 2.邮箱的登录和绕不开的base64

    一步一步从原理跟我学邮件收取及发送 2.邮箱的登录和绕不开的base64 好了,经过本系列上一篇文章 "1.网络命令的发送",假设大家已经掌握了 email 电子邮件的命令发送的方 ...

随机推荐

  1. 使用Arcglobe 10与3dmax建立三维城市

    转自:http://www.cnblogs.com/jinlun/p/3380307.html 随着国家大力推进数字城市的建设,三维城市的建设也是势在必行的.与传统二维地图相比,三维城市在立体层次.视 ...

  2. powershell学习

    PowerShell 调试器 在开始运行处,输入powershell ISE回车即可 PowerShell 与操作系统版本 powershell在windows server 2008上自带,但最好在 ...

  3. 安装qc 出现error An error occurred while attempting to connect to the database.

    When trying to install mercury quality center starter edition 9.0 on Windows XP media center, I am g ...

  4. EMC

    1.EMC的概念 EMC(Electro Magnetic Compatibility)即电磁兼容,是指设备或系统在其电磁环境中符合要求运行并不对其环境中的任何设备产生无法忍受的电磁干扰的能力.就是它 ...

  5. malloc free 和new delete区别

    从网上看的学习之 1. malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符,与"+“.”-“.”*“.”/“有一样的地位. 2. new/delete是 ...

  6. 基础 HTML之目录问题(相对路径和绝对路径区别)

    一.相对路径和绝对路径 相对路径:以引用文件之网页所在位置为参考基础,而建立出的目录路径.因此,当保存于不同目录的网页引用同一个文件时,所使用的路径将不相同,故称之为相对. 绝对路径:以Web站点根目 ...

  7. mysql日志清理

    mysql bin-log 日志清理 发现mysql数据库目录中bin-log中日志文件非常大 [root@localhost var]# du -sh mysql-bin* | sort 1020K ...

  8. Sitemap Editors for Dynamics CRM 2013

    I’ve started using a couple of different sitemap editors in my projects for CRM 2013. These tools he ...

  9. 第三步 用Jena自定义完成数据库到RDF的映射

    第三步 用Jena自定义完成数据库到RDF的映射 2013年10月17日 8:53:27 这一步用Jena编程,终于能做点有技术含量的事情了.这个工作计划本周内完成,下周一好给老师一个交待. 目标:把 ...

  10. 如何将Android默认的Camra程序导入到eclipse中

    由于工作需要将camera源码导入到Eclipse中,找了很多的方法,现将自己的整理发出来.... 由于开发的要求,需要将Android默认的Camra程序导入到eclipse中,进行修改和再开发. ...