测试自动化_博客已迁移SuperTestman_博客已迁移测试之道

TestNG官方文档-1

2014-10-12  本文已影响3566人  狼孩

感谢原作者的奉献,原作者博客地址:http://blog.csdn.net/zhu_ai_xin_520/article/details/6199194
本次使用markdown重新制作一次,带有些地方的修正。

1 - 简介

TestNG 是一个测试框架,它被设计为用来简化广泛的设计需求,从单元测试 (单独测试一个类) 到集成测试 (测试由多个类、报甚至外部框架,例如应用程序服务器,所组成的系统).

编写一个测试通常分为三步:

你可以在欢迎
页面上找到一个快速入门的例子。

文档中会使用到如下的概念:

TestNG测试可以通过@BeforeXXX@AfterXXX annotations来标记,允许在特定的生命周期点上执行一些Java逻辑,这些点可以是上述列表中任意的内容。

本手册的其余部分将会解释如下内容:

2 - Annotations

如下就是在TestNG中可以使用的annotation的速查预览,并且其中给出了属性:

TestNG 类的配置信息
@BeforeSuite @BeforeSuite: 被注解的方法,会在当前suite中所有测试方法之 前 被调用。
@AfterSuite @AfterSuite: 被注解的方法,会在当前suite中所有测试方法之 后 被调用。
@BeforeTest @BeforeTest: 被注解的方法,会在测试(原文就是测试,不是测试方法)运行 前 被调用
@AfterTest @AfterTest: 被注解的方法,会在测试(原文就是测试,不是测试方法)运行后 被调用
@BeforeGroups @BeforeGroups: 被注解的方法会在组列表中之前被调用。这个方法会在每个组中第一个测试方法被调用之前被调用
@AfterGroups @AfterGroups: 被注解的方法会在组列表中之后被调用。这个方法会在每个组中最后一个测试方法被调用之后被调用
@BeforeClass @BeforeClass: 被注解的方法,会在当前类第一个测试方法运行前被调用
@AfterClass @AfterClass: 被注解的方法,会在当前类所有测试方法运行后被调用
@BeforeMethod @BeforeMethod: 被注解的方法,会在运行每个测试方法之前调用
@AfterMethod @AfterMethod: 被注解的方法,会在每个测试方法运行之后被调用
alwaysRun 对于在方法之前的调用(beforeSuite, beforeTest, beforeTestClass 和 beforeTestMethod, 除了beforeGroups): 若为true,这个配置方法无视其所属的组而运行.对于在方法之后的调用(afterSuite, afterClass, ...): 若为true, 这个配置方法会运行,即使其之前一个或者多个被调用的方法失败或者被跳过。
dependsOnGroups 方法所依赖的一组group列表
dependsOnMethods 方法所依赖的一组method列表
enabled 在当前class/method中被此annotation标记的方法是否参与测试(不启用则不在测试中运行)
groups 一组group列表,指明了这个class/method的所属。
inheritGroups 如果是true,则此方法会从属于在类级由@Test注解中所指定的组
@DataProvider 把此方法标记为为测试方法提供数据的方法。被此注释标记的方法必须返回Object[][],其中的每个Object[]可以被分配给测试方法列表中的方法当做参数。那些需要从DataProvider接受数据的@Test方法,需要使用一个dataprovider名字,此名称必须与这个注解中的名字相同。
name DataProvider的名字
@Factory 把一个方法标记为工厂方法,并且必须返回被TestNG测试类所使用的对象们。 此方法必须返回 Object[]。
@Parameters 说明如何给一个@Test方法传参。
value 方法参数变量的列表
@Test 把一个类或者方法标记为测试的一部分。
alwaysRun 如果为true,则这个测试方法即使在其所以来的方法为失败时也总会被运行。
dataProvider 这个测试方法的dataProvider
dataProviderClass 指明去那里找data provider类。如果不指定,那么就当前测试方法所在的类或者它个一个基类中去找。如果指定了,那么data provider方法必须是指定的类中的静态方法。
ependsOnGroups 方法所依赖的一组group列表
dependsOnMethods 方法所以来的一组method列表
description 方法的说明
enabled 在当前class/method中被此annotation标记的方法是否参与测试(不启用则不在测试中运行)
expectedExceptions 此方法会抛出的异常列表。如果没有异常或者一个不在列表中的异常被抛出,则测试被标记为失败。
groups 一组group列表,指明了这个class/method的所属。
invocationCount 方法被调用的次数。
invocationTimeOut 当前测试中所有调用累计时间的最大毫秒数。如果invocationCount属性没有指定,那么此属性会被忽略。
successPercentage 当前方法执行所期望的success的百分比
sequential 如果是true,那么测试类中所有的方法都是按照其定义顺序执行,即使是当前的测试使用parallel="methods"。此属性只能用在类级别,如果用在方法级,就会被忽略。
timeOut 当前测试所能运行的最大毫秒数
threadPoolSize 此方法线程池的大小。 此方法会根据制定的invocationCount值,以多个线程进行调用。注意:如果没有指定invocationCount属性,那么此属性就会被忽略

3 - testng.xml

调用TestNG有多种方式:

本节对testng.xml的格式进行说明(你会在下文找到关于ant和命令行的相关文档)。
目前给testng.xml所使用的DTD可以在主页: http://testng.org/testng-1.0.dtd 上找到(考虑到您能更方便,可以浏览 HTML版)。
下面是个testng.xml文件的例子:

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
  
<suite name="Suite1" verbose="1" >
  <test name="Nopackage" >
    <classes>
       <class name="NoPackageTest" />
    </classes>
  </test>
 
  <test name="Regression1">
    <classes>
      <class name="test.sample.ParameterSample"/>
      <class name="test.sample.ParameterTest"/>
    </classes>
  </test>
</suite>

你也可以指定包名来替代类名:

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
 
<suite name="Suite1" verbose="1" >
  <test name="Regression1"   >
    <packages>
      <package name="test.sample" />
   </packages>
 </test>
</suite>

在这个例子中,TestNG会查看所有在test.sample的类,并且只保留含有TestNG annotations的类。
你也可以指定要包含和排除掉的组和方法:

