如何做好单元测试

转载自:http://bbs.51testing.com/thread-1079215-1-2.html

目录:

好的单元测试应该具有的特点

单元测试的命名规范

建立自己的公共调用库

单元测试带给我的思考和感悟

总结图示

1、好的单元测试应该具备的特点

一个好的单元测试一定有它具备的特点,下面就来说说那些主要的特点!

主要概括为 → A-TRIP原则:

自动化  → Automatic

彻底性  → Thorough

可重复性 → Repeatable

独立性  → Indepentdent

专业性  → Professional

恰到好处的单元测试会使你的工作轻松,代码整洁干净,乱用,没有准则的用会浪费你大量的时间,不但没有效果还会是工期延误,所有了解单元测试很重要!

① 自动化

a)不需要人的参与,有的时候只是轻轻的点击一个按钮就能自动执行,所以自动化的标志是不能比点击一个按钮的过程还要复杂!

b)在签入其它的测试代码时不能对现有的代码造成影响!

c)能够自动识别测试是失败还是成功(VS2008以后的版本都集成了这个功能)!

d)在任何时候,任何地方都能自动运行(所以“Moles”技术就是关键)!

核心:执行测试代码和检查测试结果都必须自动化(VS2008以后版本都实现这个功能了)!

总结:I,不要引入一个由于需要手动步骤而打破单元测试的自动化模型的测试!

II,对于测试所需要的任何条件(大部分是数据库)都应该让它成为自动化测试的一部分,如果有需要可以使用Mole技术!

② 彻底性

所谓的彻底性就是说你的测试案例必须要考虑的全面,应该把可能出现的问题都做成测试案例!

具体从哪些方面着手,可以参阅这篇:走进单元测试:测试需要从哪些方面着手

③ 可重复性

a)每个测试案例应该独立于所有的其它测试,而且必须独立于周围的(系统)环境!

b)测试代码能够一次又一次的运行,在不修改代码的前提下都能产生一样的结果,否则有BUG!

c)不要把测试代码写死,应该写的更加灵活一点,运用封装,重构等等的思想!

d)不要让测试本身也出现BUG,确保测试代码的正确性!

④ 独立的

a)每个测试应该有很强的针对性,也就说一个测试只能测试一个方面的内容!

b)每个测试应该独立于环境(软件所处的系统环境)和其它测试!

总结:I,每个测试都不能够依赖于其它测试,你可以在任何时间运行这个测试而不受其它测试的影响,每一个测试都应该是一座孤岛!

II,所以测试一个函数都有很多个测试方法,只有这样才是真正的测试!

⑤ 专业的

a)所谓的专业就是你的测试代码应该跟你的开发代码保持一样的风格,如:简洁明了,封装,解耦,不要出现“Hard Core”,要灵活一点!

b)拒绝编写冗余的测试代码,千万要小心不要掉进这个陷阱,因为像我们这样的新手在初期都不会注意到这样的问题,所以我们要牢记在心里!

c)遵循普遍规则:1.维护封装 2.降低耦合!

总结:不管怎么样你都应该认认真真的对待单元测试,代码的质量要求都应该跟开发代码同等水平,这是作为开发者必备的素质!

2、单元测试的命名规范

在我们项目的中,可能需要测试的方法有成千上百个,而每一个测试方法都有可能写三个以上的测试案例,那么怎么来维护这么测试案例呢?

所以我们应该规范方法的命名方式,那么其他人在阅读你的测试代码时,直接通过方法名就能知道你的测试案例是测试哪个方面的了!

Note:单元测试案例类似于一个可执行文档,可以帮助其它的开发人员了解方法的作用!

在我们的项目中是这样规定的:方法名 + _ + 你测试是哪个方面的内容 + _ + 产生的结果!

