标签:
开篇:最近在看Roy Osherove的《单元测试的艺术》一书,颇有收获。因此,将其记录下来,并分为四个部分分享成文,与各位Share。本篇作为入门,介绍了单元测试的基础知识,例如:如何使用一个测试框架,基本的自动化测试属性等等,还有对应的三种测试类型。相信你可以对编写单元测试从一无所知到及格水平,这也是原书作者的目标。
1.入门
2.核心技术
4.设计和流程
一个单元测试是一段自动化的代码,这段代码调用被测试的工作单元,之后对这个单元的单个最终结果的某些假设进行检验。
单元测试几乎都是用单元测试框架编写的。单元测试容易编写,能够快速运行。单元测试可靠、可读,并且可维护。
只要产品代码不发生变化,单元测试的结果是稳定的。
集成测试是对一个工作单元进行的测试,这个测试对被测试的工作单元没有完全的控制,并使用该单元的一个或多个真实依赖物,例如时间、网络、数据库、线程或随机数产生器等。
总的来说,集成测试会使用真实依赖物,而单元测试则把被测试单元和其依赖物隔离开,以保证单元测试结果高度稳定,还可以轻易控制和模拟被测试单元行为的任何方面。
如上图所示,TDD和传统开发方式不同,我们首先会编写一个会失败的测试,然后创建产品代码,并确保这个测试通过,接下来就是重构代码或者创建另一个会失败的测试。
NUnit 是从流行的Java单元测试框架JUnit直接移植过来的,之后NUnit在设计和可用性上做了极大地改进,和JUnit有了很大的区别,给日新月异的测试框架生态系统注入了新的活力。
作为一名.NET程序员,如何在VS中安装NUnit并能够在VS中直接运行测试呢?
Step1.在NuGet中找到NUnit并安装
Step2.在NuGet中找到NUnit Test Adapter并安装
LogAn (Log And Notificaition)
场景:公司有很多内部产品,用于在客户场地监控公司的应用程序。所有这些监控产品都会写日志文件,日志文件存放在一个特定的目录中。日志文件的格式是你们公司自己制定的,无法用现有的第三方软件进行解析。你的任务是:实现一个产品,对这些日志文件进行分析,在其中搜索特定的情况和事件,这个产品就是LogAn。找到特定的情况和事件后,这个产品应该通知相关的人员。
在本次的单元测试实践中,我们会一步一步编写测试来验证LogAn的解析、事件识别以及通知功能。首先,我们需要了解使用NUnit来编写单元测试。
(1)我们的测试从以下这个LogAnalyzer类开始,这个类暂时只有一个方法IsValidLogFileName:
public class LogAnalyzer { public bool IsValidLogFileName(string fileName) { if (fileName.EndsWith(".SLF")) { return false; } return true; } }
这个方法检查文件扩展名,据此判断一个文件是不是有效的日志文件。
这里在if中故意去掉了一个!运算符,因此这个方法就包含了一个Bug-当文件名以.SLF结尾时会返回false,而不是返回true。这样,我们就能看到测试失败时在测试运行期中显示什么内容。
(2)新建一个类库项目,命名为Manulife.LogAn.UnitTests(被测试项目项目名为Manulife.LogAn.Lib)。添加一个类,取名为LogAnalyzerTests.cs。
(3)在LogAnalyzerTests类中新增一个测试方法,取名为IsValidFileName_BadExtension_ReturnsFalse()。
首先,我们要明确如何编写测试代码,一般来说,一个单元测试通常包含三个行为:
因此,根据以上三个行为,我们可以编写出以下的测试方法:(其中断言部分使用了NUnit框架提供的Assert类)
[TestFixture] public class LogAnalyzerTests { [Test] public void IsValidFileName_BadExtension_ReturnsFalse() { LogAnalyzer analyzer = new LogAnalyzer(); bool result = analyzer.IsValidLogFileName("filewithbadextension.foo"); Assert.AreEqual(false, result); } }
其中,属性[TestFixture]和[Test]是NUnit的特有属性,NUnit用属性机制来识别和加载测试。这些属性就像一本书里的书签,帮助测试框架识别记载程序集里面的重要部分,以及哪些部分是需要调用的测试。
1.[TestFixture]加载一个类上,标识这个类是一个包含自动化NUnit测试的类;
2.[Test]加在一个方法上,标识这个方法是一个需要调用的自动化测试;
另外,再说一下测试方法名称的规范,一般包含三个部分:[UnitOfWorkName]_[ScenarioUnderTest]_[ExpectedBehavior]
1.UnitOfWorkName 被测试的方法、一组方法或者一组类
2.Scenario 测试进行的假设条件,例如“登入失败”,“无效用户”或“密码正确”等
3.ExpectedBehavior 在测试场景指定的条件下,你对被测试方法行为的预期
(1)编写好测试代码之后,点击"测试"->"运行"->"所有测试"
(2)然后,点击"测试"->"窗口"->"测试窗口管理器",你会看到以下场景
从上图可以看出,我们得测试方法并没有通过,我们期望(Expected)的结果是False,而实际(Actual)的结果却是True。
(1)通常在进行单元测试时我们会考虑到代码覆盖率,点击"测试"->"分析代码覆盖率"->"所有测试",你可以看到以下结果:80%
(2)这时,我们需要想出完善的测试策略来覆盖所有的情况,因此我们添加一些测试方法来提高我们的代码覆盖率。这里我们添加两个方法,一个测试大写文件扩展名,一个测试小写文件扩展名:
[Test] public void IsValidFileName_GoodExtensionLowercase_ReturnsTrue() { LogAnalyzer analyzer = new LogAnalyzer(); bool result = analyzer.IsValidLogFileName("filewithgoodextension.slf"); Assert.AreEqual(true, result); } [Test] public void IsValidFileName_GoodExtensionUppercase_ReturnsTrue() { LogAnalyzer analyzer = new LogAnalyzer(); bool result = analyzer.IsValidLogFileName("filewithgoodextension.SLF"); Assert.AreEqual(true, result); }
这时测试结果如下图所示:
这时再来看看代码覆盖率:100%
(3)为了让所有的测试都能通过,这时我们需要修改源代码,改用大小写不敏感的字符串匹配:
public bool IsValidLogFileName(string fileName) { if (!fileName.EndsWith(".SLF", StringComparison.CurrentCultureIgnoreCase)) { return false; } return true; }
这时,我们再来运行一下所有的测试(也可以选择 运行未通过的测试)来看下由红到绿的快感。单元测试的理念很简单:只有所有的测试都通过,继续前行的绿灯才会亮起。哪怕只有一个测试失败了,进度条上都会亮起红灯,显示你的系统(或者测试)出现了问题。
目前为止,我们的单元测试都还很简单也还比较顺利。但是,如果我们要测试的方法依赖于一个外部资源,如文件系统、数据库、Web服务或者其他难以控制的东西,那又该如何编写测试呢?为了解决这些问题,我们需要创建测试存根、伪对象及模拟对象,下一篇核心技术将会介绍这些内容,让我们跟随Roy Osherove的《单元测试的艺术》一起去探寻吧。
Roy Osherove 著,金迎 译,《单元测试的艺术(第2版)》
标签:
原文地址:http://www.cnblogs.com/edisonchou/p/5437205.html