| 网站首页 | 测试咨讯 | 新手入门 | 专注性能 | 测试技术 | 测试管理 | 测试工具 | 开发社区 | 工具下载 | 资料下载 | 测试论坛 | 

[CNTester联盟群]交流群:34446273/21968356/64461572 白盒群:18400216 自动化群:2706508 性能群:4498858 外包群:59649884 管理群(需有管理经验):64442523

新手入门
 入门指南
 经验之谈
测试咨讯
 行业新闻
 网站动态
 人才就业
 CNTester基金
 标准规范
专注性能
 性能测试知识
 性能测试工具
 程序设计性能
 数据库性能
 Web应用服务器性能
 操作系统性能
 服务器与网络性能
测试技术
 用例设计
 性能测试
 自动化测试
 Web测试
 面向对象测试
 综合技术
 阶段性测试
 行业类测试
测试管理
 项目管理
 项目案例
 质量管理
 软件过程
测试工具
 Mercury系列
 Rational系列
 测试管理工具
 性能测试工具
 功能测试工具
 单元测试工具
 其它测试工具
开发社区
 Java技术
 DotNet技术
 C技术
 数据库技术
 操作系统
 服务器与中间件
工具下载
 测试工具
 开发工具
 企业工程
 网络通信
 媒体桌面
 游戏娱乐
 其他工具
资料下载
 测试资料
 程序设计
 数据库
 操作系统
 应用服务器
 电子期刊
 其他资料
 
 
您现在的位置: 中国测试员网站 >> 测试工具 >> 单元测试工具 >> TestNG >> 文章正文
  [推荐]测试分类技术与Testng         ★★★ 【字体:
测试分类技术与Testng
作者:Andy Glo…    文章来源:BEA    点击数:    更新时间:2007-6-9    

摘要

  TestNG是一种基于注释的测试框架,通过添加诸如灵活的装置、测试分类、参数测试和依赖方法等特性来克服JUnit的一些不足之处。此外,TestNG运行于Java 5.0(通过注释)和Java 1.4(通过JavaDoc样式的注释)之上。由于TestNG可以轻松地将开发人员测试分类成单元、组件和系统组,因此能够使构建时间保持在可管理的范围内。通过使用group注释和多重Ant任务,测试组可以不同的频率运行于一台工作站之上或持续集成环境中。

  本文分析了测试分类的概念,演示了如何将TestNG的group注释与灵活的测试装置具相结合,通过特定的Ant目标促进以不同频率运行的测试。本文假设您了解TestNG。

TestNG组的研究

  TestNG支持一种直观的机制,用于分组测试类和相关测试方法。在最基本的层面上,TestNG的分组特性是通过test注释的groups参数启用的,它可附加到类或者单个方法。从其名称即可看出,一个类或单个方法可属于1至n组。

  例如,下面的类包含两个公共方法,缺省标为测试并进一步分类为属于one组:

/**
 * @testng.test groups="one"
 */
public class SimpleGroupedTest {
 private String fixture;
 /**
  * @testng.before-class = "true" 
  */
 private void init(){
  this.fixture = "Hello World";
 }
 
 public void verifyEquality(){
  Assert.assertEquals("Hello World", this.fixture);
 }    
 
 public void verifySame(){
  String value = this.fixture;
  Assert.assertSame(this.fixture, value);
 }
}

  相反,下一个类定义了两个测试方法。然而,一个方法却属于两个不同的组——one和two。相应地,任何相关装置逻辑都必须与其所需的一个组关联。本例中,在组one或组two执行之前,必须首先将init()方法配置为运行。

public class SimpleGroupedTwoTest {
 private String fixture;
 /**
  * @testng.before-class = "true"  \
  *    groups = "one, two"
  */
 private void init(){
  this.fixture = "Hello World";
 }
 /**
  * @testng.test groups="one, two"
  */
 public void verifyEqualityAgain(){
  Assert.assertEquals(this.fixture, "Hello World");
 }    
 /**
  * @testng.test groups="two"
  */
 public void verifySameAgain(){
  String value = this.fixture;
  Assert.assertSame(value, this.fixture);
 }
}

  TestNG支持以多种方式运行所需组,从通过TestNG Eclipse插件指定这些组一直到在TestNG Ant任务中列举它们。