<test name="Regression1">
  <groups>
    <run>
      <exclude name="brokenTests"  />
      <include name="checkinTests"  />
    </run>
  </groups>
  
  <classes>
    <class name="test.IndividualMethodsTest">
      <methods>
        <include name="testMethod" />
      </methods>
    </class>
  </classes>
</test>

你也可以在testng.xml定义新的group,并且在属性中指明额外的详细信息,例如是否并行运行,使用多少个线程,并且是否正在运行JUnit测试等等……
默认情况下,TestNG按他们找到的XML文件中的顺序运行测试。如果你想在这个文件中列出的类和方法的无序顺序运行,可以设置preserve-order属性设置为false

<test name="Regression1" preserve-order="false">
  <classes>
 
    <class name="test.Test1">
      <methods>
        <include name="m1" />
        <include name="m2" />
      </methods>
    </class>
 
    <class name="test.Test2" />
 
  </classes>
</test>

4 - 运行

TestNG可以使用多种方式调用:

本节将只介绍如何从命令行运行TestNG。如果您对其他方式感兴趣,那么就点击上面的链接查看更多信息。
假设TestNG已经在你的类路径中,最简单的调用方式如下:
java org.testng.TestNG testng1.xml [testng2.xml testng3.xml ...]
你至少要指定一个XML文件,它描述了你要运行的TestNG suite。此外,还有如下命令行参数:命令行参数

选项 参数 说明
-d 目录 报告会生成的目录 (默认是test-output).
-excludegroups 逗号分隔的组列表 要在当前运行中被排除的组列表.
-groups 逗号分隔的组列表 想要运行的组(e.g. "windows,linux,regression").
-listener 逗号分隔的Java类列表,它们都可以在你的类路径中找到 让你指定你自己的监听器。这些类需要实现org.testng.ITestListener
-parallel 方法/测试 如果指定了,那么在运行测试的时候,所使用的默认的机制就会决定如何去使用并行 线程。反之则不会.这是可以在suite定义中被覆盖的
-reporter 自定义报告监听器的扩展配置 类似于-listener选项,允许在报告器实例中配置JavaBean的样式属性.-reporter com.test.MyReporter:methodFilter=*insert*,enableFiltering=true这个选项不限次数,根据需要一样一个。
-sourcedir 分号间隔的目录列表 使用了JavaDoc类型的annotation的源码所在的目录。这个选项只有你在使用JavaDoc类型的注解时才会有用。(e.g. "src/test" or"src/test/org/testng/eclipse-plugin;src/test/org/testng/testng").
-suitename test suite默认的名字 指明了在命令行中定义的test suite的名字。这个选项在suite.xml或源码指定了不同的名字时会被忽略。如果使用双引号括起来,就可在名字中使用空格。例如:"like this".
-testclass 逗号分隔的类列表,它们必须能在类路径中被找到 逗号分隔的测试类的列表 (e.g. "org.foo.Test1,org.foo.test2").
-testjar 一个jar文件 指定了一个包含了测试类的Jar文件。如果testng.xml在jar文件的根目录被找到,就使用之,反之,jar文件中所有的类都会被当成测试类。
-testname 测试所使用的默认名字 它为在命令行中定义的测试指定了名字。这个选项在suite.xml或源码指定了不同的名字时会被忽略。如果使用双引号括起来,就可在名字中使用空格。例如:"like this"。
-testrunfactory 可以在类路径中找到的Java类 让你指定你自己的测试运行器,相关的类必须实现org.testng.ITestRunnerFactory.
-threadcount 在并行测试的时候默认使用的线程数 并行运行中所使用的最大线程数。只在使用并行模式中有效(例如,使用-parallel选项)。它可以在suite定义中被覆盖。

上面的参数说明可以通过不带任何参数运行TestNG来获得。
你也可以把命令行开关放到文件中,例如说c:/command.txt,之后告诉 TestNG 使用这个文件来解析其参数:

C:> more c:\command.txt
-d test-output testng.xml
C:> java org.testng.TestNG @c:\command.txt

此外TestNG也可以在命令行下向其传递JVM参数。例如:

java -Dtestng.test.classpath="c:/build;c:/java/classes;" org.testng.TestNG testng.xml

如下是TestNG所能理解的属性:

属性 类型 说明
testng.test.classpath 分号分的一系列目录,其中包含了你的测试类 如果指定了这个属性,TestNG就会查找你的测试类而不是类路径。这在你的类路径中有很多类,而大多数又不是测试类,或者在xml文件中使用 package 标记的时候会很方便。

例子:
java org.testng.TestNG -groups windows,linux -testclass org.test.MyTest
注意ant task和testng.xml允许你使用更多的参数来运行TestNG(要包含的方法、指定的参数等等),所以你在学习TestNG的时候考虑使用命令行,因为这样能让你快速进步。

5 - 测试方法、测试类和测试组

5.1 - 测试方法

测试方法被注解为@Test。注释的方法与@Test碰巧返回值将被忽略,除非你设置允许回值设置为true在你的testng.xml

<suite allow-return-values="true">
or
<test allow-return-values="true">

5.2 - 测试组

TestNG 允许你将测试方法归类为不同的组。不仅仅是可以声明某个方法属于某个组,而且还可以让组包含其他的组。这样TestNG可以调用或者请求包含一组特定的组(或者正则表达式)而排除其他不需要组的集合。这样,如果你打算将测试分成两份的时候,就无需重新编译。这个特点,会给你在划分组的时候带来很大的灵活性。

组在的testng.xml文件中指定,可以发现无论是<test><suite>标签。指定组<suite>标签适用于所有的<test>下方标签。需要注意的是群体标签:如果指定组的“a”在<suite>标签中和b在<test>标签中,那么这两个“a”和“b”将被包括在内。

例如,通常将测试划分为两种类别是再常见不过的了:

典型的来说,检测性测试通常是功能性测试的一个子集。TestNG允许你根据个人感觉来进行组划分。例如,你可能希望把你所有的测试类都划归为"functest"组,并且额外的有几个方法输入"checkintest"组。

public class Test1 {
  @Test(groups = { "functest", "checkintest" })
  public void testMethod1() {
  }
 
  @Test(groups = {"functest", "checkintest"} )
  public void testMethod2() {
  }
 
  @Test(groups = { "functest" })
  public void testMethod3() {
  }
}

通过下面的内容调用TestNG

