求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
迈出单元测试的第一步
 
作者:Gil Zilberfeld ,发布于2012-8-8,来源:infoq
 

单元测试不仅是软件行业的最佳实践,在敏捷方法的推动下,它也成为了可持续软件生产的支柱。根据最新的年度敏捷调查,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有一个运行器,能自动按运行速度分离。

总结

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


相关文章

微服务测试之单元测试
一篇图文带你了解白盒测试用例设计方法
全面的质量保障体系之回归测试策略
人工智能自动化测试探索
相关文档

自动化接口测试实践之路
jenkins持续集成测试
性能测试诊断分析与优化
性能测试实例
相关课程

持续集成测试最佳实践
自动化测试体系建设与最佳实践
测试架构的构建与应用实践
DevOps时代的测试技术与最佳实践


LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   


性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术


某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...