测试分类简介

  要验证正在工作的软件,最简单的方法之一就是执行一次构建(即编译源代码并执行测试);因此,长时间运行的构建是降低开发人员生产力的一项因素,这一点也不令人吃惊。不得不等待那超长的构建过程完成,几乎没有什么比这更恼人的了。可与之相提并论的就是编码过程中遇到意外的蓝屏和重启,但我们至少能够很容易地对时间较长的构建做点什么。

  长时间构建的原因几乎总是测试执行这个步骤(除非是有数百万的.java 文件)。此外,如果存在大量的设置步骤,例如配置数据库或是部署一个 EAR 文件,执行一个测试套件的总时间倾向于变长。所以,精心设计一个测试分类策略并按照规定的时间间隔运行分类有利于获得可管理的构建持续时间。

  然而,分类测试要求我们定义具体的分类,即细化单元测试。单元测试就像一张三层饼的一片,另两片则是组件测试和系统测试。下一节分析了开发人员通常会编写的不同类型的测试,诸如单元测试、组件测试和系统测试。随后,它们将在TestNG中执行,并集成到一个Ant 构建脚本里。

单元测试定义

  单元测试验证独立对象的行为;然而,由于类的耦合,单元测试也能验证相关对象的行为。例如,下面的单元测试验证了对象身份,它是在TestNG中实现的,只关注一个类型:PartOfSpeechEnum。

/**
 * @testng.test 
 */
public class PartOfSpeechEnumTest {
    
 public void verifyNotEquals() throws Exception{
  assert PartOfSpeechEnum.ADJECTIVE != 
     PartOfSpeechEnum.NOUN: "NOUN == ADJECTIVE?"; 
 }

 public void verifyEquals() throws Exception{
  assert PartOfSpeechEnum.ADJECTIVE == 
    PartOfSpeechEnum.ADJECTIVE "ADJECTIVE != ADJECTIVE";
 }
}

  有些时候,一个单元测试会验证多个对象的行为。然而,这些对象很少有其他的外部依赖项。例如,下面的测试验证了两个对象:Dependency和DependencyFinder。

//imports removed...

public class DependencyFinderTest {
 private DependencyFinder finder;
 /**
  * @testng.test
  */
 public void verifyFindDependencies() throws Exception{
  String targetClss  = "test.com.sedona.frmwrk.dep.Finder";
  Filter[] filtr = new Filter[] { 
    new RegexPackageFilter("java|org")};
  Dependency[] deps = 
   finder.findDependencies(targetClss, filtr);
  Assert.assertNotNull(deps, "deps was null");
  Assert.assertEquals(deps.length, 5, "should be 5 large");
 }
 /**
  * @testng.before-class = "true" 
  */
 protected void init() throws Exception {        
  this.finder = new DependencyFinder();
 }
}

  要牢记的一个要点就是:单元测试不依靠外部依赖项,如数据库。数据库会增加设置和运行测试的时间。单元测试没有配置成本(就时间来度量),运行它的资源消耗可忽略不计。

  单元测试运行很快,所以只要运行了一次构建,就应该运行单元测试,包括在持续集成环境中也是如此。在持续集成环境中,如果源储存库(如CVS)发生变更,通常就要运行构建。

组件测试

  组件测试有几个别名,如子系统测试或集成测试。不管用哪个术语,这样的测试验证了应用程序的若干部分,甚至还可能需要一个完全安装的系统或一组更有限的外部依赖项,如数据库、文件系统、或网络端点。实质上,这些测试验证了不同组件交互以产生预期组合行为的过程。

  典型的组件测试需要一个种子数据库(seeded database);此外,测试本身可能要跨架构边界来验证行为。由于组件测试要处理大量的代码,所以实现了更广泛的代码覆盖范围;但运行此类测试要比运行单元测试占用更长时间。

  因为组件测试有相关成本——依赖项必须就位并被配置好,所以不该在每次执行构建时运行,而应以规定的时间间隔运行。记住,这些测试本身可能只需几秒钟,但更多的组件测试被添加到套件中时,整个测试时间就增加了,而且往往增加的非常快。

  例如,下面的组件测试用DbUnit 播种一个底层数据库。这一测试用例中,设置本身所用的时间比大多数单元测试的运行时间都要长。