<test name="Test1">
  <groups>
    <run>
      <include name="functest"/>
    </run>
  </groups>
  <classes>
    <class name="example1.Test1"/>
  </classes>
</test>

以上会运行上面那个类中所有的测试,当药使用checkintest进行调用的时候,就仅仅运行testMethod1()和testMethod2()。
下面是另外一个例子。这次使用正则表达式。假定有些测试方法不应该运行在Linux环境下,你的测试会看起来像:

@Test
public class Test1 {
  @Test(groups = { "windows.checkintest" })
  public void testWindowsOnly() {
  }
 
  @Test(groups = {"linux.checkintest"} )
  public void testLinuxOnly() {
  }
 
  @Test(groups = { "windows.functest" )
  public void testWindowsToo() {
  }
}

然后你就可以使用下面这个 testng.xml 来只运行在Windows下的方法:

<test name="Test1">
  <groups>
    <run>
      <include name="windows.*"/>
    </run>
  </groups>
 
  <classes>
    <class name="example1.Test1"/>
  </classes>
</test>

注意:TestNG使用的是正则表达式,而不是通配符。注意这二者的区别(例如,"anything" 是匹配于 "."代表点和星号,而不是星号 "").

Method groups

也可以单独排除和包含若干方法:

<test name="Test1">
  <classes>
    <class name="example1.Test1">
      <methods>
        <include name=".*enabledTestMethod.*"/>
        <exclude name=".*brokenTestMethod.*"/>
      </methods>
     </class>
  </classes>
</test>

这样就可以不用编译而处理任何不需要的方法了,但是我不推荐过分的使用这个技巧,因为如果你要重构你的代码,那么这有可能让你的测试框架出问题(在标签中使用的方法可能再也不会匹配你的方法名了)。

5.3 - 组中组

测试组也可以包含其他组。这样的组叫做“元组"(MetaGroups)"。例如,你可能要定义一个组"all"来包含其他的组,"chekcintest""functest""functest"本身只包含了组windows和linux,而"checkintest"仅仅包含windows。你就可以在属性文件中这样定义:

<test name="Regression1">
  <groups>
    <define name="functest">
      <include name="windows"/>
      <include name="linux"/>
    </define>
  
    <define name="all">
      <include name="functest"/>
      <include name="checkintest"/>
    </define>
  
    <run>
      <include name="all"/>
    </run>
  </groups>
  
  <classes>
    <class name="test.sample.Test1"/>
  </classes>
</test>

5.4 - 排除组

TestNG 允许你包含组,当然也可以排除之。

譬如说,因为最近的改动,导致当前的测试中断并且,你还没有时间修复这些问题都是司空见惯的。但是,你还需要自己的功能测试可以正确运行,所以,制药简单的让这些不需要的测试失效就可以了。但是别忘记在以后需要的时候,要重新让其生效。

一个简单的办法来解决这个问题就是创建一个叫做"broken"组, 然后使得这些测试方法从属于那个组。例如上面的例子,假设我知道testMethod2()会中断,所以我希望使其失效:

@Test(groups = {"checkintest", "broken"} )
public void testMethod2() {
}

而我所需要做的一切就是从运行中排除这个组:

<test name="Simple example">
  <groups>
    <run>
      <include name="checkintest"/>
      <exclude name="broken"/>
    </run>
  </groups>
  
  <classes>
    <class name="example1.Test1"/>
  </classes>
</test>

通过这种办法,我们既可以得到整洁的测试运行,同时也能够跟踪那些需要稍后修正的中断的测试。
注意:你可以可以通过使用"enabled"属性来完成,这个属性适用于@Test@Before/After annotation

5.5 - 局部组

可以在类级别定义组,之后还可以在方法级定义组:

@Test(groups = { "checkin-test" })
public class All {
 