下面我就举个列子,下面的测试方法命名就是针对这个函数来命名的,如:

                                       public DataSet GetDetails(int ID)         {              // 方法的作用:这个一个获取数据,并包装成一个DataSet的方法,传入的参数是一个bondAppID,                 那么我们怎么来设计案例和命名方法名呢?         }

① 首先设计你的测试案例

看到这个方法我就会有这几个想法:1,最大值 2,最小值 3,刚刚好的值 4,随便一个值 5,还有的测试案例会随着你代码中的逻辑而产生!

下面是我的测试案例以及方法名的命名,测试方法是上面的那个:

                                  /// <summary>      /// Input valid bond ,but the cheque is presented Status.      ///</summary>         [TestMethod()]            //如果你预期有数据返回,那么就应该在最后面加上“RecordFound”,这样别人看的时候就能一目了然了!         public void GetDetails_CheckPresentedStatus_RecordFound()         {            //To Do.         }          /// <summary>      ///Cancel Status.      ///</summary>         [TestMethod()]         public void GetDetails_CheckCancelStatus_RecordFound()         {            //To Do.         }          /// <summary>      /// Input max.      ///</summary>         [TestMethod()]         public void GetDetails_CheckDetailsByMaxBoundaryValue_NoRecordFound()         {            //To Do.         }          /// <summary>      /// Input min.       ///</summary>         [TestMethod()]         public void GetDetails_CheckDetailsByMinBoundaryValue_NoRecordFound()         {            //To DO.         }          /// <summary>      /// Any bondID.      ///</summary>         [TestMethod()]         public void GetDetails_CheckDetailsByAnyBondID_NoRecordFound()         {            //To Do.         }

 ② 说一下总的命名规范

a)如果返回值是“Bool”型的话,应该在最后面加上“RetrunTrue”或“ReturnFalse”!

如:CheckRebateHasNewChange_ExistNewChanges_ReturnTrue

b)如果返回值是集合或者DataSet之类的类型话,应该在最后面加上“RecordFound”或者“NoRecordFound”!

如:GetPurchaseOrder_ByPurchaseOrderId_RecordFound

c)如果是测试异常的话,应该在后面加上“ThrowException”!

如:CreateRebateApplication_ExistSameRebateID_ThrowException

d)如果是岁数据库进行删除或者更新,插入的操作时,应该在后面写上“DataUpdated”或“NoDataUpdated”!

如:UpdateClientIncome_UpdateIncome_DataUpdated

总结:基本的变动部分都是你中间的描述,中间的描述尽量做到简洁明了,应该以动词开头,如:Update,Input,等等!

总结:好的命名方式可以增强代码的可读性,尤其是类似于单元测试这样的可读性文档,应该更加注意这方面的考虑!

3、建立自己的公共调用库

一个好的单元测试应该都有自己的公共调用库,这样能减少很多的冗余代码,使的代码简洁易懂!


所以建立了公共库的单元测试符合了单元测试特点中的专业性 → 1.维护封装,解耦等等特点!

① 建立数据库访问库

测试中的准备数据从何而来,有的时候数据准备比较简单,那么如果传入的参数是DataSet的呢,里面的数据比较多呢,或者更新数据库呢,我们应该怎么做?

那么我们应该去数据库中查找数据,然后在组合到DataSet中去,所以建立必要的数据库访问类很重要!

② 建立自己的公共库

这些是为了你验证数据,或者搭建环境等等的一些用处,提高编写测试代码的速度


这是我们建立的公共调用库,所以如果建立了库,对于我们的单元测试将是大有裨益的!

4、单元测试带给我的思考和感悟

① 感悟

说实话从一开始做单元测试的时候,我对它真的很鄙视,我觉得它的含金量很少,编写单元测试代码太枯燥了,导致了那时候我的心态是多么的浮躁,以至于在思想方面出现了偏差,感兴趣的朋友可以看看这篇文章 → 工作的思考,是走还是留!

经过一段时间的自我反省,也确实让我慢慢的走上了正轨,感兴趣的朋友可以看看 → 迷茫后的感悟!

a)做任何一件事都应该专心,戒骄戒躁,这是我深有体会的!

b)事无大小,不要认为你的事很无聊,很枯燥,很不值得一提,但是一旦你做好做精之后你就发现原来你不知道的还有这么多啊!

c)努力 + 学习方法 + 工作态度 → 是我这段时间感悟比较深的一件事!

② 思考

前提:我们做的单元测试是在项目后期写,而且我对我负责的模块是一窍不通的(我是刚刚进这个项目组的),业务流程根本不懂!

在这样的前提下,每天先熟悉下流程,看一下代码,找带头大哥帮我讲解讲解,然后才开始写单元测试代码,由此我有了下面三点的思考:

a)单元测试不应该在后期做,应该在项目的开发时期去完成它,这个可能跟我们的项目本身有原因把!

b)对于找一个还不懂业务流程的人来做单元测试自我感觉是有点不合理的,至少会花费更多的时间来熟悉流程。然后再做单元测试!

c)项目过程中的代码编写习惯也是很重要的,这是我应该要加强和思考的,也是我需要培养的习惯!






下面内容转载自:http://bbs.51testing.com/thread-1079214-1-2.html


单元测试不仅是软件行业的最佳实践,在敏捷方法的推动下,它也成为了可持续软件生产的支柱。根据最新的年度敏捷调查,70%的参与者会对他们的代码进行单元测试。

单元测试和其他敏捷实践密切相关,所以开始编写测试是组织向敏捷转型的踏脚石。道路漫长,但值得去做。我将在本文介绍符合要求的小技巧,以及在开发周期里进行单元测试的步骤。

有效的单元测试默认要能自动化。没有自动化,生产力就会下降。没有自动化,单元测试的习惯也不会持续太久。依靠手工测试(由测试人员或开发人员完成)并不能持续太长时间;在有压力的情况下,没人会记得去运行所有的测试,或者去覆盖所有的场景。自动化是我们的朋友,所有的单元测试框架都支持自动化,而且集成了其他自动化系统。

单元测试对现代开发来说至关重要

有代码相关的测试,我们就有一个天然的安全保障。我们修改的代码要是带来了什么问题,测试会告诉我们。这个安全保障越健全,我们对代码正常运行的信心就越大,对按需修改代码的能力也就越有信心。

和其他类型的测试相比,单元测试的主要优点是反馈迅速。在几秒钟内运行数百个成套的测试,这对开发流程很有帮助。我们会形成“添加一些代码,添加测试,测试运行通过,前进”的节奏。小步前进、确保一切正常也意味着调试时间会大大减少。测试能提高生产力也就不足为奇了——在Bug上少花时间,把更多的时间用到新功能的推出上。

依赖关系的壁垒

给新建项目添加测试相当容易——毕竟代码不会阻碍测试。不过这种情况绝对不常见。大多数人都是在处理遗留代码,这些代码不太容易测试,有时候甚至运行不起来——它需要的数据或配置可能只存在于生产服务器上。我们或许要为不同的场景创建不同的设置,这也许会花费过多的精力。在很多情况下,我们可能还会为了测试修改代码。这让人无法理解:我们编写测试就是为了能有修改代码的信心,还没有测试又该如何去稳妥地修改代码呢?

代码可测性是语言和工具的功能。大家认为Ruby等动态语言是可测的。对于测试的内部代码,我们可以改变其依赖关系的行为,而不用修改生产代码。C#或Java等静态类型语言则不太容易去测试。

下面有个例子:一个C#的过期检查方法,检查是否超过了特定日期:

  public class ExpirationChecker{    private readonly DateTime expirationDate = new DateTime(2012, 1, 1);    public bool IsExpired()    {        if (DateTime.Now > expirationDate)        {            return true;        }         return false;    }}

在这个例子里,IsExpired方法的DateTime属性对测试运行时间有强依赖。Now返回的是实际时间。这个方法有两种情况,它会根据日期返回不同的值。修改计算机时间是绝对不行的,因为我们要在任何时候到任何计算机上去测试场景,并且不能带来任何副作用。

要测试到两种情况,一种可能的解决方案是修改代码。比如说,我们可以把代码修改成:

  public bool IsExpired(DateTime now){    if (now > expirationDate)    {        return true;    }    return false;}  

这样,测试可以注入不同、可控的DateTime值,而不用在生产代码里写定一个值。我们要是不能修改代码,可以利用Typemock Isolator等Mocking框架,模拟静态属性和方法。针对先前的代码,测试可以写成:

  [TestMethod]public void IsExpired_BeforeExpirationDate_ReturnFalse(){    Isolate.WhenCalled(() => DateTime.Now)        .WillReturn(new DateTime(2000, 1, 1));    ExpirationChecker checker = new ExpirationChecker();    var result = checker.IsExpired();    Assert.IsFalse(result);}

