https://www.cnblogs.com/zwt-blog/p/5788222.html
单元测试 2 https://www.cnblogs.com/zwt-blog/p/5881415.html https://www.cnblogs.com/zwt-blog/p/5881415.html
一, 单元测试是什么
单元测试(unit testing), 是指对软件中的最小可测试单元进行检查和验证. 对于单元测试中单元的含义, 一般来说, 要根据实际情况去判定其具体含义, 如 C 语言中单元指一个函数, C# 里单元指一个类, 图形化的软件中可以指一个窗口或一个菜单等. 总的来说, 单元就是人为规定的最小的被测功能模块. 单元测试是在软件开发过程中要进行的最低级别的测试活动, 软件的独立单元将在与程序的其他部分相隔离的情况下进行测试.
单元测试 (模块测试) 是开发者编写的一小段代码, 用于检验被测代码的一个很小的, 很明确的功能是否正确. 通常而言, 一个单元测试是用于判断某个特定条件 (或者场景) 下某个特定函数的行为.
二, 为什么需要单元测试
在我们现在的编程思维中一直都是编码 =>编译 =>调试, 一直循环, 直到要处理的功能完成, 每一个功能完成都是如此, 且有的功能是严重依赖于上一个功能. 在如此处理中存在几个问题.
编译通过后, 运行程序出现的 bug 难以定位.
修改一个 bug, 容易引进其他 bug.
Bug 越到后期发现, 越难以修改.
后期系统的复杂性, 导致代码难以修改和重构, 使得系统难以维护.
开发人员常认为编译功过, 进行了几次手工测试就等于测试通过(认为详细的测试是测试人员的工作, 非开发人员的工作).
在完全依赖外部系统的情况下, 难以进行有效的测试.
手工测试效率低下, 针对性不强, 测试不能重用.
有了单元测试在开发过程中起到的作用.
大大节约了测试和修改的时间, 有效且便于测试各种情况.
能快速定位 bug(每一个测试用例都是具有针对性).
能使开发人员重新审视需求和功能的设计(难以单元测试的代码, 就需要重新设计).
强迫开发者以调用者而不是实现者的角度来设计代码, 利于代码之间的解耦.
自动化的单元测试能保证回归测试的有效执行.
使代码可以放心修改和重构.
测试用例, 可作为开发文档使用(测试即文档).
测试用例永久保存, 支持随时测试.
既然单元测试有这些好处, 为什么我们不去用呢. 可以归纳为以下几个理由.
对单元测试存在的误解, 如: 单元测试属于测试工作, 应该由测试人员来完成, 所以单元测试不属于开发人员的职责范围. 答: 虽然单元测试虽然叫做 "测试", 但实际属于开发范畴, 应该由开发人员来做, 而开发人员也能从中受益.
没有真正意识到单元测试的收益, 认为写单元测试太费时, 不值得.
答: 在开发时越早发现 bug, 就能节省更多的时间, 降低更多的风险. 单元测试先期要编写测试用例, 是需要多耗费些时间, 但是后面的调试, 自测, 都可以通过单元测试处理, 不用手工一遍又一遍处理. 实际上总时间被减少了.
项目经理或技术主管没有要求写单元测试, 所以不用写.
答: 写单元测试应该成为开发人员的一种本能, 开发本身就应该包含单元测试.
不知道有单元测试这回事, 不知道如何用. 经过这篇文档的说明, 就基本知道如何处理单元测试.
结论:
只进行手工测试, 只是临时性的单元测试, 代码测试覆盖率要超过 70% 都很困难, 未覆盖的代码可能遗留大量的细小的错误, 这些错误还会互相影响, 当 bug 暴露出来的时候难于调试, 大幅度提高后期测试和维护成本. 可以说, 进行充分的单元测试, 是提高软件质量, 降低开发成本的必由之路.
要进行充分的单元测试, 应专门编写测试代码, 并与产品代码隔离. 比较简单的办法是为产品工程建立对应的测试工程, 为每个类建立对应的测试类, 为每个函数 (很简单的除外) 建立测试函数.
单元测试是由程序员自己来完成, 最终受益的也是程序员自己. 可以这么说, 程序员有责任编写功能代码, 同时也就有责任为自己的代码编写单元测试. 执行单元测试, 就是为了证明这段代码的行为和我们期望的一致.
对于程序员来说, 如果养成了对自己写的代码进行单元测试的习惯, 不但可以写出高质量的代码, 而且还能提高编程水平.
三, 单元测试工具.
在. Net 平台有三种单元测试工具, 分别为 MS Test,NUnit,Xunit.NET.
1.MS Test 为微软产品, 集成在 Visual Studio 2008 + 工具中.
2.NUnit 为. Net 开源测试框架(采用 C# 开发), 广泛用于. Net 平台的单元测试和回归测试中, 官方网址( http://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("未能实现阶乘功能");
- }
- }
在粗略看了代码后, 下面就详细说明相应的测试标记 (属性) 的用法.
[TestFixture(arguments)]属性标记类为测试类, 若没有填写参数, 则测试类必须含有无参构造函数, 否则需要相应的有参构造函数. 也可以多个测试[TestFixture(1), TestFixture("a")]
[Test]属性标记方法为测试方法, 中添加 Description 参数可以给我们测试的功能添加描述信息.
[TestCase(arguments)]属性标记有参数无返值方法为测试方法(泛型方法一样标记), 想要多次测试可用逗号隔开([TestCase(1,2), TestCase(2,3)]).
[TestCase(arguments,Result = value)属性标记带参数与返回值的方法为测试方法, 执行的时候把预期的返回值也告诉 NUnit, 如果返回值不对, 测试同样无法通过.
[Suite](测试套件, 仅对属性与索引器标记有效): 可以将多个测试类组合到一起, 同时执行多个测试. 本版本的开发人员的一个信念就是减少这个的需要, 可以使用 [Category] 来替代它.
[Explicit]属性标记测试方法需要在 UI 界面显式执行, 如果不想对某个方法进行单元测试, 只是在它被选中时才进行测试的话, 可以调用该特性.
[Ignore]属性标记一个测试方法或一个测试类被忽略, 如果测试类被忽略, 其内中的测试方法也会被忽略.
[ExpectedException(Type)]属性标记测试方法在运行时抛出一个期望的异常, 如果是则测试通过, 否则不通过.
[Category("")]属性标记用于将测试分类(便于只测试需要的类别), 可在方法与类上进行标记, 在 NUnit-GUI 界面的 Categories 选项卡中对要测试种类进行添加, Run 时仅测试该类别的测试.
[TestFixtureSetUp]属性标记方法为类级别设置 (初始化) 方法, 在整个测试类中执行一次初始化, 所有的测试方法共享初始化数据.
[TestFixtureTearDown]属性标记方法为类级别拆卸方法, 在整个测试类中执行一次拆卸. 当测试类中的所有测试方法执行完成, 就会执行拆卸方法, 用于清除数据, 释放资源.
[TearDown]属性标记方法为函数级别的拆卸方法, 在执行完每个测试方法后, 执行该拆卸方法. 一个测试类可以仅有一个 TearDown/Setup/TestFixtureSetUp/TestFixtureTearDown 方法. 如果有多个定义, 测试类也会编译成功, 但是测试时不会运行这些标记过的方法.
[SetUp]属性标记方法为函数级别的设置方法, 在执行每个测试方法前, 执行该设置方法.
每执行一次 Run, 就是 new 一个新的实例在测试.
[Maxtime]/[Timeout]属性标记测试用例的最大执行时间, 前者超时时不取消测试, 而后者会强行中断, 用法如:[Test, Maxtime(2000)],[Test, Timeout(2000)].
[Repeat]属性标记测试方法重复执行多少次, 如:[Test, Repeat(100)].
[RequiresMTA]/[RequiresSTA]/[RequiresThread]属性标记测试用例必须的在多线程, 单线程, 独立的线程状态下运行.
[Values]属性标记测试用例的参数, 以参数的形式传入一组值, NUnit 会把这组值分解成相应数量的子测试. 当测试用例的 2 个参数都使用 [Values] 进行标记, NUnit 默认生成 2 组数量乘积的用例, 需要使用 [Sequential] 标记测试用例才能按顺序生成一一对应的 n(n=2 组中最大数组长度)个子测试用例.
[ValueSource]属性标记测试用例的参数, 指定参数的数据源来自哪里, 在使用 [ValueSource] 指定数据源时, 该数据源必须实现了 IEnumerable 接口, 数据源可以是属性, 无参方法, 实例或静态成员.
更多属性标记与详细说明, 可以查阅 NUnit 官网提供的说明文档. 一个方法的测试可能要写很多个测试用例, 这都是正常的, 如果一个测试用例包含多个断言, 那些紧跟失败断言的断言都不会执行, 因为通常每个测试方法最好只有一个断言.
在运行单元测试时有 3 种方式分别为:
把测试工程的属性 =>调试 =>启动外部程序, 设置为 NUnit 运行程序. 在启用调试时会启动 NUnit 界面程序, 但 NUnit 界面没有测试用例的信息, 需要自己添加在 File=>Open Project->文件资源管理器, 找你的测试工程类库或程序添加即可. 点击 Run 运行, 根据选中的节点运行该节点下所有的子测试用例(该测试可进行调试). 如下图:
以上的图片展示了运行错误界面和运行输出界面. 在测试用例的节点中绿色'√'代表通过, 黄色'√'代表忽略, 红色'*'代表失败.
直接启动 NUnit 界面程序, 在 File=>Open Project->文件资源管理器, 添加测试工程类库或程序, 点击相应的节点进行 Run 测试, NUnit 会根据类库或程序生成更新, 自动更新界面中测试用例节点, 但运行的测试用例不能进行调试. 效果图与1中的效果一样.
在 Visual Studio 2010 + 的 IDE 中以插件的方式集成 NUnit 测试工具, 直接在测试工程中点击鼠标右键, 运行测试即可. 或者在 VS 菜单栏的测试中运行 NUnit 测试. 集成与运行效果图在 "第五节" 中展示.
五, 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 官方文档, 以及自己测试使用, 进行了整理说明.----------------------------
来源: http://www.bubuko.com/infodetail-2991213.html