  @Test(groups = { "func-test" )
  public void method1() { ... }
 
  public void method2() { ... }
}

在这个类中,method2()类级组"checkin-test"的一部分,而method1()即属于"checkin-test"也属于"func-test"组。

5.6 - 参数

测试方法是可以带有参数的。每个测试方法都可以带有任意数量的参数,并且可以通过使用TestNG的@Parameters向方法传递正确的参数。

设置方式有两种方法:使用testng.xml或者程序编码。

5.6.1 - 使用 testng.xml 设置参数

如果只使用相对简单的参数,你可以在你的testng.xml文件中指定:

@Parameters({ "first-name" })
@Test
public void testSingleString(String firstName) {
  System.out.println("Invoked testString " + firstName);
  assert "Cedric".equals(firstName);
}

在这段代码中,我们让 firstName 参数能够接到XML文件中叫做 first-name参数的值。这个XML参数被定义在 testng.xml

<suite name="My suite">
  <parameter name="first-name"  value="Cedric"/>
  <test name="Simple example">
  <-- ... -->

类似的,它也可以用在@Before/After@Factory注解上:

@Parameters({ "datasource", "jdbcDriver" })
@BeforeMethod
public void beforeTest(String ds, String driver) {
  m_dataSource = ...;                              // 查询数据源的值
  m_jdbcDriver = driver;
}

这次有两个Java参数dsdriver会分别接收到来自属性datasource``和jdbc-driver`所指定的值。

参数也可以通过 Optional 注释来声明:

@Parameters("db")
@Test
public void testNonExistentParameter(@Optional("mysql") String db) { ... }

如果在你的testng.xml文件中没有找到"db",你的测试方法就会使用 @Optional中的值:"mysql"

@Parameters可以被放置到如下位置:

注意:

5.6.2 - 使用DataProviders提供参数

在 testng.xml 中指定参数可能会有如下的不足:

如果你压根不用testng.xml.
你需要传递复杂的参数,或者从Java中创建参数(复杂对象,对象从属性文件或者数据库中读取的etc...)
这样的话,你就可以使用Data Provider来给需要的测试提供参数。所谓数据提供者,就是一个能返回对象数组的数组的方法,并且这个方法被@DataProvider注解标注:

//这个方法会服务于任何把它(测试方法)的数据提供者的名字为"test1"方法
@DataProvider(name = "test1")
public Object[][] createData1() {
 return new Object[][] {
   { "Cedric", new Integer(36) },
   { "Anne", new Integer(37)},
 };
}
 
//这个测试方法,声明其数据提供者的名字为“test1”
@Test(dataProvider = "test1")
public void verifyData1(String n1, Integer n2) {
 System.out.println(n1 + " " + n2);
}

结果会打印

Cedric 36 
Anne 37

@Test标注的方法通过dataProvider属性指明其数据提供商。这个名字必须与@DataProvider(name="...")中的名字相一致。

默认的情况下,数据提供者会查找当前的测试类或者测试类的基类。如果你希望它能够被其他的类所使用,那么就要将其指定为static,并且通过 dataProviderClass属性指定要使用的类:

public class StaticProvider {
  @DataProvider(name = "create")
  public static Object[][] createData() {
    return new Object[][] {
      new Object[] { new Integer(42) }
    }
  }
}
 
public class MyTest {
  @Test(dataProvider = "create", dataProviderClass = StaticProvider.class)
  public void test(Integer n) {
    // ...
  }
}

Data Provider方法可以返回如下两种类型中的一种:

下面是使用这个功能的例子:

@DataProvider(name = "test1")
public Iterator<Object[]> createData() {
  return new MyIterator(DATA);
}

如果你声明的@DataProvider使用java.lang.reflect.Method作为第一个参数,TestNG 会把当前的测试方法当成参数传给第一个参数。这一点在你的多个测试方法使用相同的@DataProvider的时候,并且你想要依据具体的测试方法返回不同的值时,特别有用。

例如,下面的代码它内部的@DataProvider中的测试方法的名字:

@DataProvider(name = "dp")
public Object[][] createData(Method m) {
  System.out.println(m.getName());  // print test method name
  return new Object[][] { new Object[] { "Cedric" }};
}
 
@Test(dataProvider = "dp")
public void test1(String s) {
}
 
@Test(dataProvider = "dp")
public void test2(String s) {
}

所以会显示:

test1 
test2

Data provider可以通过属性parallel实现并行运行:

@DataProvider(parallel = true)
// ...

使用XML文件运行的data provider享有相同的线程池,默认的大小是10.你可以通过修改该在<suite>标签中的值来更改:

<suite name="Suite1" data-provider-thread-count="20" >
...

如果你需要让指定的几个data provider运行在不同的线程中,那么就必须通过不同的xml文件来运行。

5.6.3 - 在报告中的参数

在测试方法调用中所使用的参数,会在由TestNG中生成HTML报告里显示出来。下面是几个例子:


5.7 - 依赖方法

有些时候,需要按照特定顺序调用测试方法。对于下面的情况,这非常有用:

TestNG的允许您指定的依赖要么注解或XML格式。

5.7.1 - 依赖和注释

确保在进行更多的方法测试之前,有一定数量的测试方法已经成功完成。
在初始化测试的时候,同时希望这个初始化方法也是一个测试方法(@Before/After不会出现在最后生成的报告中)。
为此,你可以使用@Test中的dependsOnMethodsdependsOnGroups属性。

这两种依赖:

@Test
public void serverStartedOk() {}
 
@Test(dependsOnMethods = { "serverStartedOk" })
public void method1() {}

此例中,method1()依赖于方法serverStartedOk(),从而保证 serverStartedOk()总是先运行。

也可以让若干方法依赖于组:

@Test(groups = { "init" })
public void serverStartedOk() {}
 
@Test(groups = { "init" })
public void initEnvironment() {}
 
@Test(dependsOnGroups = { "init.* })
public void method1() {}

本例中,method1()``依赖于匹配正则表达式"init.*"的组,由此保证了serverStartedOk()initEnvironment()总是先于method1()`被调用。
注意:正如前面所说的那样,在相同组中的调用可是在夸测试中不保证顺序的。

如果你使用硬依赖,并且被依赖方法失败(alwaysRun=false,即默认是硬依赖),依赖方法则不是被标记为FAIL而是SKIP。被跳过的方法会被在最后的报告中标记出来(HTML既不用红色也不是绿色所表示),主要是被跳过的方法不是必然失败,所以被标出来做以区别。

无论dependsOnGroups还是dependsOnMethods都可以接受正则表达式作为参数。对于dependsOnMethods,如果被依赖的方法有多个重载,那么所有的重载方法都会被调用。如果你只希望使用这些重载中的一个,那么就应该使用 dependsOnGroups
更多关于依赖方法的例子,请参考这篇文章,其中也包含了对多重依赖使用继承方式来提供一种优雅的解决方案。

默认情况下,相关的方法是按类分组。例如,如果方法a()依赖于方法的b()和你有一个包含这些方法的类的几个实例(数据工厂提供的数据),这些方法的类的几个实例,然后调用顺序将如下:

a(1)
a(2)
b(2)
b(2)

TestNG的不会运行b(),直到所有实例都调用它们的a()方法。
这种行为可能不希望在某些情况下,例如在测试的标志并签署了Web浏览器的各个国家。在这种情况下,你希望下面的顺序:

signIn("us")
signOut("us")
signIn("uk")
signOut("uk")

对于这个顺序,你可以使用XML属性组group-by-instances。这个属性可以是在<suite>或<test>有效:

  <suite name="Factory" order-by-instances="true">
or
  <test name="Factory" order-by-instances="true">

5.7.2 - 在XML中依赖的关系

另外,您也可以在的testng.xml文件中指定的组依赖关系。您可以使用<dependencies>标签来实现这一点:

<test name="My suite">
  <groups>
    <dependencies>
      <group name="c" depends-on="a  b" />
      <group name="z" depends-on="c" />
    </dependencies>
  </groups>
</test>

5.8 - 工厂

工厂允许你动态的创建测试。例如,假设你需要创建一个测试方法,并用它来多次访问一个web页面,而且每次都带有不同的参数:

public class TestWebServer {
  @Test(parameters = { "number-of-times" })
  public void accessPage(int numberOfTimes) {
    while (numberOfTimes-- > 0) {
     // access the web page
    }
  }
}
<test name="T1">
  <parameter name="number-of-times" value="10"/>
  <class name= "TestWebServer" />
</test>
 
<test name="T2">
  <parameter name="number-of-times" value="20"/>
  <class name= "TestWebServer"/>
</test>
 
<test name="T3">
  <parameter name="number-of-times" value="30"/>
  <class name= "TestWebServer"/>
</test>

参数一旦多起来,就难以管理了,所以应该使用工厂来做:

public class WebTestFactory {
  @Factory
  public Object[] createInstances() {
   Object[] result = new Object[10]; 
   for (int i = 0; i < 10; i++) {
      result[i] = new WebTest(i * 10);
    }
    return result;
  }
}

新的测试类如下:

public class WebTest {
  private int m_numberOfTimes;
  public WebTest(int numberOfTimes) {
    m_numberOfTimes = numberOfTimes;
  }
 
  @Test
  public void testServer() {
   for (int i = 0; i < m_numberOfTimes; i++) {
     // access the web page
    }
  }
}

你的testng.xml只需要引用包含工厂方法的类,而测试实例自己会在运行时创建:

<class name="WebTestFactory" />

工厂方法可以接受诸如@Test@Before/After所标记的参数,并且会返回 Object[]。这些返回的对象可以是任何类(不一定是跟工厂方法相同的类),并且他们甚至都不需要TestNG注解(在例子中会被TestNG忽略掉)

工厂也可以被应用于数据提供者,并可以通过将@Factory注释无论是在普通的方法或构造函数利用此功能。这里是一个构造工厂的例子:

@Factory(dataProvider = "dp")
public FactoryDataProviderSampleTest(int n) {
  super(n);
}
 
@DataProvider
static public Object[][] dp() {
  return new Object[][] {
    new Object[] { 41 },
    new Object[] { 42 },
  };
}

该示例将TestNG的创建两个测试类,这个构造函数将调用值为41和42的对像。

5.9 - 类级注解

通常 @Test 也可以用来标注类,而不仅仅是方法:

@Test
public class Test1 {
  public void test1() {
  }
 
  public void test2() {
  }
}

处于类级的@Test会使得类中所有的public方法成为测试方法,而不管他们是否已经被标注。当然,你仍然可以用@Test注解重复标注测试方法,特别是要为其添加一些特别的属性时。
例如:

@Test
public class Test1 {
  public void test1() {
  }
 
  @Test(groups = "g1")
  public void test2() {
  }
}

上例中test1()test2()都被处理,不过在此之上test2()现在还属于组 "g1"。

5.10 - 并行运行和超时

您可以以各种方式指示TestNG单独线程运行测试。

5.10.1 - 并行套件

如果你正在运行几个组件文件(e.g. "java org.testng.TestNG testng1.xml testng2.xml")和想让这些组件以一个独立的线程运行这个是非常有用的。你可以使用以下命令行参数来指定线程池的大小。

java org.testng.TestNG -suitethreadpoolsize 3 testng1.xml testng2.xml testng3.xml

相应的ant任务名称为suitethreadpoolsize

5.10.2 - 并行测试,类和方法

你可以通过在suite标签中使用 parallel 属性来让测试方法运行在不同的线程中。这个属性可以带有如下这样的值:

<suite name="My suite" parallel="methods" thread-count="5">
<suite name="My suite" parallel="tests" thread-count="5">
<suite name="My suite" parallel="classes" thread-count="5">
<suite name="My suite" parallel="instances" thread-count="5">

此外,属性thread-count允许你为当前的执行指定可以运行的线程数量。

注意:@Test 中的属性timeOut可以工作在并行和非并行两种模式下。

你也可以指定@Test方法在不同的线程中被调用。你可以使用属性 threadPoolSize来实现:

@Test(threadPoolSize = 3, invocationCount = 10,  timeOut = 10000)
public void testServer() {

上例中,方法testServer会在3个线程中调用10次。此外,10秒钟的超时设定也保证了这三个线程中的任何一个都永远不会阻塞当前被调用的线程。

5.11 - 再次运行失败的测试

每次测试suite出现失败的测试,TestNG 就会在输出目录中创建一个叫做 testng-failed.xml的文件。这个XML文件包含了重新运行那些失败测试的必要信息,使得你可以无需运行整个测试就可以快速重新运行失败的测试。所以,一个典型的会话看起来像:

要注意的是,testng-failed.xml已经包含了所有失败方法运行时需要的依赖,所以完全可以保证上次失败的方法不会出现任何 SKIP。

5.12 - JUnit测试

TestNG 能够运行 JUnit 测试。所有要做的工作就是在testng.classNames属性中设定要运行的JUnit测试类,并且把testng.junit属性设置为true

<test name="Test1" junit="true">
  <classes>
    <!-- ... -->

TestNG 在这种情况下所表现的行为与 JUnit 相似:

5.13 - 程序化运行TestNG

你可以在程序中非常轻松的调用TestNG的测试:

TestListenerAdapter tla = new TestListenerAdapter();
TestNG testng = new TestNG();
testng.setTestClasses(new Class[] { Run2.class });
testng.addListener(tla);
testng.run();

本例中创建了一个TestNG对象,并且运行测试类Run2。它添加了一个 TestListener(这是个监听器) 。你既可以使用适配器类 org.testng.TestListenerAdapter来做,也可以实现org.testng.ITestListener接口。这个接口包含了各种各样的回调方法,能够让你跟踪测试什么时候开始、成功、失败等等。

类似的你可以用testng.xml文件调用或者创建一个虚拟的testng.xml文件来调用。为此,你可以使用这个包org.testng.xml中的类:XmlClass、XmlTest等等。每个类都对应了其在xml中对等的标签。

例如,假设你要创建下面这样的虚拟文件:

<suite name="TmpSuite" >
  <test name="TmpTest" >
    <classes>
      <class name="test.failures.Child"  />
    <classes>
    </test>
</suite>

你需要使用如下代码:

XmlSuite suite = new XmlSuite();
suite.setName("TmpSuite");
 
XmlTest test = new XmlTest(suite);
test.setName("TmpTest");
List<XmlClass> classes = new ArrayList<XmlClass>();
classes.add(new XmlClass("test.failures.Child"));
test.setXmlClasses(classes) ;

之后你可以传递这个XmlSuite给 TestNG:

List<XmlSuite> suites = new ArrayList<XmlSuite>();
suites.add(suite);
TestNG tng = new TestNG();
tng.setXmlSuites(suites);
tng.run();

请参阅JavaDocs来获取完整的API。

5.14 - BeanShell于高级组选择

如果<include><exclude>不够用,那就是用BeanShell表达式来决定是否一个特定的测试方法应该被包含进来。只要在<test>标签下使用这个表达式就好了:

<test name="BeanShell test">
   <method-selectors>
     <method-selector>
       <script language="beanshell"><![CDATA[
         groups.containsKey("test1")
       ]]></script>
     </method-selector>
   </method-selectors>
  <!-- ... -->

当在testng.xml文件中找到<script>标签后,TestNG 就会忽略在当前<test>标签内组和方法的的<include><exclude>标签:BeanShell就会成为测试方法是否被包含的唯一决定因素。

下面是关于BeanShell 脚本的额外说明:

5.15 - 注解转换器

TestNG 允许你在运行时修改所有注解的内容。在源码中注解大多数时候都能正常工作的时时非常有用的(这句原文就是这意思,感觉不太对劲),但是有几个情况你可能会改变其中的值。

为此,你会用到注解转换器( Annotation Transformer )。

所谓注解转换器,就是实现了下面接口的类:

public interface IAnnotationTransformer {
 
  /**
   * This method will be invoked by TestNG to give you a chance
   * to modify a TestNG annotation read from your test classes.
   * You can change the values you need by calling any of the
   * setters on the ITest interface.
   *
   * Note that only one of the three parameters testClass,
   * testConstructor and testMethod will be non-null.
   *
   * @param annotation The annotation that was read from your
   * test class.
   * @param testClass If the annotation was found on a class, this
   * parameter represents this class (null otherwise).
   * @param testConstructor If the annotation was found on a constructor,
   * this parameter represents this constructor (null otherwise).
   * @param testMethod If the annotation was found on a method,
   * this parameter represents this method (null otherwise).
   */
  public void transform(ITest annotation, Class testClass,
      Constructor testConstructor, Method testMethod);
}

像其他的TestNG 监听器,你可以指定在命令行或者通过ant来指定这个类:

java org.testng.TestNG -listener MyTransformer testng.xml

或者在程序中:

TestNG tng = new TestNG();
tng.setAnnotationTransformer(new MyTransformer());
// ...

当调用transform()的时候,你可以调用任何在 ITest test 参数中的设置方法来在进一步处理之前改变它的值。

例如,这里是你如何覆盖属性invocationCount的值,但是只有测试类中的invoke()方法受影响:

public class MyTransformer implements IAnnotationTransformer {
  public void transform(ITest annotation, Class testClass,
      Constructor testConstructor, Method testMethod)
  {
    if ("invoke".equals(testMethod.getName())) {
      annotation.setInvocationCount(5);
    }
  }
}

IAnnotationTransformer只允许你修改一个@Test注解。如果你需要修改其他的(假设说配置注解@Factory@DataProvider),使用 IAnnotationTransformer2

5.16 - 方法拦截器

一旦TestNG 计算好了测试方法会以怎样的顺序调用,那么这些方法就会分为两组:

为了能够让你更好的控制第二种类别,TestNG定义如下接口:

public interface IMethodInterceptor {
   
  List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context);
 
}

方法中叫做methods的那个列表参数包含了所有以不定序运行的方法。你的 intercept方法也要返回一个IMethodInstance列表,它可能是下面情况之一:

一旦你定义了拦截器,就把它传递个TestNG,用下面的方式:

java -classpath "testng-jdk15.jar:test/build" org.testng.TestNG -listener test.methodinterceptors.NullMethodInterceptor
   -testclass test.methodinterceptors.FooTest

关于 ant 中对应的语法,参见listeners属性ant文档 中的说明。

例如,下面是个方法拦截器会重新给方法排序,一遍“fast”组中的方法总是先执行:

public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
  List<IMethodInstance> result = new ArrayList<IMethodInstance>();
  for (IMethodInstance m : methods) {
    Test test = m.getMethod().getConstructorOrMethod().getAnnotation(Test.class);
    Set<String> groups = new HashSet<String>();
    for (String group : test.groups()) {
      groups.add(group);
    }
    if (groups.contains("fast")) {
      result.add(0, m);
    }
    else {
      result.add(m);
    }
  }
  return result;
}

5.17 - TestNG 监听器

有很多接口可以用来修改 TestNG 的行为。这些接口都被统称为 "TestNG 监听器"。下面是目前支持的监听器的列表:

当你实现了这些接口,你可以让 TestNG 知道这些接口,有如下方式:

5.17.1 - 在testng.xml或者java中制定监听

下面就是你如何在testng.xml中定义监听:

<suite>
 