//imports removed...

public class WordDAOImplTest {
 private WordDAO dao = null; 
 /**
  * @testng.before-method = "true" 
  */
 private void setUp() throws Exception {                
  final ApplicationContext context = 
    new ClassPathXmlApplicationContext(
      "spring-config.xml");        
  this.dao = (WordDAO) context.getBean("wordDAO");
 }    
 /**  
  * @testng.before-class = "true" 
  */
 public void oneTimeSetUp() throws Exception{
  final IDatabaseConnection conn = 
    this.getConnection();
  final IDataSet data = this.getDataSet();        
  try{
   DatabaseOperation.CLEAN_INSERT.execute(conn, data);
  }finally{
   conn.close();
  }
 }  
 /**
  * @testng.test
  */
 public void createWord() {       
  final IWord word = new Word();
  word.setSpelling("pabulum");
  word.setPartOfSpeech(
    PartOfSpeechEnum.NOUN.getPartOfSpeech());         
  final IDefinition defOne = new Definition();
  defOne.setDefinition("food");
  defOne.setWord(word);
  final Set defs = new HashSet();
  defs.add(defOne);
  word.setDefinitions(defs);               
  try{
   this.dao.createWord(word);
  }catch(CreateException e){
   Assert.fail("CreateException was thrown");
  }           
 }
 
 private IDataSet getDataSet() 
   throws IOException, DataSetException {
    return new FlatXmlDataSet(
     new File("test/conf/words-seed.xml"));
 }
 
 private IDatabaseConnection getConnection() 
   throws ClassNotFoundException, SQLException {
    final Class driverClass = 
      Class.forName("org.gjt.mm.mysql.Driver");
    final Connection jdbcConnection = DriverManager.
     getConnection("jdbc:mysql://localhost/words", 
      "words", "words");               
  return new DatabaseConnection(jdbcConnection);
}
}

  但是,组件测试不总是依靠数据库。例如依靠文件系统创建一个耦合,这会增加配置的复杂性,在某些情况下,也会增加所需时间。举个例子,下面的组件测试使用XMLUnit 验证所生成的XML。注意这个测试依靠文件系统路径来比较两个XML文档。

//imports removed...

