本文翻译自IBM DeveloperWorks上的一篇文章,该文讲述了测试分类(test categorization)的概念,本身这个概念很简单,但是却实际的解决我们常见的问题,在我们的测试庞大到一定地步的时候,测试的运行时间过长,维护成本很高,我们如何能够保证持续集成(CI)的正常运行?那就是通过测试分类。所以我翻译了这片文章,希望对大家有所帮助。
原文:In pursuit of code quality: Use test categorization for agile builds
原文作者:Andrew Glover is president of Stelligent Incorporated, which helps companies address software quality with effective developer testing strategies and continuous integration techniques that enable teams to monitor code quality early and often. Check out Andy's blog for a list of his publications.
大家都同意开发人员的测试很重要,但是为什么要花这么长的时间运行测试呢?这个月,Andrew Glover将给我们讲述对于系统来说需要保证运行的三类测试,并且告诉你如何根据分类整理和运行测试。结果将会奇迹般的减少build的时间,即使是面对当今庞大的测试集。
如果不太难过的话,假想一下你是一个2002年初刚刚建立的公司的开发人员。在淘金热潮中,你和你的同事已经决定使用最流行最强大的Java API来开发一个庞大的数据驱动的Web应用程序。你可你的管理团队坚定的信仰敏捷过程。从第一天开始,就使用JUnit编写测试,并且通过Ant build脚本尽可能频繁的运行它们。最后,你们还会使用cron(*nix下的一个定时运行脚本的任务)来进行nightly build。再然后,某些人可能会下在CruiseControl然后把测试写成套件,然后在每次check-in时执行(持续集成)。
现在回到今天。
经过了前几年的磨练,你的公司已经开发了数量巨大的代码,当然也有同样庞大的JUnit测试。一年前所有的事情都运转良好,当你的测试套件有超过2000个测试,人们开始注意到build过程可能要执行三个小时以上。几个月以前,你停止通过代码提交来处罚持续集成(CI)运行单元测试,因为CI服务器会因此过渡繁忙。你改为进行nightly测试(每日测试),第二天早上开发人员可能会头疼测试为何失败。
最近,测试套件似乎很难在晚上运行一次以上了——这是为什么呢?它们永远运行不完!没有人会用几个小时的时间来等待确认代码运行是正常的(或不正常)。所以,整个的测试会在晚上运行,对么?
因为你如此频繁的运行测试,他们总是充满了问题。(译者注:你会开始怀疑是不是测试也出了问题,是否想测试你的测试?)从而,你和你的团队开始怀疑单元测试的价值:如果代码质量并不那么重要,为什么我们要承受这种痛苦?假如你可以用敏捷的方法运行它们的话,你们完全同意这是单元测试的基本价值。
尝试测试分类(test categorization)
你需要的是一个让你的build转变到更敏捷状态的策略。你需要一种解决方案来允许你在一天内多次运行测试,让那些已经需要三个小时完成的测试回到原先的状态。
在你尝试使用这个策略让你的测试套件恢复原形之前,思考一下“单元测试”的基本概念可能会有所帮助。“我家有一只动物”和“我喜欢汽车”这样的陈述不是非常明确,所以,不幸的是,“我们编写单元测试”也不明确。现在,但愿测试泛指一切。
思考前面的两个关于动物和汽车的陈述:它们产生了很多疑问。例如,你家里有什么动物?是猫、蜥蜴还是熊?“我家有一只熊”与“我家有一只猫”完全不同。同样的,“我喜欢汽车”对于与汽车销售商交谈时没有帮助。你喜欢哪种车:运动车、卡车或者大货车?不同的答案会将你引入不同的路径。
同样,对于开发人员进行测试,根绝测试类型分类是有所帮助的。这样做更加精确,能够允许你的团队以不同的频度运行不同类型的测试。分类是避免恼人的运行所有“单元测试”的三小时build的关键方法。
三种分类
形象的将你的测试套件整理为三层,每一层代表开发人员进行的不同类型的测试,它们是根据运行时间的长短划分的。如图1所示,每一层将花费更多的总build时间,无论是运行时间还是编写它们所需的时间。
图1 测试分类的三层
最下面一层测试运行时间最短,如你所想,他们也是最容易写的。他们也覆盖最少量的代码。顶层是有高层次的测试组成,它们检测应用程序的很大一部分。这些测试相对难写,同时也需要更多时间来执行。中间一层测试介于两个极端之间。
这三个分类如下:
让我们分别的考察它们。
1、单元测试
单元测试隔离的确认一个或者多个对象。单元测试不处理数据库、文件系统或者任何可能带来测试不能保证长期可运行的因素;顺序上,测试可以从(项目)第一天就开始写。事实上,这就是JUnit的设计目标。单元测试的隔离概念是在很多mock对象库隔离特定对象的外在依赖的基础上的。进一步说,单元测试可以在实际代码编写前就开始写——也就是测试先行开发TDD的概念。
单元测试一般容易编写,因为他们不依靠于系统依赖,并且他们运行迅速。不好的方面是,单独的单元测试只能提供有限的代码覆盖度。单元测试的价值在于允许开发者在最低的依赖程度下保证对象的质量。
因为单元测试运行迅速容易编写,一个代码库应该有很多单元测试且尽量频繁的运行它们。你应该在每次build的时候运行它们,不管是在你的机器或者一个CI环境(以为这你应该在每次向SCM系统chech in之前运行它们)。
2、组件测试
组件测试保证多个对象的交互,但是它们突破了代码隔离的概念。因为组件测试处理多层架构,他们经常要处理数据库、文件系统、网络元素等。而且组件测试一般很难在(项目)前编写,所以将它们加入到一个实际的测试先行/测试驱动的场景中是个很大的挑战。
组件测试编写要花多一些时间,因为他们比单元测试要棘手。从另一个方面来看,他们能够提供比单元测试更高的代码覆盖率因为它们的宽工作范围。它们运行耗时更多,所以它们会极大地拖长你们的总测试耗时。
一个宿主框架可能减少测试庞大架构组建的挑战难度。DbUnit就是一个这种框架的完美例子。DbUnit是编写依赖于数据库的测试容易,它能够处理复杂的数据库状态准备工作。
当测试引起build时间延长,你基本上可以确定那就是大组的组件测试造成的。因为这些测试比单元测试运行时间更长,你可能发现你不能总是运行它们。因此,它让CI环境至少以小时为间隔执行它们。你一应该要求每个开发者在check in前在本机环境运行这些代码。
3、系统测试
系统测试从端到端保证软件应用。因此,他们提出了高度的架构复杂性:整个应用必须在进行系统测试时运行。如果是一个Web应用程序,你需要访问数据库,从Web服务器、(应用程序)容器、任何相关的配置都要配合系统测试的运行。系统测试总是在软件开发周期的最后阶段撰写的。
系统测试对于编写人员是个挑战,并且实际往往花费比较长的时间。另一方面,他们提供更好的催款理由,也就是说,他们提供了系统架构级的代码覆盖率。
系统测试与功能测试非常相近。区别在于它们不是一个假扮用户,用户是虚拟的。就像组件测试一样,很多框架都是来帮助这类测试的。例如,jWebUnit通过模拟一个浏览器提供了测试Web应用程序的基础设施。
什么是接受测试?
接受测试与功能测试类似,不同点在于,理想情况下,客户或者最终用户来编写接受测试。与功能测试类似,接受测试按照最终用户的行为测试。一个备受关注的接受测试框架是Selenium,它使用浏览器来测试Web应用程序。Selenium可以在build过程中自动运行,就像JUnit测试一样。但是Selenium是一个新的平台:他不一定使用JUnit,方式也不太一样。(Selenium RC就没有这个问题了)
我应该使用jWebUnit或者Selenium?
jWebUnit是一个JUnit扩展框架,设计用来进行系统测试;所以,它需要你自己写这些测试。Selenium是一个优秀的接受测试和功能测试工具,不同于jWebUnit,它允许非程序员编写测试。理想状态下,你的团队可以同时使用两种工具来确认应用程序的功能。
使用TestNG进行测试分类
使用TestNG实现测试分类非常容易。使用TestNG的group注释,逻辑上将测试分类就是进行合适的group注释,这非常简单。运行某一分类的测试只需要将group名称传给test runner就可以了,例如通过Ant。
实现测试分类
所以,你的单元测试套件实际上是单元测试、组件测试和系统测试的套件。甚至,在你检查所有的测试后发现build需要这么长时间是因为大部分测试都是组件测试。下一个问题是,如何通过JUnit实现测试分类?
你有很多选择,但是让我们先试验一下最简单的两个:
根据需要的分类创建不同的JUnit套件(suite)文件
对于不同类型的测试创建不同的目录
创建不同的套件
你可以使用JUnit的TestSuite类(它也是一种Test)定义一组同类测试的集合。你要创建一个TestSuite的实例并添加相关的测试类到test方法中。你可以在TestSuite实例中通过定义一个叫做suite()的public static方法告诉JUnit这个套件包括哪些测试。所有包括的测试将会一次全部执行。因此你可以通过创建TestSuite来实现测试分类,一个单元测试的TestSuite、一个组件测试的TestSuite,有一个系统测试的TestSuite。
例如清单1的类中的suite()方法创建了一个包含所有组建测试的TestSuite。注意这个类不是非常符合JUnit规范。他既没有继承TestCase,也没有任何测试的定义。但是JUnit会自动发现suite()方法并且运行它返回的所有测试类。
清单1 单元测试的TestSuite
package test.org.acme.widget;
import junit.framework.Test;
import junit.framework.TestSuite;
import test.org.acme.widget.*;
public class ComponentTestSuite {
public static void main(String[] args) {
junit.textui.TestRunner.run(ComponentTestSuite.suite());
}
public static Test suite(){
TestSuite suite = new TestSuite();
suite.addTestSuite(DefaultSpringWidgetDAOImplTest.class);
suite.addTestSuite(WidgetDAOImplLoadTest.class);
suite.addTestSuite(WidgetReportTest.class);
return suite;
}
}
清单2 运行组建测试的一个Ant任务
< target name ="component-test"
if ="Junit.present"
depends ="junit-present,compile-tests" >
< mkdir dir ="${testreportdir}" />
< junit dir ="./" failureproperty ="test.failure"
printSummary ="yes"
fork ="true" haltonerror ="true" >
< sysproperty key ="basedir" value ="." />
< formatter type ="xml" />
< formatter usefile ="false" type ="plain" />
< classpath >
< path refid ="build.classpath" />
< pathelement path ="${testclassesdir}" />
< pathelement path ="${classesdir}" />
classpath >
< batchtest todir ="${testreportdir}" >
< fileset dir ="test" >
< include name ="**/ComponentTestSuite.java" />
fileset >
batchtest >
junit >
target >
理想情况下,你还需要一个触发单元测试的任务和系统测试的任务。最后,还有希望运行所有测试的情况,你需要创建第四个任务来运行其它三个任务,就像清单3里面那样:
清单3 运行所有测试的任务
< target name ="test-all" depends ="unit-test,component-test,system-test" />
创建单独的TestSuite是一个迅速实现测试分类的解决方案。缺点是这个方法需要你创建新的测试,你必须编成式的将它们添加到合适的TestSuite里面,这可能有点痛苦。给每个测试类型创建单独的目录可能是一种更加有弹性的方法,它允许你添加新的测试分类但无需重新编译。
创建单独的目录
我发现最简单的通过JUnit实现测试分类的方法是逻辑上将不同类型的测试放到不同的目录中。使用这个方法,所有的单元测试都放在unit目录,所有的组建测试都放在component目录,等等。
例如,在test目录中保存着所有未分类的测试,你可以创建三个新的子目录,就像清单4中那样:
清单4 实现测试分类的目录结构
acme-proj/
test/
unit/
component/
system/
conf/
运行这些测试,你需要定义至少四个Ant任务:一个给单元测试,另外的给组建测试,还有系统测试。第四个任务是一个方便运行其它三个测试类型的任务(就像清单3种展示的那种方式)。
JUnit任务就像清单2中的形式。区别在哪里呢,只是在任务的batchtest这个地方。这次,fileset指向的是一个指定的目录,就像清单5种的样子,他指向了unit目录:
清单5 JUnit任务中的batchtest方面,用来运行所有单元测试
< batchtest todir ="${testreportdir}" >
< fileset dir ="test/unit" >
< include name ="**/**Test.java" />
fileset >
batchtest >
注意这个任务运行test/unit目录下的所有测试,当创建了新的单元测试(或者其它分类的其它测试),你只需要把它们放到这个目录里面就可以了!这比添加一行到TestSuite中并重新编译它要方便多了。
问题解决了!
回到最初的场景,我认为你和你的团队会决定使用单独的目录这种弹性的解决方案来解决你们的build时间过长的问题。这个任务最难的一个方面是检查和分清测试类型。你重构你的Ant build文件创建四个新的任务(三个单独的测试分类还有一个运行它们三个)。甚至,你修改CuiresControl只在check-in的时候运行单元测试,而组建测试按小时运行。更进一步的检查后,系统测试也可以几个小时运行一次,也许你会创建一个新的任务来同时运行组建测试和系统测试。
最后的结果是每天测试运行很多次,你的团队可以快速的发现集成错误——一般在几个小时内。
创建敏捷构建不是为了赶时髦,它实际上是保证代码质量的重要因素。测试运行的更加频繁,开发人员的测试的价值就能直接转化为钱。并且,希望你们的公司能够在2006取得广泛的成功!
资源
Learn
- " Automate acceptance tests with Selenium " (Christian Hellsten, developerWorks, December 2005): Architects, developers, and testers learn how to use the Selenium testing tools to automate acceptance tests.
- " Effective Unit Testing with DbUnit " (Andrew Glover, OnJava, January 2004): Introduces database-dependent testing with DbUnit.
- " An early look at JUnit 4 " (Elliotte Harold, developerWorks, September 2005): Obsessive code tester Elliotte Harold takes JUnit 4 out for a spin.
- " Repeatable system tests " (Andrew Glover, developerWorks, September 2006): Andrew Glover introduces Cargo, an open source framework that automates container management in a generic fashion.
- " Create test cases for Web applications " (Amit Tuli, developerWorks, May 2005): Software engineer Amit Tuli introduces jWebUnit.
- " In pursuit of code quality: JUnit 4 vs. TestNG " (Andrew Glover, developerWorks, April 2006): Has JUnit 4 rendered TestNG obsolete? Find out why not.
- In pursuit of code quality series (Andrew Glover, developerWorks): Learn more about code metrics, test frameworks, and writing quality-focused code.
- developerWorks : Hundreds of articles about every aspect of Java programming.
Get products and technologies
Discuss
定义TestSuite的过程需要你察看你当前的所有测试并将它们加入到相应的类里面(例如,所有的单元测试加入到UnitTestSuite)。这也就意味着你在相应的分类里面创建了新的测试,你必须编程式的将它们添加到合适的TestSuite中,当然还需要重新编译它们。
运行单独的TestSuite需要单独的Ant任务来运行正确的测试组。你可以定义一个component-test任务来执行ComponentTtestSuite,就像清单2中的样子:
分享到:
相关推荐
在这个项目中,“tc.rar”是一个包含相关资源的压缩包,它涉及到“Text Categorization”,即文本分类,以及“ns.dct”可能代表的一种特定的数据格式或分类标准。此外,标签明确指出这个项目使用了Java编程语言来...
Visual Categorization with Bags of Keypoints(视觉关键词包分类方法)这篇论文介绍了一种新颖的通用视觉分类方法,它解决了在自然图像中识别对象内容并泛化对象类别固有变化的问题。这种基于关键词包(Bag of ...
《机器学习在自动化文本分类中的应用》一文不仅回顾了文本分类技术的发展历程,还深入分析了文档表示、分类器构建与评估等关键环节,为读者呈现了一幅机器学习在文本分类领域应用的全景图。随着数据科学和人工智能...
通过上述分析可以看出,《统计方法在文本分类中的评估》这篇文章不仅对当时流行的文本分类技术进行了全面的比较,也为后续研究者提供了宝贵的参考信息。随着自然语言处理技术的不断发展,文本分类仍然是一个非常活跃...
传统的手工构建分类器不仅耗时而且效率低下,因此通过学习方法自动生成分类器成为了优选方案。支持向量机作为一种新兴的学习算法,由V. Vapnik等人提出,在计算学习理论基础上有着坚实的理论支撑,并且易于理解和...
为了验证所提出的模型的有效性,研究人员使用了十个不同的数据集进行了实验。每个数据集中,70%的数据被用作训练集,剩余的30%则作为测试集。实验结果显示,通过引入进化优化分类模型,确实可以直接利用EC技术解决...
相比之下,基于机器学习的方法通过从一组预先分类的文档中自动学习类别的特征来构建分类器,展现出了更高的效果、显著节省人力成本,并且能够轻松地扩展到不同领域。 ### 文档表示 文本分类的第一步是文档表示。这...
本文主要探讨了使用基于文本的监督学习方法对孟加拉语内容进行分类的问题。随着电子形式的文本文档日益普及,自动分析文本内容的重要性也随之增加。特别是在近年来社交媒体用户数量急剧增长的背景下,孟加拉语内容的...
Source Code: A Feedforward Architecture Accounts for Rapid Categorization,This code provides a framework for reproducing the main experimental result described in: T. Serre, A. Oliva & T. Poggio, "A...
在实际应用中,text_categorization库可能会利用机器学习算法,如朴素贝叶斯、支持向量机或者深度学习模型(如卷积神经网络或Transformer架构)来训练模型进行文本分类。这些模型需要大量的标注数据进行训练,以便...
【Viewport-Aware Dynamic 360-Degree Video Segment Categorization】阅读报告深入探讨了针对360度全景视频的动态分类方法。在当前的数字媒体时代,360度视频已经成为一种新兴的多媒体形式,它允许用户自由探索场景...
文本分类的基本思路是基于机器学习算法,通过对训练集的学习来构建模型,进而对新文本进行分类预测。常见的文本分类方法包括: - **朴素贝叶斯分类器**:基于概率统计理论,假设特征之间相互独立,适用于大规模文档...
层级分类(Hierarchic Categorization)则是一种组织和分析知识社群结构的方法,它通过识别不同社群之间的层次关系来理解知识的传播和专业化过程。这种分类可以帮助我们了解知识如何在不同层次的社群之间流动,以及...
successful multilingual text categorization combines linguistic techniques with robust monolingual text categorization.
在NLP中,我们利用机器学习(ML)技术来解决各种任务,如文本分类、情感分析、问答系统、机器翻译等。本资源集合专门针对NLP中的问题分类学习训练和测试,特别强调了英文环境的应用。 问题分类是NLP中的一个核心...
对于分类任务,KNN通过计算测试样本与训练集中每个样本之间的距离,找到距离最近的K个训练样本,并根据这些训练样本的类别来预测测试样本的类别。通常情况下,类别采用多数投票的方式决定。 ##### 2. 偏斜排序条件...