  <listeners>
    <listener class-name="com.example.MyListener" />
    <listener class-name="com.example.MyMethodInterceptor" />
  </listeners>
 
...

或者你喜欢在java中定义的监听:

@Listeners({ com.example.MyListener.class, com.example.MyMethodInterceptor.class })
public class MyTest {
  // ...
}

@Listeners注释可以包含扩展org.testng.ITestNGListener除了IAnnotationTransformerIAnnotationTransformer2任何类。其原因是,这些监听需要在过程中很早就知道以便TestNG可以使用它们来重写你的注解,因此,你需要在你的testng.xml文件中指定这些侦听器。

请注意@Listeners注释将适用于整个套件的文件,就像如果你已经指定在一个的testng.xml文件。如果你想限制它的范围(例如,仅运行在当前类),在监听的代码可以先检查测试方法的运行,并决定该怎么做呢。

5.17.2 - 书写服务加载监听

最后,JDK中提供了一个非常优雅的机制,通过ServiceLoader类的借口路径来实现监听。

随着ServiceLoader,这一切你所需要做的就是创建一个包含你的监听和一些配置文件的JAR文件,把这个jar文件放在classpath中运行TestNG并TestNG会自动找到它们。

下面是它如何工作的具体例子。
让我们先创建一个侦听器(所有的TetstNG监听都应该响应):

package test.tmp;
 
public class TmpSuiteListener implements ISuiteListener {
  @Override
  public void onFinish(ISuite suite) {
    System.out.println("Finishing");
  }
 