public class BatchDepXMLReportValidationTest {
 /**
  * @testng.before-class = "true" 
  */
 protected void configure() throws Exception {        
  XMLUnit.setControlParser(
   "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
  XMLUnit.setTestParser(
   "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
  XMLUnit.setSAXParserFactory(
   "org.apache.xerces.jaxp.SAXParserFactoryImpl");
  XMLUnit.setIgnoreWhitespace(true);   
 }

 private Filter[] getFilters(){            
  return new Filter[] {
   new RegexPackageFilter("java|org"),
   new SimplePackageFilter("junit.")
  };
 }

 private Dependency[] getDependencies(){
  return new Dependency[] {
   new Dependency("com.vanward.resource.XMLizable"),
   new Dependency("com.vanward.xml.Element")
  };        
 }
 /**
  * @testng.test     
  */
 public void assertToXML() throws Exception{
  BatchDependencyXMLReport report = 
   new BatchDependencyXMLReport(
    new Date(9000000), this.getFilters());

  report.addTargetAndDependencies(
   "com.vanward.test.MyTest", this.getDependencies());
  report.addTargetAndDependencies(
   "com.xom.xml.Test", this.getDependencies());

  Diff diff = new Diff(new FileReader(
   new File("./test/conf/report-control.xml")),
    new StringReader(report.toXML()));

  Assert.assertTrue(
   diff.identical(),"XML was not identical");        
 }
}

  虽然不应在每次运行构建时执行组件测试,但在将代码提交到储存库之前运行组件测试却是一个好主意。在持续集成环境中,时常运行它很可能是个好主意,比如每小时一次。

系统测试

  系统测试处理一个完整的软件应用程序,验证外部接口,如Web页面、Web 服务端点、GUI、按照设计端到端地完成工作。因为系统测试处理的是整个系统,所以往往在开发的后期创建。除增加了设置和配置时间之外,系统测试还倾向于具有更长的执行时间。

  例如,以下测试使用 jWebUnit 测试一个Web站点的登陆功能。请注意,这个测试中做了许多假设,如可用URL、实际上拥有有效账户的“tst”用户、未提及交易历史记录等。这些隐含的依赖项通常需要在测试运行前完成一个配置步骤。

public class LoginTest  {
 private WebTester tester;
 /**
  * @testng.before-class = "true"    
  */
 protected void init() throws Exception {        
  this.tester = new WebTester();
  this.tester.getTestContext().
  setBaseUrl("http://stocktrader.com");
 }
 /**
  * @testng.test
  */
 public void verifyLogIn() {
  this.tester.beginAt("/");        
  this.tester.setFormElement("user", "tst");
  this.tester.setFormElement("psswd", "t@t");
  this.tester.submit();        
  this.tester.assertTextPresent("Logged in as tst");
 }
 /**
  * @testng.test dependsOnMethods = "verifyLogIn"
  */
 public void verifyAccountInfo() {
  this.tester.clickLinkWithText("History", 0);        
  this.tester.assertTextPresent("$89.00, sold");
 }
}

  开发人员应当以需求为基础,在一个持续集成环境中本地运行这些测试。每晚执行测试是个不错的策略(如果测试能够实现自动运行)。更频繁地运行测试可能消耗大量系统资源,特别是在较大的环境中。但有了合理的硬件规划和更加完善的自动化,团队就能更频繁地运行这些测试。

实现TestNG分类

  将TestNG测试分成三类就像使用上面所讲的group注释一样简单。通常不会有一个具有跨不同测试粒度的方法的测试类,所以在类的级别上,可以有效地应用标记。

  例如,下面的类被标记为属于unit测试组。请注意,HierarchyBuilderTest是怎样依靠HierarchyBuilder类型来验证Hierarchy类的行为的。由于这一关系结束于不依赖文件系统或数据库的HierarchyBuilder,因此实际上可以把它看作单元测试:

import org.testng.Assert;

/**
 * @testng.test groups="unit"
 */
public class HierarchyBuilderTest {
 private Hierarchy hier;
 /**
  * @testng.before-class = "true" groups="unit"
  */
 private void init() throws Exception{
  this.hier = 
    HierarchyBuilder.buildHierarchy(Vector.class);
 }
 
 public void validateIsntNull() throws Exception{        
  Assert.assertNotNull(this.hier, 
    "should be something!");
 }
 /**
  * @testng.test dependsOnMethods="validateIsntNull"     
  */
 public void validateValues() throws Exception{        
  Assert.assertEquals(
    this.hier.getHierarchyClassNames().length, 
    2, "should be 2");        
 }
}

  类似地,系统测试中单独的方法可以用system组标识来标记,示范如下:

public class LoginTest  {
 private WebTester tester;
 /**
  * @testng.before-class = "true" groups="system"
  */
 protected void init() throws Exception {        
  this.tester = new WebTester();
  this.tester.getTestContext().
  setBaseUrl("http://acme.com:8080/ppo/");
 }
 /**
  * @testng.test groups="system" 
  */
 public void verifyView() {
  this.tester.beginAt("/");        
  this.tester.setFormElement("isbn", "900930390");
  this.tester.submit();        
  this.tester.assertTextPresent("Book in stock");
 } 
}

运行分类测试

  将代码签入内容管理系统之前,通过构建或像IDE那样的环境进行本地测试是极为重要的。通过TestNG Eclipse插件运行分类测试非常简单。如图1所示,通过在TestNG Create, manage, and run configurations 对话框中选择组选项,可用组的列表将出现,它带有复选框,这使选择变得更轻松。选好所需的一个或多个组以后,单击Run按钮,然后就看着绿色进度条一直向前!

测试分类技术与Testng图-1

   图1,TestNG的Eclipse集成(单击将图片放大至完整尺寸)

  通过构建,运行分类TestNG 测试将转变成为各组定义恰当的 Ant 目标。例如,为了运行属于组件组的所有测试,用指定的component组定义TestNG Ant 任务。

<target name="testng-component" 
   depends="testng-init">
 <mkdir dir="${testng.output.dir.comp}"/>

 <testng groups="component"
    outputDir="${testng.output.dir.comp}"
    sourceDir="${testng.source.dir}"
    classpath="${testclassesdir};${classesdir}">
   <classfileset dir="${testng.source.dir}">                
     <include name="**/*Test.java"/>
   </classfileset>
   <classpath>
     <path refid="build.classpath"/>
   </classpath>
 </testng>

  因此,采用这一策略,至少可以创建4个目标。其中3个分别对应单元、组件和系统测试,最后一个目标则能运行所有这3种测试。

结束语

  TestNG使测试分类变得相当容易,这很可能是TestNG的最激动人心的优点之一。此外,TestNG的group注释还有助于把测试放到其他分类中,如成批测试、验收测试,甚至是性能测试。事实上,这一特性似乎已对最新版本的JUnit产生了影响,JUnit也在规划支持测试组!

  下一次,当您摸索着编写新的测试用例时,考虑一下它对构建执行次数的长期影响。构建的可伸缩性以一个以各种频率运行的测试分类策略开始,有效的测试分类则以TestNG开始。

参考资料

  • TestNG.org ——TestNG之家
  • DbUnit ——使数据库依赖项管理(Database Dependency Management)变得更加轻松
  • XMLUnit ——XML测试用例速成
  • jWebUnit ——轻松完成系统测试
 作者简介
Andy Glover 是Stelligent公司的总裁。Stelligent公司的总部位于弗吉尼亚州的雷斯顿。Andy在 www.thediscoblog.com上积极地撰写关于软件质量的博客文章。

文章录入:root    责任编辑:root 
  • 上一篇文章:

  • 下一篇文章: 没有了
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
     
    最新热点 最新推荐 相关文章
    · 常用软件测试工具简介!
    · Test Director 8.0安装配置!
    · Parasoft Jtest 7.5.59 使用手册
    · Test Director 8.0项目数据库维护和移植!
    · 使用CQ TestManager 运行并分析软件测试
    · TestDirector 8.2 SP2的下载与安装
    · Mercury WinRunner 软件试用 下载
    · Winrunner TSL命令简介
    · Mercury TestDirector 软件试用 下载
    · JTest7.59定义——JAVA 编码标准规范
    · 初识IBM Rational RobotJ
    · 使用 Rational Robot 实现自动化测试
    · 使用CQ TestManager 运行并分析软件测试
    · 常用软件测试工具简介!
    · 使用Functional Tester 6.1 进行功能测试
    · 使用Functional Tester 6.1 进行功能测试
    · 使用Functional Tester 6.1 进行功能测试
    · 使用Functional Tester 6.1 进行功能测试
    · 使用 EclEmma 进行覆盖测试
    · JTest7.59定义——JAVA 编码标准规范
    Eclipse 3.1 中使用TestNG:
    TestNG 使 Java 单元测试轻而
    使用 TestNG 的新特性管理实
    追求代码质量: 使用 TestNG-
     
     
     
    ======> [CNTester联盟群]交流群:34446273/21968356/64461572 白盒群:18400216 自动化群:2706508 性能群:4498858 外包群:59649884 管理群(需有管理经验):64442523
    | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 网站公告 | 管理登录 | 

    Copyright@2007 by CNTester.com 中国测试员网站 桂ICP备07005590

    本站为开源免费网站,非商业赢利性组织。本站文章部分从网络搜索获取,如果您认为某些侵犯了您的权益,麻烦您联系本站,我们会尽快删除相关内容,同时也希望您的谅解,我们的初衷是为了让更多人去学习这方面的知识,让行业有更好的发展。

    联系电话: 15021358905