在过去的几年中,由于称为“极限编程(XP)”的轻量级编程方法,单元测试已成为我编写软件的关键(请参阅参考资料 )。 这种方法要求我为添加的每个功能编写单元测试,并维护这些测试。 我无法将任何代码与失败的单元测试集成在一起。 随着代码库的增长,这些测试使开发人员可以放心地集成更改。
最初,我认为这些单元测试的存在将使功能测试变得不必要。 糟糕,再次出错。 功能测试和单元测试有很大的不同。 我花了很长时间了解它们之间的不同之处以及如何将它们一起使用以增强开发过程。
本文探讨了单元测试和功能测试之间的差异。 它还概述了在日常开发中使用它们的过程。
测试与开发过程
作为开发人员,测试是如此重要,因此您应该一直进行测试。 它不应降级到开发周期的特定阶段。 在将系统交付给客户之前,这绝对不是最后要做的事情。 完成后您还将如何知道? 您还如何知道对小错误的修复是否破坏了系统的主要功能? 该系统还能如何发展到超出当前预期的范围? 测试(无论是单元测试还是功能测试)都需要成为开发过程中不可或缺的一部分。
单元测试应该成为您编写代码的中心,尤其是当您正在处理的项目有严格的时间限制并且希望控制它时,尤其如此。 单元测试非常重要,因此在编写代码之前应先编写测试。
维护的单元测试套件:
- 代表最实用的设计
- 提供类的最佳文档形式
- 确定何时完成某个类
- 使开发人员对代码充满信心
- 是快速重构的基础
单元测试构成了随系统自然发展的设计文档。 再读一遍。 这是软件开发的圣杯,文档随系统自然发展。 有比提供一组用例编码更好的记录类的方法。 这就是这些单元测试的内容:给定一组受控制的输入,一组编码用例记录了类的工作。 因此,此设计文档始终是最新的,因为始终必须通过单元测试。
在编写代码之前,应先编写测试。 这样做为测试将要使用的类提供了一种设计,使您可以专注于小部分代码。 这种做法也使设计保持简单。 您不是要展望未来,而是要实现不必要的功能。 另外,首先编写测试可以让您知道课程何时结束。 所有测试通过后,任务即告完成。
最后,单元测试为您提供了高度的信心,这使开发人员感到满意。 如果每当对代码进行更改时就运行单元测试,那么您将立即发现更改是否破坏了某些内容。
功能测试比单元测试更为重要,因为它们可以验证您的系统已准备好发布。 功能测试以一种有用的方式定义了您的工作系统。 维护的功能测试套件:
- 以有用的方式捕获用户需求
- 使团队(用户和开发人员)对系统符合这些要求的信心
功能测试以一种有用的方式捕获用户需求。 传统开发捕获用例中的需求。 通常,人们会争论用例,并花费大量时间来完善它们。 完成后,他们所拥有的只是纸。 功能测试就像自我验证用例。 极限编程方法论可以说明这一概念。 XP Stories是对客户与开发人员之间未来对话的承诺。 功能测试是此对话的输出。 没有功能测试的故事无法很好地构建。
功能测试填补了单元测试留下的空白,使团队对代码更有信心。 单元测试会遗漏许多错误。 他们可能会为您提供所需的所有代码覆盖范围,但可能不会为您提供所需的所有系统覆盖范围。 功能测试将暴露单元测试所缺少的问题。 一套经过维护的自动化功能测试套件也可能无法捕获所有内容,但它所捕获的功能远胜于最佳的单元测试套件所能单独捕获的功能。
单元测试与功能测试
单元测试告诉开发人员代码处事正确 ; 功能测试告诉开发人员代码正在做正确的事情 。
单元测试
单元测试是从程序员的角度编写的。 它们确保类的特定方法成功执行一组特定任务。 每个测试都可以确认一种方法在给出已知输入时会产生预期的输出。
在没有测试框架的情况下编写一套可维护的,自动化的单元测试几乎是不可能的。 在开始之前,请选择您的团队同意的框架。 您将不断使用它,因此您更喜欢它。 从极限编程网站上可以找到几种单元测试框架(请参阅参考资料 )。 我最熟悉的一个是用于测试Java代码的JUnit。
功能测试
功能测试是从用户的角度编写的。 这些测试证实了系统可以完成用户期望的操作。
很多时候,系统的开发都比作房屋的建设。 尽管这种类比不是很正确,但是我们可以扩展它,以便理解单元测试和功能测试之间的差异。 单元测试类似于建筑物检查员访问房屋的建筑工地。 他专注于房屋的各种内部系统,地基,框架,电气,管道系统等。 他确保(测试)房屋的各个部分将正确,安全地工作,即符合建筑规范。 在这种情况下的功能测试类似于房主访问相同的建筑工地。 他假设内部系统将正常运行,并且建筑物检查员正在执行其任务。 房主专注于这所房子的生活。 他关心的是房子的外观,各个房间的大小是否舒适,房子是否满足家庭的需求,窗户是否是捕捉早晨阳光的好地方。 房主正在对房屋执行功能测试。 他具有用户的观点。 房屋检查员正在对房屋进行单元测试。 他具有建造者的观点。
像单元测试一样,编写没有测试框架的可维护的自动化功能测试套件几乎是不可能的。 JUnit非常擅长于单元测试。 但是,在尝试编写功能测试时会解开。 没有等效的JUnit可以进行功能测试。 有可用于此目的的产品,但我从未见过将这些产品用于生产环境。 如果找不到满足您需求的测试框架,则必须构建一个框架。
无论我们在构建我们正在从事的项目上有多聪明,无论我们构建的系统有多灵活,如果我们生产的产品不可用,我们都在浪费时间。 因此,功能测试是开发中最重要的部分。
由于两种类型的测试都是必需的,因此您将需要编写准则。
如何编写单元测试
当您开始编写单元测试时,很容易变得不知所措。 最好的开始方法是为新代码创建单元测试。 (很难通过为现有代码创建单元测试来开始,但是有可能这样做。)从新代码开始,熟悉该过程,然后重新访问现有代码以为其创建测试套件。
如前所述,在编写要测试的代码之前,应先编写单元测试。 您如何为尚不存在的内容编写测试? 很好的问题,蚱hopper。 掌握这种做法需要90%的头脑和10%的技术。 我的意思是,您只是假装正在编写测试的类存在。 然后编写测试。 最初,您会遇到很多语法错误,但请耐心等待。 通过此练习,您正在定义该类将实现的接口。 下一步是运行单元测试,修复语法错误(即,使用测试刚刚定义的接口实现该类),然后再次运行测试。 重复此过程,每次编写足够的代码来修复故障。 运行测试,直到通过。 当所有单元测试都通过时,代码“完成”。
通常,您的类的每个公共方法都应该进行单元测试。 但是,具有非常简单功能的方法(例如getter和setter方法)不需要单元测试,除非它们以某种“有趣的”方式进行获取和设置。 遵循的一个很好的指导方针是在您需要注释代码中的某些行为时编写单元测试。 如果您像许多不喜欢注释代码的程序员一样,则单元测试是记录代码行为的一种方式。
将单元测试与相关的测试类放在同一包中。 这种类型的组织结构允许每个单元测试调用方法和引用变量,这些方法和引用变量具有package
访问修饰符或在要测试的类中protected
。
避免在单元测试中使用域对象。 域对象是特定于应用程序的对象。 例如,电子表格应用程序可能有一个注册对象。 该对象将是域对象。 如果您有一个已经知道域对象的类,则可以在测试中使用这些对象。 但是,如果您有一个不使用这些对象的类,请不要通过测试将这些对象绑定到该类。 应该避免这种做法的原因都与代码重用有关。 通常为一个项目创建的类适用于其他项目。 重用这些类可能很简单。 但是,如果重用类的测试使用另一个项目的域对象,则使测试正常工作可能会变得非常耗时。 通常,测试将被删除或重写。
这些机制可以很好地为您服务,但是如果您不运行测试,那么一套全面的单元测试将毫无用处。 尽早运行测试通常会使您始终对代码绝对有信心。 随着项目的进行,您将添加功能。 运行测试将告诉您刚刚实施的新功能是否有问题。
在掌握了编写单元测试的技巧之后,请重新访问现有的代码。 为现有代码编写测试可能是一个挑战。 不要为了测试而测试。 当您发现需要修改没有良好(或任何)测试的类时,请以“及时”方式编写测试。 是时候添加测试了。 与往常一样,该类的单元测试应捕获其每个方法的功能。 找出测试应该测试什么的最简单方法之一就是查看现有代码中的注释。 任何评论都应在单元测试中捕获。 在方法开始时翻译块注释,描述方法在单元测试中的作用。
如何编写功能测试
尽管功能测试非常重要,但它却被誉为开发的丑陋继子。 在大多数项目中,有一个单独的小组进行功能测试。 通常会有一群人不断与系统互动,以确定其行为是否正确。 这种态度和团队建立是愚蠢的。
功能测试应该像单元测试一样进行。 一旦有待编写的代码会产生与用户交互的内容(例如对话框),就应编写测试,但要在实际编写代码之前。 与用户一起编写捕获用户需求的功能测试。 每当您开始新任务时,请在功能测试框架中描述任务。 然后,您的开发工作将继续进行,并在添加新代码时进行单元测试。 当所有单元测试都通过后,运行原始功能测试以查看它是否通过或是否需要修改。
理想情况下,功能测试小组的概念应该消失。 开发人员应该与用户一起编写功能测试。 在对系统进行了一系列功能测试之后,负责功能测试的开发团队的成员应使用初始测试的变型轰炸系统。
单元测试与功能测试之间的界线
通常,不清楚单元测试和功能测试之间的界限。 老实说,我也不总是清楚这条线在哪里。 在编写单元测试时,我使用了以下准则来确定所编写的单元测试是否实际上是功能测试:
- 如果单元测试跨越类边界,则可能是功能测试。
- 如果单元测试变得非常复杂,则可能是功能测试。
- 如果单元测试易碎(即,它是有效的测试,但是必须不断更改以处理不同的用户排列),则它可能是功能测试。
- 如果单元测试比编写的代码难编写,则可能是功能测试。
注意短语“这可能是功能测试”。 这里没有硬性规定。 单元测试和功能测试之间有一条界线,但是您必须确定该线在哪里。 您对单元测试的满意度越高,当特定测试越过单元到功能的界限时,就越清晰。
结论
单元测试是从开发人员的角度编写的,着眼于被测类的特定方法。 编写单元测试时,请遵循以下准则:
- 在编写要测试的类的代码之前,请先编写单元测试。
- 在单元测试中捕获代码注释。
- 测试所有执行“有趣”功能的公共方法(即,不要使用getter和setter,除非它们以某种独特的方式进行获取和设置)。
- 将每个测试用例与要测试的类放在同一包中,以访问包和受保护的成员。
- 避免在单元测试中使用特定于域的对象。
功能测试是从用户的角度编写的,并且侧重于用户感兴趣的系统行为。找到一个好的功能测试框架,或者开发一个良好的功能测试框架,然后使用这些功能测试来确定用户真正想要的是什么。 通过这种方式,功能测试人员可以获得自动化的工具,并且具有使用该工具的起点。
使单元测试和功能测试对您的开发过程至关重要。 如果这样做,您将有信心系统可以正常运行并且可以扩展。 如果不这样做,则不能确定。 测试可能不会很有趣,但是进行工作单元和功能测试会使开发变得更加有趣。
翻译自: https://www.ibm.com/developerworks/java/library/j-test/index.html