  @Override
  public void onStart(ISuite suite) {
    System.out.println("Starting");
  }
}

编译这个文件,然后在当前文件位置创建一个文件META-INF/services/org.testng.ITestNGListener这个名字就是你要实现的接口。

最后您应该知道如下的目录结构,只有仅仅两个文件:

$ tree
|____META-INF
| |____services
| | |____org.testng.ITestNGListener
|____test
| |____tmp
| | |____TmpSuiteListener.class
 
$ cat META-INF/services/org.testng.ITestNGListener
test.tmp.TmpSuiteListener

在这个目录创建一个jar文件如下:

$ jar cvf ../sl.jar .
added manifest
ignoring entry META-INF/
adding: META-INF/services/(in = 0) (out= 0)(stored 0%)
adding: META-INF/services/org.testng.ITestNGListener(in = 26) (out= 28)(deflated -7%)
adding: test/(in = 0) (out= 0)(stored 0%)
adding: test/tmp/(in = 0) (out= 0)(stored 0%)
adding: test/tmp/TmpSuiteListener.class(in = 849) (out= 470)(deflated 44%)

接着,把你的jar文件放在当前你调用TestNG的classpath上:

$ java -classpath sl.jar:testng.jar org.testng.TestNG testng-single.yaml
Starting
f2 11 2
PASSED: f2("2")
Finishing

这种机制允许你在同一组侦听器适用于整个组织只需增加一个JAR文件添加到类路径,而不是要求每一个开发者要记得在他们的testng.xml文件中指定这些侦听器。

5.18 - 依赖注入

TestNG的支持两种不同类型的依赖注入:本机(由TestNG的本身执行)和外部(通过依赖注入框架,如Guice)。

5.18.1 - 本地依赖注入

TestNG 允许你在自己的方法中声明额外的参数。这时,TestNG会自动使用正确的值填充这些参数。依赖注入就使用在这种地方:

你可以使用@NoInjection关闭注释:

public class NoInjectionTest {
 
  @DataProvider(name = "provider")
  public Object[][] provide() throws Exception {
      return new Object[][] { { CC.class.getMethod("f") } };
  }
 
  @Test(dataProvider = "provider")
  public void withoutInjection(@NoInjection Method m) {
      Assert.assertEquals(m.getName(), "f");
  }
 
  @Test(dataProvider = "provider")
  public void withInjection(Method m) {
      Assert.assertEquals(m.getName(), "withInjection");
  }
}

5.18.2 - Guice依赖注入

如果您使用Guice,TestNG给你一个简单的方法来与Guice模块注入测试对象:

@Guice(modules = GuiceExampleModule.class)
public class GuiceTest extends SimpleBaseTest {
 
  @Inject
  ISingleton m_singleton;
 
  @Test
  public void singletonShouldWork() {
    m_singleton.doSomething();
  }
 
}

在这个例子中,GuiceExampleModule被预期绑定到ISingleton借口的一些具体的类:

public class GuiceExampleModule implements Module {
 
  @Override
  public void configure(Binder binder) {
    binder.bind(ISingleton.class).to(ExampleSingleton.class).in(Singleton.class);
  }
 
}

如果在指定的模块实例化你的测试类需要更多的灵活性,您可以指定一个模块工厂:

@Guice(moduleFactory = ModuleFactory.class)
public class GuiceModuleFactoryTest {
 
  @Inject
  ISingleton m_singleton;
 
  @Test
  public void singletonShouldWork() {
    m_singleton.doSomething();
  }
}

模块工厂需要实现该IModuleFactory口:

public interface IModuleFactory {
 /**
   * @param context The current test context
   * @param testClass The test class
   *
   * @return The Guice module that should be used to get an instance of this
   * test class.
   */
  Module createModule(ITestContext context, Class<?> testClass);
}

你的工厂将会通过了测试环境和TestNG需要实例化测试类的一个实例。你的createModule方法应该返回一个Guice模块,就会知道如何实例化这个测试类。您可以使用测试环境,以了解您的环境的详细信息,如在规定的testng.xml设置参数等...

5.19 - 监听方法调用

无论何时TestNG即将调用一个测试(被@Test注解的)或者配置(任何使用@Beforeor@After注解标注的方法),监听器 IInvokedMethodListener都可以让你得到通知。你所要做的就是实现如下接口:

public interface IInvokedMethodListener extends ITestNGListener {
  void beforeInvocation(IInvokedMethod method, ITestResult testResult);
  void afterInvocation(IInvokedMethod method, ITestResult testResult);
}

并且就像在 关于TestNG监听器一节 中所讨论的那样,将其声明为一个监听器。

5.20 - 重写测试方法

TestNG的允许你覆盖和跳过测试方法的调用。在那里,这是非常有用的一个例子,如果你需要你的测试方法需要一个特定的安全管理。通过提供实现IHookable一个监听器,你就可以做到这一点。

下面是用JAAS(Java验证和授权API)一个例子:

public class MyHook implements IHookable {
  public void run(final IHookCallBack icb, ITestResult testResult) {
    // Preferably initialized in a @Configuration method
    mySubject = authenticateWithJAAs();
    
    Subject.doAs(mySubject, new PrivilegedExceptionAction() {
      public Object run() {
        icb.callback(testResult);
      }
    };
  }
}

6 - 测试结果

6.1 - 成功、失败和断言

如果一个测试没有抛出任何异常就完成运行或者说抛出了期望的异常(参见@Test注解的expectedExceptions属性文档),就说,这个测试是成功的。
测试方法的组成常常包括抛出多个异常,或者包含各种各样的断言(使用Java "assert"关键字)。一个"assert"失败会触发一个AssertionErrorException,结果就是测试方法被标记为失败(记住,如果你看不到断言错误,要在加上-ea这个JVM参数)。
下面是个例子:

@Test
public void verifyLastName() {
  assert "Beust".equals(m_lastName) : "Expected name Beust, for" + m_lastName;
}

TestNG 也包括 JUnit 的Assert类,允许你对复杂的对象执行断言:

import static org.testng.AssertJUnit.*;
//...
@Test
public void verify() {
  assertEquals("Beust", m_lastName);
}

注意,上述代码使用了静态导入,以便能够使用assertEquals 方法,而无需加上它的类前缀。

6.2 - 日志与结果

当运行SuiteRunner的时候会指定一个目录,之后测试的结果都会保存在一个在那个目录中叫做index.html的文件中。这个index文件指向其他多个HTML和文本文件,被指向的文件包含了整个测试的结果。你可以再 这里 看到一个例子。

通过使用监听器和报表器,可以很轻松的生成自己的TestNG报表:

例如,如果你想要生成一个PDF报告,那么就不需要实时通知,所以用IReporter。如果需要写一个实时报告,例如用在GUI上,还要在每次测试时(下面会有例子和解释)有进度条或者文本报告显示点 ("."),那么ITestListener是你最好的选择。

6.2.1 - 日志监听器

这里是对每个传递进来的测试显示"."的监听器,如果测试失败则显示 "F" ,跳过则是"S":

public class DotTestListener extends TestListenerAdapter {
  private int m_count = 0;
 
