转载: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. word 中Sentences、Paragraph等含义和用法

    word 中有Words,Characters,Sentences.Paragraph,Sections 具体含义如下表达式             含义   返回的对象 Words(index)  ...

  2. iphone4s丢失如何找回

    iphone4s丢失如何找回 iphone4s手机丢了怎么办,其实苹果手机自带找回功能,但是前提你得打开了icloud这款软件. 方法/步骤 1 在手机的设置里找到icloud设置,如图. 2 点击进 ...

  3. 基于WebForm+EasyUI的业务管理系统形成之旅 -- 构建Web界面(Ⅴ)

    上篇<基于WebForm+EasyUI的业务管理系统形成之旅 -- 数据统计>,主要介绍系统数据统计所采用图形.报表工具. 本篇将如何构建Web界面以及新增.编辑.导出数据等功能. 一.在 ...

  4. SSL双向认证(高清版)

    介绍了SSL双向认证的一些基本问题,以及使用Nginx+PHP基于它搭建https的Webservice. 之前的方式只是实现1:1的模式,昨天同事继续实现了n:1的模式,这里我再整理记录下. 由于n ...

  5. xcodebuild导出ipa方法

    xcode 5.x版本导出ipa是不需要开发者账号,而xcode6以后导出ipa必须要求选择开发者team,无法绕开,但我们使用xcodebuild命令行可以无视这个限制 环境: mac osx 10 ...

  6. makefile 中 $@ $^ %< 使用

    这篇文章介绍在LINUX下进行C语言编程所需要的基础知识.在这篇文章当中,我们将会学到以下内容: 源程序编译 Makefile的编写 程序库的链接 程序的调试 头文件和系统求助 1.源程序的编译 在L ...

  7. Storm系列(十一)架构分析之Supervisor-管理Worker进程的事件线程

    处理流程:   方法原型: (defn sync-processes [supervisor]) 函数说明: Supervisor是一个supervisor-data对象. 从local-state中 ...

  8. POJ3080 - Blue Jeans(KMP+二分)

    题目大意 求N个字符串的最长公共字串 题解 和POJ1226做法一样...注意是字典序最小的...WA了一次 代码: #include <iostream> #include <cs ...

  9. MySQL开发中常用的查询语句总结

    1.查询数值型数据: SELECT * FROM tb_name WHERE sum > 100; 查询谓词:>,=,<,<>,!=,!>,!<,=>, ...

  10. JBPM数据库表说明

    http://blog.163.com/tigerlion@126/blog/static/167675665201072642627400/ 最近这几天一直在研究JBPM工作流引擎,以下为JBMP的 ...