5 - 测试方法、测试类和测试组
5.1 - 测试组
TestNG 允许你将测试方法归类为不同的组。不仅仅是可以声明某个方法属于某个组,而且还可以让组包含其他的组。这样TestNG可以调用或者请求包含一组特定的组(或者正则表达式)而排除其他不需要组的集合。这样,如果你打算将测试分成两份的时候,就无需重新编译。这个特点,会给你在划分组的时候带来很大的灵活性。
例如,通常将测试划分为两种类别是再常见不过的了:
- 检查性测试(Check-in test):这些测试在你提交新代码之前就会运行。它们一般都是很快进行的,并且保证没有哪个基本的功能是不好使的。
- 功能性测试(Functional test):这些测试涵盖你的软件中所有的功能,并且至少每天运行一次,不过你也可能希望他们持续的运行。
典型的来说,检测性测试通常是功能性测试的一个子集。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" 是匹配于 ".*" -- 点和星号 -- 而不是星号 "*").
方法组
也可以单独排除和包含若干方法:
<test name="Test1"> <classes> <class name="example1.Test1"> <methods> <include name=".*enabledTestMethod.*"/> <exclude name=".*brokenTestMethod.*"/> </methods> </class> </classes> </test>
这样就可以不用编译而处理任何不需要的方法了,但是我不推荐过分的使用这个技巧,因为如果你要重构你的代码,那么这有可能让你的测试框架出问题(在标签中使用的方法可能再也不会匹配你的方法名了)。
5.2 - 组中组
测试组也可以包含其他组。这样的组叫做“元组”(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.3 - 排除组
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.4 - 局部组
可以在类级别定义组,之后还可以在方法级定义组:
@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.5 - 参数
测试方法是可以带有参数的。每个测试方法都可以带有任意数量的参数,并且可以通过使用TestNG的@Parameters向方法传递正确的参数。
设置方式有两种方法:使用 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参数 ds 和 driver 会分别接收到来自属性datasource 和 jdbc-driver 所指定的值。
参数也可以通过 Optional 注释来声明:
@Parameters("db") @Test public void testNonExistentParameter(@Optional("mysql") String db) { ... }
如果在你的testng.xml文件中没有找到"db",你的测试方法就会使用 @Optional 中的值: "mysql"。
@Parameters 可以被放置到如下位置:
- 在任何已经被 @Test, @Before/After 或 @Factory 注解过的地方。
- 在测试类中至多被放到一个构造函数签。这样,TestNG才能在需要的时候使用 testng.xml 中特定的参数来实例化这个类。这个特性可以被用作初始化某些类中的值,以便稍后会被类中其他的方法所使用。
注意:
- XML中的参数会按照Java参数在注解中出现的顺序被映射过去,并且如果数量不匹配,TestNG会报错。
- 参数是有作用范围的。在testng.xml 中,你即可以在<suite> 标签下声明,也可以在<test>下声明。如果两个参数都有相同的名字,那么,定义在 <test> 中的有优先权。这在你需要覆盖某些测试中特定参数的值时,会非常方便。
在 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 static 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方法可以返回如下两种类型中的一种:
- 含有多个对象的数组 (Object[][]),其中第一个下标指明了测试方法要调用的次数,第二个下标则完全与测试方法中的参数类型和个数相匹配。上面的例子已经说明。
- 另外一个是迭代器 Iterator<Object[]>。二者的区别是迭代器允许你延迟创建自己的测试数据。TestNG会调用迭代器,之后测试方法会一个接一个的调用由迭代器返回的值。在你需要传递很多参数组给测试组的时候,这样你无须提前创建一堆值。
下面是使用 JDK 1.4 和 JDK5 的例子 (注意 JDK 1.4 的例子不适用泛型):
/** * @testng.data-provider name="test1" */ public Iterator createData() { return new MyIterator(DATA); )
@DataProvider(name = "test1") public Iterator
如果你声明的 @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.5.3 - 在报告中的参数
在测试方法调用中所使用的参数,会在由TestNG中生成HTML报告里显示出来。下面是几个例子:
5.6 - 依赖方法
有些时候,需要按照特定顺序调用测试方法。对于下面的情况,这非常有用:
- 确保在进行更多的方法测试之前,有一定数量的测试方法已经成功完成。
- 在初始化测试的时候,同时希望这个初始化方法也是一个测试方法( @Before/After 不会出现在最后生成的报告中)。
为此,你可以使用 @Test 中的 dependsOnMethods 或 dependsOnGroups 属性。
这两种依赖:
- Hard dependencies(硬依赖)。所有的被依赖方法必须成功运行。只要有一个出问题,测试就不会被调用,并且在报告中被标记为SKIP。
- Soft dependencies(软依赖)。 即便是有些依赖方法失败了,也一样运行。如果你只是需要保证你的测试方法按照顺序执行,而不关心他们的依赖方法是否成功。那么这种机制就非常有用。可以通过添加"alwaysRun=true" 到 @Test 来实现软依赖。
硬依赖的例子:
@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。
更多关于依赖方法的例子,请参考这篇文章,其中也包含了对多重依赖使用继承方式来提供一种优雅的解决方案。
5.7 - 工厂
工厂允许你动态的创建测试。例如,假设你需要创建一个测试方法,并用它来多次访问一个web页面,而且每次都带有不同的参数:
public class TestWebServer { @Test(parameters = { "number-of-times" }) public void accessPage(int numberOfTimes) { while (numberOfTimes-- > 0) { // access the web page } } }testng.xml:
<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忽略掉)
5.8 - 类级注解
通常 @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.9 - 并行运行于超时
你可以通过在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">
- parallel="methods": TestNG 会在不同的线程中运行测试方法,除非那些互相依赖的方法。那些相互依赖的方法会运行在同一个线程中,并且遵照其执行顺序。
- parallel="tests": TestNG 会在相同的线程中运行相同的<test>标记下的所有方法,但是每个<test>标签中的所有方法会运行在不同的线程中。这样就允许你把所有非线程安全的类分组到同一个<test>标签下,并且使其可以利用TestNG多线程的特性的同时,让这些类运行在相同的线程中。
- parallel="classes": TestNG 会在相同线程中相同类中的运行所有的方法,但是每个类都会用不同的线程运行。
此外,属性 thread-count 允许你为当前的执行指定可以运行的线程数量。
你也可以指定 @Test 方法在不同的线程中被调用。你可以使用属性 threadPoolSize 来实现:注意:@Test 中的属性 timeOut 可以工作在并行和非并行两种模式下。
@Test(threadPoolSize = 3, invocationCount = 10, timeOut = 10000) public void testServer() {上例中,方法 testServer 会在3个线程中调用10次。此外,10秒钟的超时设定也保证了这三个线程中的任何一个都永远不会阻塞当前被调用的线程。
5.10 - 再次运行失败的测试
每次测试suite出现失败的测试,TestNG 就会在输出目录中创建一个叫做 testng-failed.xml 的文件。这个XML文件包含了重新运行那些失败测试的必要信息,使得你可以无需运行整个测试就可以快速重新运行失败的测试。所以,一个典型的会话看起来像:java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs testng.xml java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs test-outputs/testng-failed.xml
要注意的是,testng-failed.xml 已经包含了所有失败方法运行时需要的依赖,所以完全可以保证上次失败的方法不会出现任何 SKIP。
5.11 - JUnit测试
TestNG 能够运行 JUnit 测试。所有要做的工作就是在 testng.classNames 属性中设定要运行的JUnit测试类,并且把 testng.junit 属性设置为true:<test name="Test1" junit="true"> <classes> <!-- ... -->
TestNG 在这种情况下所表现的行为与 JUnit 相似:
- 所有类中要运行的测试方法由 test* 开头
- 如果类中有 setUp() 方法,则其会在每个测试方法执行前被调用
- 如果类中有 tearDown() 方法,则其会在每个测试方法之后被调用
- 如果测试类包含 suite() 方法,则所有的被这个方法返回的测试类都会被调用
5.12 - JDK 1.4
TestNG 也可以工作在 JDK 1.4 下。你需要使用 JDK 1.4 jar ,在发行版中可以找到( 名为 testng-...-jdk14.jar )。唯一的不同就是注解的方式,它使用的流行的XDoclet JavaDoc 注解语法:public class SimpleTest { /** * @testng.before-class = "true" */ public void setUp() { // code that will be invoked when this test is instantiated } /** * @testng.test groups = "functest" dependsOnGroups = "group1,group2" */ public void testItWorks() { // your test code } }
JavaDoc 语法的规则是相当的直接,并且与JDK 1.5 注解唯一的不同就在于字符串数组被表示为单个逗号或者空格分隔的形式。然而,用双引号标注值时可选的,不过我极力推荐你使用双引号,以便以后 迁移到JDK 1.5 会更容易些。
你也需要在使用ant任务中在 <testng> 标记中指定 sourcedir 属性 (或 -sourcedir 在命令行下),这样TestNG 就能够按顺序找到测试文件,并且解析JavaDoc注解。
下标就是比较 JDK 1.4 和 JDK 5 注解的异同。
JDK 5 | JDK 1.4 |
---|---|
@Test(groups = { "a", "b" }, dependsOnMethods = { "m1" }) | /** * @testng.test groups = "a b" * dependsOnMethods = "m1" */ |
@AfterMethod(alwaysRun = true) | /** * @testng.before-method alwaysRun = "true" */ |
@Parameters({ "first-name", "last-name" }) | /** * @testng.parameters value = "first-name last-name" */ |
@Factory @Parameters({ "first-name", "last-name"}) | /** * @testng.factory * @testng.parameters value = "first-name last-name" */ |
@DataProvider(name = "test1") | /** * @testng.data-provider name="test1" */ |
关于 TestNG's JDK 1.4 的例子和支持,请参看发行版中的 test-14/ 目录,其中包含了完整的 JDK 1.5 被映射到 JavaDoc 注解的说明。
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 脚本的额外说明:
- 必须返回一个boolean值。除了这个约束以外,任何有效的 BeanShell 代码都是允许的 (例如,可能需要在工作日返回 true 而在周末返回 false,这样就允许你可以依照不同的日期进行测试)。
- TestNG 定义了如下的变量供你调用:
java.lang.reflect.Method method: 当前的测试方法
org.testng.ITestNGMethod testngMethod: 当前测试方法的描述
java.util.Map<String, String> groups: 当前测试方法所属组的映射 - 你也可能需要使用 CDATA 声明来括起Beanshell表达式(就像上例)来避免对XML保留字冗长的引用。
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会尝试通过类来组织方法)。
为了能够让你更好的控制第二种类别,TestNG定义如下接口:
public interface IMethodInterceptor { List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context); }
方法中叫做methods的那个列表参数包含了所有以不定序运行的方法。你的 intercept 方法也要返回一个IMethodInstance列表,它可能是下面情况之一:
- 内容与参数中接收的一致,但是顺序不同
- 一组 IMethodInstance 对象
- 更大的一组 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().getMethod().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 - 从JavaDoc 注解迁移到JDK 注解
如果项目伊始使用了javadoc annotations,但是之后要转换到JDK annotations,这样需要把所有这些都进行转换。
TestNG 提供了一个工具来帮你做这些。
java org.testng.AnnotationConverter -srcdir directory [-overwrite|-d destdir] [-quiet]
上述命令会把源文件从一个格式转换到另外一个。如果不带参数运行,就会显示所有参数的用法。
注意,转换后的文件,可能不是非常漂亮,所以要检查一下其正确性。所以最好用版本控制工具,如果不喜欢可以撤销操作!转换器本身也假设你使用了上面 所推荐的语法规约。
5.18 - TestNG 监听器
有很多接口可以用来修改 TestNG 的行为。这些接口都被统称为 "TestNG 监听器"。下面是目前支持的监听器的列表:
- IAnnotationTransformer (doc, javadoc)
- IReporter (doc, javadoc)
- ITestListener (doc, javadoc)
- IMethodInterceptor (doc, javadoc)
- IInvokedMethodListener (doc, javadoc)
当你实现了这些接口,你可以让 TestNG 知道这些接口,有如下方式:
- 在命令行下使用 -listener
- ant中使用 <listeners>
- 在 testng.xml 中使用 <listeners> 标签
下面是第三种方式的例子:
<suite> <listeners> <listener class-name="com.example.MyListener" /> <listener class-name="com.example.MyMethodInterceptor" /> </listeners> ...
5.19 - 依赖注入
TestNG 允许你在自己的方法中声明额外的参数。这时,TestNG会自动使用正确的值填充这些参数。依赖注入就使用在这种地方:
- 任何 @Before 或 @Test 方法可以声明一个类型为 ITestContext 的参数。
- 任何 @After 都可以声明一个类型为 ITestResult 的单数,它代表了刚刚运行的测试方法。
- 任何 @Before 和 @After 方法都能够声明类型为 XmlTest 的参数,它包含了当前的 <test> 参数。
- 任何 @BeforeMethod 可以声明一个类型为 java.lang.reflect.Method 的参数。这个参数会接收@BeforeMethod 完成调用的时候马上就被调用的那个测试方法当做它的值。
- 任何 @BeforeMethod 可以声明一个类型为 Object[] 的参数。这个参数会包含要被填充到下一个测试方法中的参数的列表,它既可以又 TestNG 注入,例如 java.lang.reflect.Method 或者来自 @DataProvider。
- 任何 @DataProvider 可以声明一个类型为 ITestContext 或 java.lang.reflect.Method 的参数。后一种类型的参数,会收到即将调用的方法作为它的值。
5.20 - 监听方法调用
无论何时TestNG即将调用一个测试(被@Test注解的)或者配置(任何使用 @Before or @After 注解标注的方法),监听器 IInvokedMethodListener 都可以让你得到通知。你所要做的就是实现如下接口:
public interface IInvokedMethodListener extends ITestNGListener { void beforeInvocation(IInvokedMethod method, ITestResult testResult); void afterInvocation(IInvokedMethod method, ITestResult testResult); }
并且就像在 关于TestNG监听器一节 中所讨论的那样,将其声明为一个监听器。