  @Override
  public void onTestFailure(ITestResult tr) {
    log("F");
  }
 
  @Override
  public void onTestSkipped(ITestResult tr) {
    log("S");
  }
 
  @Override
  public void onTestSuccess(ITestResult tr) {
    log(".");
  }
 
  private void log(String string) {
    System.out.print(string);
    if (++m_count % 40 == 0) {
      System.out.println("");
    }
  }
}

上例中,我们选择扩展TestListenerAdapter,它使用空方法实现了ITestListener。所以我不需要去重写那些我不需要的方法。如果喜欢可以直接实现接口。

这里是我使用这个新监听器调用TestNG的例子:

java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -listener org.testng.reporters.DotTestListener test\testng.xml

输出是:

........................................
........................................
........................................
........................................
........................................
.........................
===============================================
TestNG JDK 1.5
Total tests run: 226, Failures: 0, Skips: 0
===============================================

注意,当你使用-listener的时候,TestNG 会自动的检测你所使用的监听器类型。

6.2.2 - 日志报表

org.testng.IReporter接口只有一个方法:

public void generateReport(List<ISuite> suites, String outputDirectory)

这个方法在 TestNG 中的所有测试都运行完毕之后被调用,这样你可以方法这个方法的参数,并且通过它们获得刚刚完成的测试的所有信息。

6.2.3 - JUnit 报表

TestNG 包含了一个可以让TestNG的结果和输出的XML能够被JUnitReport所使用的监听器。这里 有个例子,并且ant任务创建了这个报告:

<target name="reports">
  <junitreport todir="test-report">
    <fileset dir="test-output">
      <include name="*/*.xml"/>
    </fileset>
  
    <report format="noframes"  todir="test-report"/>
  </junitreport>
</target>

6.2.4 - 报表 API

如果你要在HTML报告中显示日志信息,那么就要用到类 org.testng.Reporter:Reporter.log("M3 WAS CALLED");


6.2.5 - XML 报表

TestNG 提供一种XML报表器,使得能够捕捉到只适用于TestNG而不适用与JUnit报表的那些特定的信息。这在用户的测试环境必须要是用TestNG特定信息的XML,而JUnit又不能够提供这些信息的时候非常有用。下面就是这种报表器生成XML的一个例子:

<testng-results>
  <suite name="Suite1">
    <groups>
      <group name="group1">
        <method signature="com.test.TestOne.test2()" name="test2" class="com.test.TestOne"/>
        <method signature="com.test.TestOne.test1()" name="test1" class="com.test.TestOne"/>
      </group>
      <group name="group2">
        <method signature="com.test.TestOne.test2()" name="test2" class="com.test.TestOne"/>
      </group>
    </groups>
    <test name="test1">
      <class name="com.test.TestOne">
        <test-method status="FAIL" signature="test1()" name="test1" duration-ms="0"
              started-at="2007-05-28T12:14:37Z" description="someDescription2"
              finished-at="2007-05-28T12:14:37Z">
          <exception class="java.lang.AssertionError">
            <short-stacktrace>java.lang.AssertionError
              ... Removed 22 stack frames
            </short-stacktrace>
          </exception>
        </test-method>
        <test-method status="PASS" signature="test2()" name="test2" duration-ms="0"
              started-at="2007-05-28T12:14:37Z" description="someDescription1"
              finished-at="2007-05-28T12:14:37Z">
        </test-method>
        <test-method status="PASS" signature="setUp()" name="setUp" is-config="true" duration-ms="15"
              started-at="2007-05-28T12:14:37Z" finished-at="2007-05-28T12:14:37Z">
        </test-method>
      </class>
    </test>
  </suite>
</testng-results>

这个报表器是随着默认监听器一起诸如的,所以你默认的情况下就可以得到这种类型的输出。这个监听器提供了一些属性,可以修改报表来满足你的需要。下表包含了这些属性,并做简要说明:

属性 说明 Default value
outputDirectory 一个String指明了XML文件要存放的目录 TestNG 输出目录
timestampFormat 指定报表器生成日期字段的格式 yyyy-MM-dd'T'HH:mm:ss'Z'
fileFragmentationLevel 值为1,2或3的整数,指定了XML文件的生成方式:1 - 在一个文件里面生成所有的结果。2 - 每套测试都单独生成一个XML文件,这些文件被链接到一个主文件。3 - 同2,不过添加了引用那些套测试的测试用例的XML文件。 1
splitClassAndPackageNames boolean值,指明了对<class>元素的生成方式。例如,你在false的时候得到<class class="com.test.MyTest">,在true的时候<class class="MyTest" package="com.test"> false
generateGroupsAttribute boolean值指定了对<test-method>元素是否生成groups 属性。这个功能的目的是,对那些无需遍历整个<group>元素,且只含有一个测试方法的组来说,可以提供一种更直接的解析组的方式。 false
stackTraceOutputMethod 指定在发生异常的时候生成异常,追踪弹栈信息的类型,有如下可选值:0 - 无弹栈信息 (只有异常类和消息)。1 - 尖端的弹栈信息,从上到下只有几行。2 - 带有所有内部异常的完整的弹栈方式。3 - 短长两种弹栈方式 2
generateDependsOnMethods 对于<test-method>元素,使用这个属性来开启/关闭 depends-on-methods属性。 true
generateDependsOnGroups 对于<test-method>属性开启depends-on-groups属性。 true

为了配置报表器,你可以在命令行下使用-reporter选项,或者在 Ant 任务中嵌入<reporter>元素。对于每个情况,你都必须指明类org.testng.reporters.XMLReporter。但是要注意你不能够配置内建的报表器,因为这个只使用默认配置。如果你的确需要XML报表,并且使用自定义配置,那么你就不得不手工完成。可以通过自己添加一两个方法,并且禁用默认监听器达到目的。

上一篇下一篇

猜你喜欢

热点阅读