`

转 单元测试之道(使用NUnit)(一)

阅读更多

首先来看下面几个场景你是否熟悉

  1、你正在开发一个系统,你不断地编码-编译-调试-编码-编译-调试……终于,你负责的功能模块从上到下全部完成且编译通过!你长出一口气,怀着激动而又忐忑的心情点击界面上的按钮,顿时你刚刚的轻松感烟消云散:系统无法正常工作,你想读的数据显示不出来,你想存的东西也送不到数据库……于是,你再次回到IDE里,设断点、调试、一层一层跟踪,当你精疲力尽终于将数据送到数据库里,你又发现了其它问题,于是你继续设断点、调试、编译、调试……

  2、你狂躁地敲击着键盘和鼠标,咒骂着不断出现的bug:啊?这里怎么没返回值啊!哎?这里不该是0啊!不对啊,这里怎么没数据……你永远不知道还有多少 bug,你也永远不知道你的改动会不会引入其它bug——这里有几十个甚至上百个类,几百几千个方法!我不能都照顾到啊!你感觉bugs像敲击鼹鼠游戏中的鼹鼠:打下了这个,另一个又从其它洞口露出头来……

  3、也许是毕业答辩的演示,也许是客户的审查,你小心地打开自己要演示的系统,进行着预定的操作,忽然,有个功能不能正常运行,你大汗淋漓,在答辩老师或者客户质疑且不满的目光下你试了又试,但还是于事无补……于是,答辩老师可能扭头便走,客户可能愤然离去,然后离去的还有你的学位证和项目奖金。当后来你检查代码时,发现这一切竟然只是因为一个底层工具类中一个方法输出结果为空。

  如果你觉得上面的场景令你似曾相识甚至痛心疾首,那么你应该看完这篇文章

  什么是单元测试

  单元测试(Unit Test)的一个测试用例(Test Case)是一小段代码,这段代码用于测试一个小的程序功能(一般是一个方法或相关的几个方法)行为是否正常。下面给出一个实际项目中单元测试用例的代码,大家可以不用深究这段代码中的细节,这里贴这段代码只是给大家一个直观的感觉。

 

  1 ///<summary>
  2 ///测试基本的添加及删除角色是否正确
  3 ///</summary>
  4 [Test]
  5 publicvoidTestAddAndRemoveRole()
  6 {
  7          IRoleServices roleServ=UnityHelper.CreateContainer().Resolve<IRoleServices>();
  8          IRoleRepository roleRep=UnityHelper.CreateContainer().Resolve<IRoleRepository>();
  9          Assert.IsNotNull(roleServ);
10          Assert.IsNotNull(roleRep);
11 
12          String timeStamp=DateTime.Now.ToString();
13          RoleDto newRole=newRoleDto()
14          {
15                  Name="测试角色"+timeStamp,
16                  Desciption="此角色仅供测试使用",
17          };
18          roleServ.AddRole(newRole);
19
20          RoleDto addedRole=roleRep.GetRoleByName("测试角色"+timeStamp);
21          Assert.AreNotEqual(-1, addedRole.ID);//确认新角色添加成功
22 
23          roleServ.RemoveRole(addedRole.ID);
24          Assert.AreEqual(-1, roleRep.GetRoleByName("测试角色"+timeStamp).ID);//确认刚才添加的角色删除成功
25  }

  上面的Unit Test Case来自我目前负责的一个项目,这段代码的作用是测试Services层对角色的添加和删除是否正常工作。这里大家可以先不必太细究代码,后面会有详解。

  为什么大家不使用单元测试

  按照惯例,说完什么是单元测试,就该说为什么要使用单元测试了。但是,我在这里想先和大家讨论,为什么很多开发人员知道单元测试,也“认为”单元测试有必要,但绝大多数开发人员都不写单元测试,能认真对待单元测试的开发人员更是寥寥无几了。

  我私下调查了一些开发人员,发现大家不写单元测试主要有两点原因:一是对单元测试存在很多误解,二是没有真正意识到单元测试的收益。下面我就这两点做一些讨论。

  首先,我们来看看大家对单元测试普遍存在哪些误解。

  误解1:单元测试属于测试工作,应该由测试人员来完成,所以单元测试不属于开发人员的职责范围。

  正解1:单元测试虽然叫做“测试”,但实际属于开发范畴,应该由开发人员来做。

  在大多数开发人员眼里,“开发”和“测试”是两个泾渭分明的范畴,他们认为:开发人员的工作就是写新代码,实现新功能,至于代码的测试,那是测试人员的职责,我只要让代码编译通过就行了。

  我们都知道,软件是很复杂很抽象的东西,软件开发人员压力都很大,况且人非圣贤,强求开发人员开发出没有缺陷的程序是不现实的,所以才有了“测试工程师”这一职位。但是,开发人员至少应该保证一点:你写的每一个函数或方法(Function)应该能够正常完成功能,即行为正常。软件最终可能会有缺陷,这不是开发人员完全可以控制的,但你写了一个类,类里有4个方法,作为开发人员应该保证这四个方法实现了“眼下”的功能。例如,你写了一个获取IoC容器的工具类,你总要保证其中的GetContainer方法能正确返回一个Container吧。

  所以,单元测试虽然叫“测试”,但实际其属于开发范畴,其目的是保证开发的功能子项能完成正确实现其基本功能。甚至我个人认为,当开发人员开发每一个功能子项(通常是方法)时,如果不能附带一配套的单元测试代码,都不能算开发完成。换言之,单元测试代码应该是开发人员必须提供的要素。

 

  误解2:单元测试是一种测试,其功能是对代码进行检测。

  正解2:单元测试是一种工具,其功能除了是对代码进行检测,更重要的是对软件的质量起到一种保证,并且是为他人和后续编码、重构工作提供的一种十分美妙的工具!

  单元测试不是一种测试。没错,我不是在说疯话,单元测试其实是一种工具。特别是当自动化测试软件(如NUnit、JUnit)出现后,单元测试更像是一种工具了。

  当你处在一个多人开发团队中,你需要和其他队友配合开发,而这在程序层面则表现为你开发的Class会被别人用,而你也会用别人开发的Class。我们每个人都希望别人交给我的Class是行为正确的,如果我拿到一个同事写的数据库操作类DBHelper,但发现其中的Connect方法根本无法连接上数据库(虽然没有编译错误),那我将非常郁闷。所以,在交给别人一个Class之前,你应该使用Unit Test保证这个Class是正常实现功能的,在交付的时候,你应该一手递上刚出炉的Class,然后另一只手递上配套的Unit Test,然后说:嘿!哥们,这是你要的类,而这个是配套的单元测试,你可以随时使用自动化测试工具运行它以便迅速知道这个类是否工作正常。

  这将会是个很棒的工具,你的队友以后可能会想知道它的改动是否影响了你提供的类的功能,也可能会对你的类进行重构,但无论何时,它只要拿出你的配套单元测试,让自动化测试工具跑一下,不出几秒,就知道你提供的类是否还正常完成功能。即使是对于你自己,以后也会有很多机会用到它。而当你写的代码出现bug,你可以拿出你这段代码调用的所有类的单元测试跑一遍,很快就能知道到底是你依赖的类出了问题还是你自己代码有问题,而不必抓狂似地到处设断点。

  误解3:项目经理或技术主管没有要求写单元测试,所以不用写。

  正解3:写单元测试应该成为开发人员的一种本能,开发本身就应该包含些单元测试。

  就像项目经理不用告诉你要使用计算机写程序一样,写单元测试应该成为开发人员的必须动作。因为你是开发人员,因为你在做开发,所以你必须写单元测试,就这么简单。

  误解4:写单元测试获益者是测试人员,而开发人员无法从中获益,还要搭上宝贵的时间。

  正解4:写单元测试谁都获得不了像开发人员获得的那么大的益处。

  有了单元测试,你可以随时从同事手中接过值得信赖的代码;有了单元测试,你可以随时保证你写的代码行为正确;有了单元测试,你可以随时通过自动化操作得知某个Class行为是否正确;有了单元测试,你以后的Debug和重构工作将变得轻松异常;有了单元测试,……没有人比开发人员从中获得的利益更大了。

  为什么开发人员很难意识到单元测试的收益

  关于这点,我认为有两个重要原因。

  第一、绝大多数开发人员没有尝试过贯彻单元测试。

  这个很好理解,如果你不亲口尝尝一道菜,即使是海参鲍鱼,你也不知道它有多美味。我曾经也是其中的一员,但当我第一次将单元测试贯彻于项目中并尝到甜头后,我就爱上“她”了,所以,迈出一地步,很关键。

  第二、人有一种天性:相比长远的更大利益,人们更倾向于眼前的小的多的利益,正所谓“贪小便宜吃大亏”是也。

  想起了美国人类行为专家的一个实验:他到了美国一个小学,里面一个一年级班级有48个孩子,他给每个孩子5颗做了特殊标记的糖,并告诉他们如果到一周后谁能一颗都不吃,我就给他100颗糖。一周后,48个孩子中只有4个孩子做到了。他跟踪了这48个孩子30年的成长,最后发现那4个孩子都成为了十分成功的人物,他们4个人30年后拥有的财富是剩下44个孩子财富总和的3倍。

  同样道理,即使很多开发人员也知道好的单元测试能让以后省不少心,但他们也宁可省掉写单元测试时间去堆砌代码。因为我们总觉得今天省掉1个小时多写一个类更有的赚,虽然我们以后要为省掉的1小时多付出3个小时去抓狂。

  小结一下

  上面写了很多,所以我认为这里有必要小结一下,整理一下思绪。

  单元测试的概念——一小段代码,用于检查一个或几个相关的方法行为是否正确。

  单元测试的本质——随功能代码一起提供的一个配套工具。

  单元测试的用途——保证交付Class行为正确,随时可用于自动化检测其对应的Class行为是否正确,对整个软件的质量是一种保证,对缺陷是一种控制。

  为什么需要单元测试

  我忽然发现,写了上面的文字后,再来讨论这个问题有些多余了,那么我尽量写简短一点。

  1、开发人员有义务提供行为正确的Class,也有权利得到行为正确的Class。

  很明显,如果你和你的同事,都能重视单元测试的话,你将同时履行这份义务和享受这份权利。

  2、尽早消灭缺陷。

  缺陷越早消灭所付出的代价越小,而越往后其代价呈指数增长,这是有充分的实验数据证明的,并已经被写到每一本软件工程教科书中。毫无疑问,当你交付一个Class前,就将其行为上的缺陷全部扼杀,那将取得巨大的收益。

  3、使合作变得愉快顺畅。

  想想看,每个你调用的Class,都是经过你的同事测试,确保行为是正确的,这是多么美妙的事情!我们写程序经常没有安全感,我们战战兢兢,很大程度上是因为我们没信心认为调用的每个Class行为是正确的。

  4、得到一个有力的工具,会在后续工作中大显身手的工具。

  如果每个Class都有配套的单元测试,好的,如果你想确认你的改动有没有影响到其它几个Class,run it!如果你想看看你调用的类是否行为正确,run it!如果你在重构,想看看重构有没有改变或损害其行为,run it!你正在调试一个bug但很难定位问题出在哪个地方,run it!你想看看目前项目中所有集成进来的代码是否行为都正确 run it! ……

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics