zoukankan      html  css  js  c++  java
  • 一步一步学NUnit(1)

    http://www.cnblogs.com/moozi/archive/2009/03/17/nunit-step-by-step-1.html

    单元测试基础知识

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

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

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

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

    NUnit介绍

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

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

    如何安装 NUnit

    在官网http://www.nunit.org/index.php?p=download下载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的,且没有返回值。这里的TestFixtureTest都是NUnit的Attribute,下表给出了NUnit常用的Attribute

    usage

    NUnit attributes

    标识测试类

    TestFixture

    标识测试用例(TestCase

    Test

    标识测试类初始化函数

    TestFixtureSetup

    标识测试类资源释放函数

    TestFixtureTearDown

    标识测试用例初始化函数

    Setup

    标识测试用例资源释放函数

    TearDown

    标识测试用例说明

    N/A

    标识忽略该测试用例

    Ignore

    标识该用例所期望抛出的异常

    ExpectedException

    标识测试用例是否需要显式执行

    Explicit

    标识测试用例的分类

    Category

    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的官网也有简单教程,大家可以看看。http://www.nunit.org/index.php?p=quickStart&r=2.4.8

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

    本章示例代码下载

    前一章我们简单介绍了NUnit的入门示例一步一步学NUnit(1),让大家对NUnit有个简单的认识。

    NUnit的使用是非常简单的,但是它在项目中使用时,有许多最佳实践。这章我们把上一章没有讲到的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。

    本文原载:http://www.moozi.net/archives/2009/03/21/nunit-step-by-step-2.aspx

    本章示例代码下载

    注:本来早就想写这章的,可是因为学校课程设计答辩还有毕设开题一直很忙,今天下午刚回到福州,趁着晚上有空就贴出来了。写的不好的地方请大家谅解,这些文章也是我的学习笔记,如果有不对的地方,请大家指教。谢谢!

    前面一篇文章在博客园里转贴出来得到了一些朋友的支持,这里表示感谢。我现在也在学单元测试,所以会把学习笔记一直继续下去,希望在学习的过程中能得到更多朋友的帮助,谢谢!

  • 相关阅读:
    X
    W
    J
    A
    Q
    P
    B
    排列和组合的求解
    深度学习之序列处理
    32位和64位数据类型大小对比
  • 原文地址:https://www.cnblogs.com/fx2008/p/2282738.html
Copyright © 2011-2022 走看看