green's profile我的共享空间PhotosBlogLists Tools Help

Blog


    12/18/2006

    锁定目标

    正视现状,接受现实!
    锁定目标,冷静沉着,步步为营,持之以恒!
    12/7/2006

    追求代码质量: JUnit 4 与 TestNG 的对比

    追求代码质量: JUnit 4 与 TestNG 的对比

    为什么 TestNG 框架依然是大规模测试的较好选择?

    developerWorks
    文档选项

    未显示需要 JavaScript 的文档选项

    将此页作为电子邮件发送

    将此页作为电子邮件发送

    讨论


    拓展 Tomcat 应用

    下载 IBM 开源 J2EE 应用服务器 WAS CE 新版本 V1.1


    级别: 初级

    Andrew Glover (aglover@stelligent.com), 总裁, Stelligent Incorporated

    2006 年 9 月 18 日

    JUnit 4 具有基于注释的新框架,它包含了 TestNG 一些最优异的特性。但这是否意味着 JUnit 4 已经淘汰了 TestNG?Andrew Glover 探讨了这两种框架各自的独特之处,并阐述了 TestNG 独有的三种高级测试特性。

    经过长时间积极的开发之后,JUnit 4.0 于今年年初发布了。JUnit 框架的某些最有趣的更改 —— 特别是对于本专栏的读者来说 —— 正是通过巧妙地使用注释实现的。除外观和风格方面的显著改进外,新框架的特性使测试用例的编制从结构规则中解放出来。使原来僵化的 fixture 模型更为灵活,有利于采取可配置程度更高的方法。因此,JUnit 框架不再强求把每一项测试工作定义为一个名称以 test 开始的方法,并且现在可以只运行一次 fixture,而不是每次测试都需要运行一次。

    虽然这些改变令人欣慰,但 JUnit 4 并不是第一个提供基于注释的灵活模型的 Java™ 测试框架。在修改 JUnit 之前很久,TestNG 就已建立为一个基于注释的框架。

    事实上,是 TestNG 在 Java 编程中率先 实现了利用注释进行测试,这使它成为 JUnit 的有力竞争对手。然而,自从 JUnit 4 发布后,很多开发者质疑:二者之间还有什么差别吗?在本月的专栏中,我将讨论 TestNG 不同于 JUnit 4 的一些特性,并提议采用一些方法,使得这两个框架能继续互相补充,而不是互相竞争。

    您知道吗?

    在 Ant 中运行 JUnit 4 测试比预计的要难得多。事实上,一些团队已发现,惟一的解决方法是升级到 Ant 1.7。

    表面上的相似

    JUnit 4 和 TestNG 有一些共同的重要特性。这两个框架都让测试工作简单得令人吃惊(和愉快),给测试工作带来了便利。二者也都拥有活跃的社区,为主动开发提供支持,同时生成丰富的文档。

    提高代码质量

    要找到您最迫切问题的答案,请不要错过 Andrew 的 论坛

    两个框架的不同在于核心设计。JUnit 一直 是一个单元测试框架,也就是说,其构建目的是促进单个对象的测试,它确实能够极其有效地完成此类任务。而 TestNG 则是用来解决更高 级别的测试问题,因此,它具有 JUnit 中所没有的一些特性。

    一个简单的测试用例

    初看起来,JUnit 4 和 TestNG 中实现的测试非常相似。为了更好地理解我的意思,请看一下清单 1 中的代码。这是一个 JUnit 4 测试,它有一个 macro-fixture(即仅在所有测试运行前调用一次的 fixture),这个 macro-fixture 由 @BeforeClass 属性表示:


    清单 1. 一个简单的 JUnit 4 测试用例
    package test.com.acme.dona.dep;
    
    import static org.junit.Assert.assertEquals;
    import static org.junit.Assert.assertNotNull;
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    public class DependencyFinderTest {
     private static DependencyFinder finder;
    
     @BeforeClass
     public static void init() throws Exception {
      finder = new DependencyFinder();
     }
    
     @Test
     public void verifyDependencies() 
      throws Exception {
       String targetClss = 
         "test.com.acme.dona.dep.DependencyFind";
    
       Filter[] filtr = new Filter[] { 
          new RegexPackageFilter("java|junit|org")};
    
       Dependency[] deps = 
          finder.findDependencies(targetClss, filtr);
    
       assertNotNull("deps was null", deps);
       assertEquals("should be 5 large", 5, deps.length);	
     }
    }
    

    JUnit 用户会立即注意到:这个类中没有了以前版本的 JUnit 中所要求的一些语法成分。这个类没有 setUp() 方法,也不对 TestCase 类进行扩展,甚至也没有哪个方法的名称以 test 开始。这个类还利用了 Java 5 的一些特性,例如静态导入,很明显地,它还使用了注释。

    更多的灵活性

    在清单 2 中,您可以看到同一个 测试项目。不过这次是用 TestNG 实现的。这里的代码跟清单 1 中的测试代码有个微妙的差别。发现了吗?


    清单 2. 一个 TestNG 测试用例
    package test.com.acme.dona.dep;
    
    import static org.testng.Assert.assertEquals;
    import static org.testng.Assert.assertNotNull;
    import org.testng.annotations.BeforeClass;
    import org.testng.annotations.Configuration;
    import org.testng.annotations.Test;
    
    public class DependencyFinderTest {
     private DependencyFinder finder;
    
     @BeforeClass
     private void init(){
      this.finder = new DependencyFinder();
     }
    
     @Test
     public void verifyDependencies() 
      throws Exception {
       String targetClss = 
         "test.com.acme.dona.dep.DependencyFind";
    
       Filter[] filtr = new Filter[] { 
          new RegexPackageFilter("java|junit|org")};
    
       Dependency[] deps = 
          finder.findDependencies(targetClss, filtr);
       
       assertNotNull(deps, "deps was null" );
       assertEquals(5, deps.length, "should be 5 large");		
     }
    }
    

    显然,这两个清单很相似。不过,如果仔细看,您会发现 TestNG 的编码规则比 JUnit 4 更灵活。清单 1 里,在 JUnit 中我必须把 @BeforeClass 修饰的方法声明为 static,这又要求我把 fixture,即 finder 声明为 static。我还必须把 init() 声明为 public。看看清单 2,您就会发现不同。这里不再需要那些规则了。我的 init() 方法既不是 static,也不是 public

    从最初起,TestNG 的灵活性就是其主要优势之一,但这并非它惟一的卖点。TestNG 还提供了 JUnit 4 所不具备的其他一些特性。





    回页首


    依赖性测试

    JUnit 框架想达到的一个目标就是测试隔离。它的缺点是:人们很难确定测试用例执行的顺序,而这对于任何类型的依赖性测试都非常重要。开发者们使用了多种技术来解决这个问题,例如,按字母顺序指定测试用例,或是更多地依靠 fixture 来适当地解决问题。

    如果测试成功,这些解决方法都没什么问题。但是,如果测试不成功,就会产生一个很麻烦的后果:所有 后续的依赖测试也会失败。在某些情况下,这会使大型测试套件报告出许多不必要的错误。例如,假设有一个测试套件测试一个需要登录的 Web 应用程序。您可以创建一个有依赖关系的方法,通过登录到这个应用程序来创建整个测试套件,从而避免 JUnit 的隔离机制。这种解决方法不错,但是如果登录失败,即使登录该应用程序后的其他功能都正常工作,整个测试套件依然会全部失败!

    跳过,而不是标为失败

    与 JUnit 不同,TestNG 利用 Test 注释的 dependsOnMethods 属性来应对测试的依赖性问题。有了这个便利的特性,就可以轻松指定依赖方法。例如,前面所说的登录将在某个方法之前 运行。此外,如果依赖方法失败,它将被跳过,而不是标记为失败。


    清单 3. 使用 TestNG 进行依赖性测试
    import net.sourceforge.jwebunit.WebTester;
    
    public class AccountHistoryTest  {
     private WebTester tester;
    
     @BeforeClass
     protected void init() throws Exception {
      this.tester = new WebTester();
      this.tester.getTestContext().
       setBaseUrl("http://div.acme.com:8185/ceg/");
     }
    
     @Test
     public void verifyLogIn() {
      this.tester.beginAt("/");		
      this.tester.setFormElement("username", "admin");
      this.tester.setFormElement("password", "admin");
      this.tester.submit();		
      this.tester.assertTextPresent("Logged in as admin");
     }
    
     @Test (dependsOnMethods = {"verifyLogIn"})
     public void verifyAccountInfo() {
      this.tester.clickLinkWithText("History", 0);		
      this.tester.assertTextPresent("GTG Data Feed");
     }
    }
    

    在清单 3 中定义了两个测试:一个验证登录,另一个验证账户信息。请注意,通过使用 Test 注释的 dependsOnMethods = {"verifyLogIn"} 子句,verifyAccountInfo 测试指定了它依赖 verifyLogIn() 方法。

    通过 TestNG 的 Eclipse 插件(例如)运行该测试时,如果 verifyLogIn 测试失败,TestNG 将直接跳过 verifyAccountInfo 测试,请参见图 1:


    图 1. 在 TestNG 中跳过的测试

    对于大型测试套件,TestNG 这种不标记为失败,而只是跳过的处理方法可以减轻很多压力。您的团队可以集中精力查找为什么百分之五十的测试套件被跳过,而不是去找百分之五十的测试套件失败的原因!更有利的是,TestNG 采取了只重新运行失败测试的机制,这使它的依赖性测试设置更为完善。





    回页首


    失败和重运行

    在大型测试套件中,这种重新运行失败测试的能力显得尤为方便。这是 TestNG 独有的一个特性。在 JUnit 4 中,如果测试套件包括 1000 项测试,其中 3 项失败,很可能就会迫使您重新运行整个测试套件(修改错误以后)。不用说,这样的工作可能会耗费几个小时。

    一旦 TestNG 中出现失败,它就会创建一个 XML 配置文件,对失败的测试加以说明。如果利用这个文件执行 TestNG 运行程序,TestNG 就 运行失败的测试。所以,在前面的例子里,您只需重新运行那三个失败的测试,而不是整个测试套件。

    实际上,您可以通过清单 2 中的 Web 测试的例子自己看到这点。verifyLogIn() 方法失败时,TestNG 自动创建一个 testng-failed.xml 文件。该文件将成为如清单 4 所示的替代性测试套件:


    清单 4. 失败测试的 XML 文件
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
    <suite thread-count="5" verbose="1" name="Failed suite [HistoryTesting]" 
           parallel="false" annotations="JDK5">
     <test name="test.com.acme.ceg.AccountHistoryTest(failed)" junit="false">
      <classes>
       <class name="test.com.acme.ceg.AccountHistoryTest">
        <methods>
         <include name="verifyLogIn"/>
        </methods>
       </class>
      </classes>
     </test>
    </suite>
    

    运行小的测试套件时,这个特性似乎没什么大不了。但是如果您的测试套件规模较大,您很快就会体会到它的好处。





    回页首


    参数化测试

    TestNG 中另一个有趣的特性是参数化测试。在 JUnit 中,如果您想改变某个受测方法的参数组,就只能给每个 不同的参数组编写一个测试用例。多数情况下,这不会带来太多麻烦。然而,我们有时会碰到一些情况,对其中的业务逻辑,需要运行的测试数目变化范围很大。

    在这样的情况下,使用 JUnit 的测试人员往往会转而使用 FIT 这样的框架,因为这样就可以用表格数据驱动测试。但是 TestNG 提供了开箱即用的类似特性。通过在 TestNG 的 XML 配置文件中放入参数化数据,就可以对不同的数据集重用同一个测试用例,甚至有可能会得到不同的结果。这种技术完美地避免了只能 假定一切正常的测试,或是没有对边界进行有效验证的情况。

    在清单 5 中,我用 Java 1.4 定义了一个 TestNG 测试,该测试可接收两个参数:classnamesize。这两个参数可以验证某个类的层次结构(也就是说,如果传入 java.util.Vector,则 HierarchyBuilder 所构建的 Hierarchy 的值将为 2 )。


    清单 5. 一个 TestNG 参数化测试
    package test.com.acme.da;
    
    import com.acme.da.hierarchy.Hierarchy;
    import com.acme.da.hierarchy.HierarchyBuilder;
    
    public class HierarchyTest {
     /**
      * @testng.test
      * @testng.parameters value="class_name, size"
      */
     public void assertValues(String classname, int size) throws Exception{
      Hierarchy hier = HierarchyBuilder.buildHierarchy(classname);
      assert hier.getHierarchyClassNames().length == size: "didn't equal!";
     }
    }
    

    清单 5 列出了一个泛型测试,它可以采用不同的数据反复重用。请花点时间思考一下这个问题。如果有 10 个不同的参数组合需要在 JUnit 中测试,您只能写 10 个测试用例。每个测试用例完成的任务基本是相同的,只是受测方法的参数有所改变。但是,如果使用参数化测试,就可以只定义一个 测试用例,然后,(举例来说)把所需的参数模式加到 TestNG 的测试套件文件中。清单 6 中展示了这中方法:


    清单 6. 一个 TestNG 参数化测试套件文件
    <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd">
    <suite name="Deckt-10">
     <test name="Deckt-10-test">
    
      <parameter name="class_name" value="java.util.Vector"/>
      <parameter name="size" value="2"/> 	
    
      <classes>  		
       <class name="test.com.acme.da.HierarchyTest"/>
      </classes>
     </test>  
    </suite>
    

    清单 6 中的 TestNG 测试套件文件只对该测试定义了一个参数组(class_namejava.util.Vector,且 size 等于 2),但却具有无限的可能。这样做的一个额外的好处是:将测试数据移动到 XML 文件的无代码工件就意味着非程序员也可以指定数据。





    回页首


    高级参数化测试

    尽管从一个 XML 文件中抽取数据会很方便,但偶尔会有些测试需要有复杂类型,这些类型无法用 String 或原语值来表示。TestNG 可以通过它的 @DataProvider 注释处理这样的情况。@DataProvider 注释可以方便地把复杂参数类型映射到某个测试方法。例如,清单 7 中的 verifyHierarchy 测试中,我采用了重载的 buildHierarchy 方法,它可接收一个 Class 类型的数据, 它断言(asserting)HierarchygetHierarchyClassNames() 方法应该返回一个适当的字符串数组:


    清单 7. TestNG 中的 DataProvider 用法
    package test.com.acme.da.ng;
    
    import java.util.Vector;
    
    import static org.testng.Assert.assertEquals;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.Test;
    
    import com.acme.da.hierarchy.Hierarchy;
    import com.acme.da.hierarchy.HierarchyBuilder;
    
    public class HierarchyTest {
    
     @DataProvider(name = "class-hierarchies")
     public Object[][] dataValues(){
      return new Object[][]{
       {Vector.class, new String[] {"java.util.AbstractList", 
         "java.util.AbstractCollection"}},
       {String.class, new String[] {}}
      };
     }
    
     @Test(dataProvider = "class-hierarchies")
     public void verifyHierarchy(Class clzz, String[] names) 
      throws Exception{
        Hierarchy hier = HierarchyBuilder.buildHierarchy(clzz);
        assertEquals(hier.getHierarchyClassNames(), names, 
    	  "values were not equal");		
     }
    }
    

    dataValues() 方法通过一个多维数组提供与 verifyHierarchy 测试方法的参数值匹配的数据值。TestNG 遍历这些数据值,并根据数据值调用了两次 verifyHierarchy。在第一次调用时,Class 参数被设置为 Vector.class ,而 String 数组参数容纳 “java.util.AbstractList ” 和 “ java.util.AbstractCollection ” 这两个 String 类型的数据。这样挺方便吧?





    回页首


    为什么只选择其一?

    我已经探讨了对我而言,TestNG 的一些独有优势,但是它还有其他几个特性是 JUnit 所不具备的。例如 TestNG 中使用了测试分组,它可以根据诸如运行时间这样的特征来对测试分类。也可在 Java 1.4 中通过 javadoc 风格的注释来使用它,如 清单 5 所示。

    正如我在本文开头所说,JUnit 4 和 TestNG 在表面上是相似的。然而,设计 JUnit 的目的是为了分析代码单元,而 TestNG 的预期用途则针对高级测试。对于大型测试套件,我们不希望在某一项测试失败时就得重新运行数千项测试,TestNG 的灵活性在这里尤为有用。这两个框架都有自己的优势,您可以随意同时使用它们。





    回页首


    参考资料

    学习
    • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

    • TestNG 使 Java 单元测试轻而易举”(Filippo Diotalevi,developerWorks,2005 年 1 月):TestNG 不仅仅是性能强大、富于创新、易于扩充、使用灵活,它也展示了 Java 注释的一种有趣应用方式。

    • JUnit 4 抢先看”(Elliotte Rusty Harold,developerWorks,2005 年 9 月):软件测试人员 Elliotte Harold 兴趣盎然地试用了 JUnit 4 并详细介绍了如何在您的工作中使用这个新的框架。

    • 在 TestNG 中使用 JUnit 扩展(Andrew Glover,thediscoblog.com,2006 年 3 月):某个框架声称它是 JUnit 扩展并不表示它可以在 TestNG 中使用。

    • 使用 TestNG 的统计测试(Cedric Beust,beust.com,2006 年 2 月):使用 TestNG 进行高级测试,本文由该项目的创始人编写。

    • 重新运行失败的测试”(Andrew Glover,testearly.com,2006 年 4 月):关于如何在 TestNG 中重新运行失败测试的深入分析。

    • 追求代码质量:决心采用 FIT(Andrew Glover,developerWorks,2006 年 2 月):有利于加强商业客户和开发者沟通的集成测试框架。

    • JUnit 4 you(Fabiano Cruz,Fabiano Cruz's Blog,2006 年 6 月):关于 JUnit 4 的生态系统支持的有趣内容。

    • TestNG 测试的代码覆盖(改善代码质量论坛, 2006 年 3 月):请参与关于在 TestNG 中集成代码覆盖工具的讨论。

    • 追求代码质量 系列 (Andrew Glover,developerWorks):查看本系列从代码度量到测试框架和重构的所有文章。

    • developerWorks:查阅数百篇关于 Java 编程各个方面的文章。

    获得产品和技术

    讨论




    回页首


    关于作者

    Andrew Glover 是 Stelligent Incorporated 的总裁,该公司通过有效的开发者测试策略和连续集成技术帮助企业解决软件质量问题,使开发团队能够及早、经常性地监测代码质量。请访问 Andy 的 Blog ,查看他出版的资料列表。






    回页首

    Eclipse 3.1 中使用TestNG:基于注释的单元测试框架

    Eclipse 3.1 中使用TestNG:基于注释的单元测试框架

    developerWorks
    文档选项

    未显示需要 JavaScript 的文档选项

    将此页作为电子邮件发送

    将此页作为电子邮件发送


    拓展 Tomcat 应用

    下载 IBM 开源 J2EE 应用服务器 WAS CE 新版本 V1.1


    吴嫣 IBM

    2005 年 8 月

    这篇文章将为大家介绍TestNG这个新的测试框架的特性,以及TestNG优于Junit3.X的地方。

    TestNG(Test Next Generation),顾名思义,下一代的测试框架。它是基于J2SE5.0的注释特性的而构建的轻量级的单元测试框架结构。说起单元测试框架,大家都会自然地联想到JUnit。用过JUnit3.X的程序开发人员,都会发现JUnit在提供了强大功能的同时,也存在很多令人沮丧的地方。其中一个问题就是,JUnit3.x 在每个测试方法调用前和调用后都会调用setUp()和tearDown()的方法。如果开发人员希望在不同的测试方法中重用同一个JDBC连接或者 JNDI的Context的时候,会觉得很不方便。一般的解决这个问题的方法是使用静态方法,而这样的话,就必须小心并发控制的问题(多个线程访问共享的静态对象)。除此之外,JUnit 3.X对于多线程测试也比较麻烦,需要其他模块的支持。

    这篇文章将为大家介绍TestNG这个新的测试框架的特性,以及TestNG优于Junit3.X的地方。众所周知,Eclipse不仅仅是功能强大的Java IDE,同时也是一个开放的应用集成平台。而Eclipse3.1提供了对J2SE5.0的支持。因此,笔者将以Eclipse为运行环境,介绍 Testng的安装,使用和运行。Eclipse3.1可以从http://www.eclipse.org/downloads/index.php下载。

    关于注释

    由于TestNG是基于J2SE5.0的注释特性所构建的。因此读者在阅读本文之前,必须了解注释的一些基本概念。关于J2SE的注释特性,笔者曾经在另一篇文章中详细的介绍过,详细介绍请参考"参考资料"。这里只简单的介绍一些概念。

    注释是J2SE5.0所新提供的对于元数据的支持。程序开发人员可以在不改变原有逻辑的情况下,在源文件嵌入一些补充的信息。注释都是由 @Interface annotationName 来声明的。注释可以用来修饰类定义,方法,域变量等等。使用的时候是在修饰的对象的定义前@annotationName。注释可以包含多个属性,使用的时候为属性赋值,例如 @annotationName(prop1=value1,prop2=value2)。程序的开发人员还可以通过Java的反射特性,在运行时获得这些注释的信息。在后面的章节中,大家会看到TestNG是如何使用它所定义的注释类型的来实现测试框架的。





    回页首


    安装TestNG

    在Eclipse中安装testNG很简单。和安装其他的plugin的方法相似。首先启动Eclipse3.1,在Help-> Software Update->Find and Install, 在弹出的向导中,选择"Search New Features to Install", 点击"New Remote Site",如图1所示。在URL中输入 http://beust.com/eclipse,点击"OK"。如图2所示,点击"Finish",Eclipse会帮助你完成下面的安装。熟悉 Eclipse的读者对这个过程一定不会觉得陌生。


    图1 新建Update Site
    图1 新建Update Site

    图2 安装TestNG
    图2 安装TestNG

    安装好TestNG后,在Eclipse中单击"Window"->Show View->Other->Java->TestNG, TestNG的视图就打开了。


    图3 TestNG的视图
    图3 TestNG的视图

    注意:TestNG的视图的作用时为了现实测试结果。为了显示视图的功能,图3的视图是运行了一个测试用例后的结果。读者如果是第一次打开视图,应该是空白的。





    回页首


    一个简单的例子

    TestNG和JUnit不同,他使用注释、正则表达式和基于XML的配置文件对测试方法进行配置的。我们先来看一个简单的例子。

    1) 在Eclipse中创建一个Java的项目,com.catherine.lab.testng.demo

    2) 在Packet Explorer中,右键点击刚生成的项目,选择Properties。

    3) 在Properties属性框中,选择"Java Build Path",点击"Add External JARs…"

    4) 在文件浏览的对话框中,选择{eclipse 3.1 home directory}/plugins/com.beust.testng.eclipse_XXX/eclipse_testng.jar,以及 {eclipse 3.1 home directory}/plugins/com.beust.testng.eclipse_XXX/lib/testng-jdk14.jar/以及 testng-jdk15.jar. 点击OK

    5) 在Project中创建一个package: com.catherine.lab.testng.firstTest。在package里边创建一个类:FristTestSample.


    清单1 TestNG的第一个例子
    
    package com.catherine.lab.testng.firstTest;
    import com.beust.testng.annotations.*;
    public class FirstTestSample {
    	public FirstTestSample() {
    		super();
    	}
        @Test
        public void testPass() {
        		assert true :  "This test should pass.";
        }
        
        @Test
        public void testFail() {
        		assert false : "This test will fail";
        }	
        
        @Configuration(beforeTestClass = true)
    	public void doBeforeTests() {
    		System.out.println("invoke before test class!");
    	}
    
        @Configuration(afterTestClass = true)
    	public void doAfterTests() {
    		System.out.println("invoke after test class!");
    	}
    }
    

    6) 在Eclipse中打开Run->Run..,如图4所示。 首先在选择使用TestNG的Project,而后在选择编写了测试逻辑的Class,点击Run。测试结果就显示在TestNG的视图中了。如图5所示。


    图4 配置运行TestNG的程序
    图4 配置运行TestNG的程序

    图5 TestNG的运行结果
    图5 TestNG的运行结果

    这是一个完整的测试用例。和JUnit不同,TestNG中实现测试逻辑的类不需要继承任何父类。测试方法也无需遵循testXXX的命名规则。

    TestNG的类是大家所非常熟悉的普通的Java类,而在这个类中,所有的被@Test这个注释所修饰的方法都会被当作测试方法来运行。除了测试类之外,TestNG还需要了一个配置文件,用来配置测试过程。以下是一个简单的配置文件:testng.xml。


    清单2 testNG的配置文件
    
    <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
     <suite name="My First TestNG test">
       <test name="Hello Test!">
        <classes>
          <class name=" com.catherine.lab.testng.firstTest.FirstTestSample " />
        </classes>
      </test>
    </suite>
    

    testng.xml可以配置测试套件<suite>,类似于JUnit的TestSuite。而<test>类似于 JUnit中的TestCase。所不同的是, TestNG中的测试套件可以包括多个测试用例,一个测试用例可以包括多个测试类,而一个测试类中可以定义多个测试方法。在下面的例子中,我们将看到这个配置文件更复杂的应用。

    在图4的运行配置中,我们也可以设置一个xml文件作为配置文件,而不是直接使用测试类。其实我们使用测试类的时候,testNG也帮我们生成了一个缺省的xml文件。不相信的话,你可以切换到Resource Perspective,然后刷新Workspace,就会发现这个project里边生成了一个xml文件,而这个文件就是TestNG的缺省的配置文件。

    现在我们再回到清单1,大家在上面的程序清单中会发现,除了使用@Test这个注释以外,我们还使用了@Configuration这个注释。下面我们就来介绍@Configuration这个注释的用途。

    在注释Configuration中,定义了以下的属性:


    清单3 configuration中的属性
    
    public boolean beforeSuite() default false;
    public boolean afterSuite() default false;
    
    public boolean beforeTest() default false;
    public boolean afterTest() default false;
    
    public boolean beforeTestClass() default false;
    public boolean afterTestClass() default false;
    
    public boolean beforeTestMethod() default false;
    public boolean afterTestMethod() default false;
    

    • beforeSuite=true,所修饰的方法将在测试套件(也就是配置文件中的Suite Tag)中任何一个方法调用之前,调用一次
    • afterSuite=true,所修饰的方法将在测试套件中所有方法都调用过后,调用一次
    • beforeTest=true,在测试用例(配置文件中Test Tag)中任何一个测试方法调用之前,调用一次
    • afterTest=true, 在测试用例中任何所有方法都调用之后,调用一次
    • beforeTestClass=true,在测试类中任何测试方法调用之前,调用一次
    • afterTestClass=true,在这个测试类中所有方法都调用过后,调用一次
    • beforeTestMethod=true,在每个测试方法调用之前,调用一次
    • afterTestMethod=true,在每个测试方法调用之后,调用一次

    这个清单1中doBeforeTests()方法,在任何一个test方法调用之前被调用一次。doAfterTests,就是所有的test方法运行过了以后再调用一次。从Console输出的信息中,我们可以验证这一点:


    图6 console输出的运行信息
     图6 console输出的运行信息




    回页首


    更复杂的例子

    上一节中我们介绍了使用testNG的一个最简单的例子,这一节中我们将介绍一些关于testNG的高级应用。注释Test除了标志其修饰的方法为测试方法, 还提供了groups的属性。比如上面例子的两个方法testPass()和testFail(),我们可以给这两个方法加上group的属性。


    清单4 测试@Test的groups属性
    
         @Test(groups={"functional_test"})
        public void testPass() {
        		assert true :  "This test should pass.";
        }
            @Test(groups={"checkin_test"})
        public void testFail() {
        		assert false : "This test will fail";
        }	
       }
       

    而后打开Run->Run…,在配置文件的Runtime配置中选择Groups,然后选择你要运行的group的名字。


    图7 运行选定的测试组
    图7 运行选定的测试组

    这个时候我们从TestNG中看到测试结果,只有testPass运行了,而testFail因为不属于funcational_test这个组,因此并没有运行。


    图8 运行结果
    图8 运行结果

    和第一个例子类似,虽然我们在这里并没有显示地定义配置文件,testNG已经生成了相应的配置文件了。在Resource Perspective底下可以看到这个文件:Custom_SuiteXXXX.xml.


    清单5 自动生成的配置文件
    
    <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
     <suite name="My First TestNG test">
       <test name="Hello Test!">
    <groups>
        <run>
          <include name="functional_test"/>
         </run>
      </groups>
    
        <classes>
          <class name=" com.catherine.lab.testng.firstTest.FirstTestSample " />
        </classes>
      </test>
    </suite>
    

    除了groups属性以外,注释Test还支持属性dependsOnMethods和属性dependsOnGroups. 这两个属性主要用于规定测试方法的执行顺序。

    TestNG并不保证按照定义的顺序执行测试方法。如果这些测试方法之间有依赖关系的话,那么我们就可以使用dependsOnXXXX的属性。我们还是看第一个例子,现在我们在这个例子里边增加了一个方法:

    setupEnvforPass()。我们希望setupEnvforPass()方法在testPass方法前执行,我们修改了testPass的test注释。如清单6所示:


    清单6 测试@Test的属性dependsOnMethods
    
        @Test(groups={"functional_test"},
    dependsOnMethods = { "setEnvForPass" })
        public void testPass() {
        		assert true :  "This test should pass.";
        }
            @Test(groups={"checkin_test"})
        public void testFail() {
        		assert false : "This test will fail";
    }	
    @Test(groups = {"init"})
    public void setEnvForPass(){
       assert true: "This is dependent method"
    }
       }
      

    运行配置和配置文件都不需要改动,现在我们来运行这个例子,测试结果如图9所示。大家可以看到,虽然我们在testPass方法之后定义了, setEnvForPass方法,但是由于我们将setEnvForPass定义为testPass的以来方法,setEnvForPass在 testPass前执行了。

    同样,我们可以定义dependsOnGroups的属性,这样只有Groups中所有的方法都被执行完,这个方法才会被执行。注意:如果 depensOnGroups中制定的group在配置文件中被excluded了,那么这个方法会依然被执行。但是如果指定的group在配置文件中被 include了,而group中的方法有错误的话,那么这个方法会被skip,不会被执行。


    图9 运行结果
    图9 运行结果

    下面我们要介绍一个新的注释类型: @Parameter。

    TestNG的测试方法可以带有参数,参数可以通过@Parameter来声明,具体的参数值在testng.xml中定义。这是testng的一个很优越的特性。我们还是在以前的例子上的基础上来验证这个特性。我们为setEnvForPass这个方法定义一个参数,target_server,并且在测试方法中打印这个参数。


    清单7 测试注释Parameter
    
    @Parameters({ "target_server" })
    @Test(groups = {"init"})
    public void setEnvForPass(String targetServer){
       assert true: "This is dependent method";
    System.out.println(targetServer);
    }
       }
    

    Target_server的值在testng.xml中定义。在TestNG的运行时配置中选择Suite,然后Browse清单8中定义好的的 testng.xml。运行TestNG,我们从Console的运行结果中看到,target_server的值被打印出来了。


    清单8 自定义的配置文件
    
    <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd">
    <suite name="Custom suite">
      <parameter name="target_server"  value="127.0.0.1"/>
      <test verbose="6" name="Test for 1 classes" annotations="1.5">
        <groups>
          <run>
            <include name="functional_test"/>
          </run>
        </groups>
        <classes>
          <class name="com.catherine.lab.testng.firstTest.FirstTestSample">
          </class>
        </classes>
      </test>
    </suite>
    

    测试方法的参数可以是任意多个,只要你通过配置文件传入了正确的参数,那么测试方法中就可以使用这些参数了。不过需要注意的是,参数是有作用域的,比如参数可以在配置文件的suite和test之后定义,而如果两个参数的名称一样,test中定义的参数值有较高的优先级。

    testNG可以从多个线程中运行测试方法,只需要将配置文件中suite的parallel属性设为true。线程的数目在thread-count中设置。如果两个方法有依赖关系,那么他们将在一个线程中运行,除此之外,都可以在多个线程中并发的运行。

    <suite name="My suite" parallel="true" thread-count="10">

    除了以上介绍的特性以外,(请参阅"参考资料")

    • TestNG提供了注释Factory,用来动态生成测试方法的参数。
    • TestNG还提供了AntTask <testng>,可以从Ant脚本中调用testNG。
    • TestNG的可以调用JUnit的test cases。只需要配置文件中声明
      <test name="Test1" junit="true">
      <classes ..>
    • TestNG还可以从程序中调用:
      TestNG testng = new TestNG();
      testng.setTestClasses(Class[] {});
      testng.run();

    除了TestNG之外… 从上面的例子可以看出,TestNG这个单元测试框架的功能是很强大的,而且简单易学。开发者只需要使用TestNG所提供的注释和正确的配置文件,可以轻松地完成复杂的测试用例。

    除了TestNG之外,JTiger也是一种基于J2SE5.0的单元测试框架,其中应用了大量J2SE5.0的新特性,比如注释和静态 Import。和TestNG类似, JTiger提供了大量内建的注释类型, 比如JTiger也使用注释@Test标明测试方法,使用注释@Category表示这个测试方法属于那一类,类似于TestNG的@Test的groups属性。和TestNG不同的是,JTiger并没有使外部的配置文件。

    总之,TestNG和JTiger都解决了JUnit3.x中存在的问题,提供了大量优于JUnit3.x的特性。而JUnit也并没有就此止步,即将发布的JUnit4.0有了根本性的变化,JUnit4.0也将变成基于注释的测试系统。同样也将提供大量的内建注释类型:比如@Test, @Before, @After等等。这些引入的注释类型,使得JUnit克服了以前的问题,拥有了新的活力。Justine Lee在他的文章中,详细地比较了这三种测试框架,请参阅"参考资料"。

    选用何种测试框架,取决于很多的因素。虽然JUnit具有众多的拥簇者,但是TestNG和JTiger的崛起也不可小觑的。应该说,TestNG 是建立在JUnit3.x之上的,吸取了JUnit的优点,同时也摈弃和改正了JUnit的缺点。笔者曾经在Eclipse中使用过JUnit3.x和 TestNG,个人认为TestNG使用起来比JUnit3.x要更为方便。但是JUnit提供测试插件(plug-in)的功能,TestNG目前并没有提供这种功能。不过我们有理由相信,在不久的将来TestNG会对于eclipse插件提供更为丰富的支持。本文通过对TestNG的介绍,希望能够为大家在选择测试框架的时候提供一个新的选择。





    回页首


    参考资料





    回页首


    关于作者






    回页首
    12/5/2006

    要学会站在对方的角度看问题

          今天坐地铁到王府井,准备换乘公交线路,出了1号线车后向右上电梯,后找指示牌,指示牌说B出口有我要换乘的公交车,于是向反方向前进,看到前面有两个出口,其中一个写着A,另外一个没看到,但是看到上面写着“此处可去王府井步行街”,猜想不应该是B出口,而对面柱子上指示朝我的方向是B出口?!难道我走过了?!不可能啊,来的那边明明写着我的前方是B出口啊。看到自己左前方是1线车检票口,于是过去问B出口在哪里?她则指向我的身后,原来那标志是和我行进的方向是平行的,不过检票的看再清楚不过了--方向角度都绝佳。真是讨厌,光线的传播路径居然是直的,害得我看不到指示牌,我想乘客也都这样想吧,北京有几个没花过“区间车”“快慢车”的冤枉钱啊--不稀罕。于是我右转,进入再左转上楼梯,然后我傻眼了,左右两条路,哪个是呢?!再次找指示牌,哇!看到了!上面写着“东南方向出口可通公交站”,这个时候俺必须感到极度惭愧,因为他认为“一般人”都应该知道方向的,东南就是东南啦,现在您知道站在那里有多傻了吧,真是为难啊,东南在哪里啊。终于看到一位姐姐穿过,我想她也晕了吧,--上下穿梭,难道是锻炼身体?她只跟俺说了一句话:“那边是东!”我想剩下的地方肯定是南了,于是我按照姐姐的指引,找到了公交站--累死我啦!!
         最后不知道怎么说啦,俺笨啊,笨死啦!
         最后想想真是自己的不对,应该站在地铁人员的角度看指导牌不就清楚了嘛,自己真是笨!