现有的遗留代码不能轻易修改,因为我们没有针对它的测试。开始测试遗留代码之后,我们就能明白:代码越丑陋,测试越困难。工具可以减轻一些痛苦,但我们要努力去构建安全的环境。

依赖关系并不是唯一的内容……

我们很快会遇到的另一个问题是测试维护:测试和被测试代码耦合在一起。有耦合关系,修改生产代码就有可能破坏测试。要是代码修改引起测试失败,我们就需要回去解决这些问题。很多开发人员害怕维护两个代码库,这种恐惧甚至会让他们干脆不进行单元测试。真正的维护工作既取决于工具,也取决于技巧。

编写好的测试是通过实践获得的技能。编写的测试越多,我们就越精于此,同时会提升测试质量,维护也越来越少。有了测试,我们就有机会重构代码,这反过来又会让测试更简洁、更易读、更健壮。

工具对实践的难易程度有极大的影响。在基础层,我们需要一个测试框架和一个Mocking框架。在.Net领域,两种框架的选择都很丰富。

编写第一个测试的准则

开始的时候,我们通常会试用不同的工具,来理解他们的工作原理。我们往往不会在实际的工作代码上开始编写测试。但很快就要给代码编写真正的测试。有一些小提示届时会有用:

  • 从哪里开始:一般来说,我们编写测试是针对工作代码的,无论代码是Bug修复还是新功能。对Bug修复来说,编写的测试要检查修复。对功能来说,测试应检查正确的行为。
  • 支架:以我们掌握的知识来看,明智的做法是先添加能确保当前实现运行的测试。添加新的代码之前先写测试,因为我们希望在修改现有代码之前,能有安全的保障。这些测试被称为“特征测试”,这个术语来自Michael Feathers编写的《修改代码的艺术》。
  • 命名:测试最重要的属性是它的名字。我们一般不会去看运行通过的测试。但当它失败时,我们看的就是它的名字。所以挑一个好名字,描述出场景和代码的预期结果。好名字还有助于我们定位测试里的Bug。
  • 评审:为了增加测试成功通过的机会,编写第一个测试时我们应该和同事结对。两个人都能从实践中学习,而且我们还能立即评审测试。最好对所测的内容、测试的名称达成共识,因为这会成为团队其他人员的基本模板。
  • AAA:现代测试的结构符合AAA模式——Arrange(测试设置)、Act(调用测试里的代码)、Assert(测试通过的标准)。如果我们使用测试驱动开发(TDD),我们要先编写完整的测试,然后再添加代码。对遗留代码来说,我们可能需要换一种方式。一旦我们有一个场景和名称需要测试,那先编写Act和Assert部分。我们要不停构建Arrange部分,因为对需要准备或仿造的依赖关系,我们知道的要更多一些。然后继续这么做,直到有一个测试能够通过。
  • 重构:一旦准备好了测试,我们就可以重构代码了。重构和测试都是后天获得的技能。我们不仅要重构被测试代码,也要重构测试本身。但DRY(不要重复自己)原则不适用于测试。测试失败时,我们希望尽快修复问题,所有的测试代码最好在一个地方,而不是分散在不同的文件里。
  • 可读性:测试应该是可读的,最好是人类可读。和搭档评审测试代码,看他能否理解测试的目的。评审其他测试,看看它们的名称和内容怎样与相邻的测试区分开来。一旦测试失败,就需要修复它们,最好还是在运行失败之前评审它们。
  • 组织:一旦我们有了更多的测试,组织就有了用武之地。测试可以在很多方面有所不同,但最明显的一个就是如何快速运行。有些测试可能在毫秒内运行完,而有些则需要数秒或好几分钟。和工作一样,我们都希望得到最快的反馈。这就是前面谈到的怎么按一定的节奏去进行。要做到这一点,你应该把测试划分一下,把快的测试和慢的测试分开运行。这能手工(努力)去做,但在.NET领域,Typemock Isolator有一个运行器,能自动按运行速度分离。
总结

迈出单元测试的第一步是很有挑战的。体验依赖的东西很多——语言、工具、现有代码、依赖关系和技能。只要稍稍思考,进行大量训练和实践,你就能渐入测试的佳境。




  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值