https://www.cnblogs.com/zwt-blog/p/5788222.html
一、单元测试是什么
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,C#里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。
二、为什么需要单元测试
在我们现在的编程思维中一直都是编码=>编译=>调试,一直循环,直到要处理的功能完成,每一个功能完成都是如此,且有的功能是严重依赖于上一个功能。在如此处理中存在几个问题。
有了单元测试在开发过程中起到的作用。
既然单元测试有这些好处,为什么我们不去用呢。可以归纳为以下几个理由。
答:在开发时越早发现bug,就能节省更多的时间,降低更多的风险。单元测试先期要编写测试用例,是需要多耗费些时间,但是后面的调试、自测,都可以通过单元测试处理,不用手工一遍又一遍处理。实际上总时间被减少了。
答:写单元测试应该成为开发人员的一种本能,开发本身就应该包含单元测试。
结论:
只进行手工测试,只是临时性的单元测试,代码测试覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当bug暴露出来的时候难于调试,大幅度提高后期测试和维护成本。可以说,进行充分的单元测试,是提高软件质量,降低开发成本的必由之路。
要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。
单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。
对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。
三、单元测试工具。
在.Net平台有三种单元测试工具,分别为MS Test、NUnit、Xunit.Net。
1.MS Test为微软产品,集成在Visual Studio 2008+工具中。
2.NUnit为.Net开源测试框架(采用C#开发),广泛用于.Net平台的单元测试和回归测试中,官方网址(www.nunit.org)。
3.XUnit.Net为NUnit的改进版。
(以下主要讲解NUnit的使用,会了NUnit其他2个测试工具也能快速熟悉)。
任何xUnit工具都使用断言进行条件的判断,NUnit自然也不例外,与其它的xUnit(如JUnit、phpUnit、pythonUnit)相比,由于大量使用了Generic、Attribute等语言特征,NUnit提供了更为方面、灵活的测试方法,下面先介绍一下断言。
NUnit一共有五个断言类,分别是Assert、StringAssert、FileAssert、DirectoryAssert、CollectionAssert,它们都在NUnit.Framework命名空间,其中Assert是常用的,而另外四个断言类,顾名思义,分别对应于字符串的断言、文件的断言、目录的断言、集合的断言。理论上,仅Assert类就可以完成所有条件的判断,然而,如果合理的运用后面的四个断言,将使代码更加简洁、美观,也更加便于理解和维护。
四、NUnit的使用。
本处演示所使用的NUnit版本为2.6.4,若要使用最新版可以去官网下载。
首先创建一个类库项目(也可以是其他项目),然后创建一个Test+类库名称的项目(也可以是项目名称+Test),用于代表是测试工程。如下图:
Demonstration项目中含有一个计算功能类,对应的测试项目含有一个测试计算类,一个计算功能类中方法可能需要多个测试用例来完成检测。如下展示出了2个类的代码:
/// <summary> /// 用于演示的一个简单计算功能 /// </summary> public class Calculate { /// <summary> /// 加法 /// </summary> public int Add(int a, int b) { return a + b; } /// <summary> /// 减法 /// </summary> public int Subtract(int a, int b) { return a - b; } /// <summary> /// 乘法 /// </summary> public int Multiply(short a, short b) { return a * b; } /// <summary> /// 除法 /// </summary> public int Quotient(int a, int b) { return a / b; } /// <summary> /// 开平方根 /// </summary> public double SquareRoot(int num) { return Math.Sqrt(num); } /// <summary> /// 四舍五入,取整 /// </summary> public int Round_Off(double num) { return (int)Math.Round(num); } /// <summary> /// 向上取整 /// </summary> public int UpwardTrunc(double num) { return (int)Math.Ceiling(num); } /// <summary> /// 平方 /// </summary> public int Square(short num) { throw new NotImplementedException(); } } [TestFixture(Description = "测试示例")] public class TestCalculate { private Calculate calculate; private StreamReader reader; private string[] sourceData = new string[] { @"..\..\..\Resource\score_1.csv" }; private short a, b; [TestFixtureSetUp] public void Initialize() { Console.WriteLine("初始化信息"); calculate = new Calculate(); } [TestFixtureTearDown] public void Dispose() { Console.WriteLine("释放资源"); if (reader != null) { reader.Close(); } } [SetUp] public void SetUp() { a = 3; b = 2; } [TearDown] public void TearDown() { Console.WriteLine("我是清理者"); } [Test(Description = "加法")] [Category("优先级 1")] public void TestAdd() { Assert.AreEqual(5, calculate.Add(a, b)); } [Category("优先级 1")] [TestCase(1, 2), TestCase(2, 3)] public void TestSubtract(int a, int b) { Assert.AreEqual(a - b, calculate.Subtract(a, b)); } [Category("优先级 2")] [TestCase(1, 2, Result = 2), TestCase(2, 3, Result = 6)] public int TestMultiply(short a, short b) { return calculate.Multiply(a, b); } [Test] [Category("优先级 2")] [ExpectedException(typeof(DivideByZeroException))] public void TestQuotient() { calculate.Quotient(a, 0); } [Test] [Category("优先级 3")] public void TestSquareRoot() { Assert.Less(1, calculate.SquareRoot(a)); } [Test] [Category("优先级 3")] [Sequential] public void TestRound_Off([Values(3.4, 4.5, 4.6, 5.5)] double num, [Values(3, 5, 5, 6)] int result) { Assert.AreEqual(result, calculate.Round_Off(num)); } [Test] [Category("优先级 3")] public void TestUpwardTrunc([ValueSource("sourceData")] object fileName) { reader = new StreamReader((string)fileName); string content; while ((content = reader.ReadLine()) != null) { var nums = content.Split(‘,‘).Select(c => double.Parse(c)).ToArray(); Array.ForEach(nums, (num) => { int result = calculate.UpwardTrunc(num); Console.Write(result + "\n"); }); } } [Test] public void TestSquare() { Assert.Throws<NotImplementedException>(() => calculate.Square(b)); } [Test, Explicit] [Ignore] public void TestFactorial() { Assert.Fail("未能实现阶乘功能"); } }
在粗略看了代码后,下面就详细说明相应的测试标记(属性)的用法。
更多属性标记与详细说明,可以查阅NUnit官网提供的说明文档。一个方法的测试可能要写很多个测试用例,这都是正常的,如果一个测试用例包含多个断言,那些紧跟失败断言的断言都不会执行,因为通常每个测试方法最好只有一个断言。
在运行单元测试时有3种方式分别为:
以上的图片展示了运行错误界面和运行输出界面。在测试用例的节点中绿色‘√‘代表通过,黄色‘√‘代表忽略,红色‘ב代表失败。
五、Nunit常用类和方法
1、Assert(断言):如果断言失败,方法将没有返回,并且报告一个错误。
1)、测试二个参数是否相等
Assert.AreEqual;
Assert.AreEqual;
2)、测试二个参数是否引用同一个对象
Assert.AreSame;
Assert.AreNotSame;
3)、测试一个对象是否被一个数组或列表所包含
Assert.Contains;
4)、测试一个对象是否大于另一个对象
Assert.Greater;
5)、测试一个对象是否小于另一个对象
Assert.Less;
6)、类型断言:
Assert.IsInstanceOfType;
Assert.IsAssignableFrom;
7)、条件测试:
Assert.IsTrue;
Assert.IsFalse;
Assert.IsNull;
Assert.IsNotNull;
Assert.IsNaN;用来判断指定的值是否为数字。
Assert.IsEmpty;
Assert.IsNotEmpty;
Assert.IsEmpty;
Assert.IsNotEmpty;
8)、其他断言:
Assert.Fail;方法为你提供了创建一个失败测试的能力,这个失败是基于其他方法没有封装的测试。对于开发你自己的特定项目的断言,它也很有用。
Assert.Pass;强行让测试通过
2、字符串断言(StringAssert):提供了许多检验字符串值的有用的方法
StringAssert.Contains;
StringAssert.StartsWith;
StringAssert.EndsWith;
StringAssert.AreEqualIgnoringCase;
3、CollectionAssert类
CollectionAssert.AllItemsAreInstancesOfType;集合中的各项是否是某某类型的实例
CollectionAssert.AllItemsAreNotNull:集合中的各项均不为空
CollectionAssert.AllItemsAreUnique;集合中的各项唯一
CollectionAssert.AreEqual;两个集合相等
CollectionAssert.AreEquivalent;两个集合相当
CollectionAssert.AreNotEqual;两个集合不相等
CollectionAssert.AreNotEquivalent;两个集合不相当
CollectionAssert.Contains;
CollectionAssert.DoesNotContain;集合中不包含某对象
CollectionAssert.IsSubsetOf:一个集合是另外一个集合的子集
CollectionAssert.IsNotSubsetOf:一个集合不是另外一个集合的子集
CollectionAssert.IsEmpty;集合为空
CollectionAssert.IsNotEmpty;集合不为空
CollectionAssert.IsOrdered;集合的各项已经排序
4、FileAssert
FileAssert.AreEqual;
FileAssert.AreNotEqual;
5、DirectoryAssert
DirectoryAssert.AreEqual;
DirectoryAssert.AreNotEqual;
DirectoryAssert.IsEmpty;
DirectoryAssert.IsNotEmpty;
DirectoryAssert.IsWithin;
DirectoryAssert.IsNotWithin;
六、NUnit集成到VS中的使用。
在使用NUnit-GUI处理运行测试用例,是不是感觉比较麻烦,还要使用外部的NUnit应用程序,有没有简单点的最好能够跟VS开发工具紧密结合的方式来进行NUnit单元测试呢?答案是肯定的,有2种方式。
1.我们在VS中选择工具菜单栏下的扩展和更新,选择联机并在搜索框中输入NUnit。出现如下图的信息,有2个版本的Nunit适配器,分别为NUnit 3.x(最新版为3.4.1)和NUnit 2.x(最新版为2.6.4),都支持Visual Studio 2012+。若想在VS2010中集成,需要安装NUnit 2.6.4安装包(可在官网下载)与VS2010 NUnit整合插件(下载地址:
http://visualstudiogallery.msdn.microsoft.com/c8164c71-0836-4471-80ce-633383031099),下载安装完毕就能在 VS2010 的视图=>其他窗口中看到 Visual Nunit(或使用快捷键Ctrl + F7),打开该视图,将之拖到合适的位置。
下载安装NUnit Test Adapter后关闭VS,重启一下就好了,我们打开类库项目中的TestCalculate类,在右键弹出的菜单中点击运行测试。运行结束后,会在左侧的测试资源管理器当中显示本次操作的结果。
2.通过ReSharper工具处理NUnit的单元测试,在VS2010+中安装了ReSharper开发插件,ReSharper内中自带支持NUnit与MS Test这2个单元测试工具,只要你的测试工程中引用了相应的单元测试类库(如nunit.Framework.dll)、以及含有测试用例。通过鼠标右键或快捷键(Ctrl + T,R),就可以运行单元测试,也可以进行单元测试调试,ReSharper选项图与运行效果如下图。
七、后续
上面列出只能单元测试的基本使用,未能说明对Mock等其他功能的使用,也没有解释对难以单元测试的代码进行重新设计的说明,需要后期深入了解才能列出相应的文档说明。能够更好的使用单元测试才能更好的使用TDD(测试驱动开发)来开展项目,TDD测试驱动开发是测试先行(此测试是单元测试)、是极限编程的一个重要特点,它以不断的测试推动代码的开发,既简化了代码,同时也保证了软件指令,另一方面说编写的测试用例将成为重要文档(可以作为SDK提供给开发者,测试即文档)。
-----------------以上内容是根据博客园其他博客的说明与Nunit官方文档,以及自己测试使用,进行了整理说明。----------------------------
原文:https://www.cnblogs.com/kelelipeng/p/10550848.html