TestNG的基本使用

TestNG的注释

@Test
被加注了@Test的方法,都是一个Method。

@BeforeMethod/@AfterMethod
此注释方法,会在所在测试类中所有Methods被调用前后运行一次,有多少Methods就会执行多少次。

@BeforeTest/@AfterTest
BeforeTest会在该测试类中没有一个Method被执行的时候执行,AfterTest会在所有Method执行完毕以后紧接着执行一次

@BeforeClass/@AfterClass
BeforeClass会在测试类中还没有任何一个方法被执行的时候执行,AfterClass会在测试类中所有方法都被执行过了以后执行(包括AfterTest)

@BeforeGroups/@AfterGroups
TestNG中Groups的概念是相对于测试方法而言的,将具有相似功能的测试方法分组,这样在定义测试用例的时候就可以以组为单位加入对应的测试方法。一般在定义测试方法或者测试类的同时,为其指定所属的组,一个测试方法可以属于多个组。

BeforeGroups将在之前运行组列表。此方法保证在调用属于这些组中的任何一个的第一个测试方法之前不久运行。

AfterGroups将在之后运行组列表。该方法保证在调用属于任何这些组的最后一个测试方法之后不久运行。

@BeforeSuite/@AfterSuite
Suite可以理解为一个测试套件,一个Suite中可以包含Groups、Class、Test。

BeforeSuite在该套件的所有测试都运行在注释的方法之前,仅运行一次。

@AfterSuite在该套件的所有测试都运行在注释方法之后,仅运行一次。

@DataProvider
标记一种方法来提供测试方法的数据。注释方法必须返回一个Object[][],其中每个Object[]可以被分配给测试方法的参数列表。 要从该DataProvider接收数据的@Test方法需要使用与此注释名称相等的dataProvider名称

@Factory
将一个方法标记为工厂,返回TestNG将被用作测试类的对象。该方法必须返回Object[]。

@Listeners
定义测试类上的侦听器。

@Parameters
描述如何将参数传递给@Test方法。

testng.xml书写

第一行是固定写法

1
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

一般要先声明一个suite的名字,用于描述将要运行的脚本集,比如:

1
2
3
4
5
<suite name="First suite" verbose="1" >
<test name="TestName" >
……
</test>
</suite>

如果测试脚本是基于group的,那么你就要描述是哪些,比如包含了什么、排除了什么,用include和exclude来表示。只要在include组中的方法都会被运行,exclude中的都不会,如果一个方法既在include又在exclude中,也会被运行。举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//比如有这样的一些测试方法,各自标注了属于什么分组
public class TestClass1 {
@Test(groups = {"group1", "group2"})
public void testMethod1() {
}

@Test(groups = {"group1"} )
public void testMethod2() {
}

@Test(groups = {"group2"})
public void testMethod3() {
}
}

//当然也可以在整个类之前加注group name,例如下面表示整个类中的方法都是属于checkin-test组的,其中method1又属于func-test组

@Test(groups = {"group3"})
public class TestClass2 {
@Test(groups = {"group4"})
public void testmethod4() { ... }
public void testmethod5() { ... }
}

<!-- testng.xml中定义如下 -->
<groups>
<run>
<include name = "group1" />
<exclude name = "group2" />
</run>
</groups>

这上述情况下testMethod1和testMethod2都会被运行,其余的没有被include也没有被exclude的,都不会被运行。上面是按照method所属group为依据来将测试方法分组,也可以从包、类、方法的角度来区分,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<suite name="First suite" verbose="1" >
<test name="TestName" >

<!-- 选择一个包 -->
<packages>
<package name = "packageName" />
</packages>

<!-- 选择一个类 -->
<classes>
<class name = "className" />
</classes>

<!-- 选择一个方法 -->
<classes>
<class name = "className" />
<methods>
<include name = "methodName" />
<exclude name = "methodName" />
</methods>
</class>
</classes>
</test>
</suite>

预期异常测试

假设有这样的测试场景:一个方法会抛出一个异常,我们需要另一个方法去获得此异常,并进行一些操作,如果能正常处理,也是一个通过运行的case。随便举个例(不一定符合真实场景):

1
2
3
4
5
6
7
@Test(expectedExceptions = {FileNotFoundException.class})
public void whatToThrow() throws FileNotFoundException {
File f = null;
if (f == null) {
throw new FileNotFoundException();
}
}

上面的运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
[RemoteTestNG] detected TestNG version 6.14.3
PASSED: whatToThrow

===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

忽略测试

如果一个测试方法还没写好或者不想运行,可以用注释的方法忽略它,这样就不会被执行,比如:

1
2
3
4
@Test(enabled = false)
public void test(){
……
}

超时测试

“超时”表示如果单元测试花费的时间超过指定的毫秒数,那么TestNG将会中止它并将其标记为失败。例如:

1
2
3
4
5
6
@Test(timeOut = 3000)
public void timeoutTest() {
while(true) {

}
}

这个方法运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FAILED: timeoutTest
org.testng.internal.thread.ThreadTimeoutException: Method com.test.api.ExceptionTest.timeoutTest() didn't finish within the time-out 3000 at
……
……

===============================================
Default test
Tests run: 1, Failures: 1, Skips: 0

===============================================


===============================================
Default suite
Total tests run: 1, Failures: 1, Skips: 0

===============================================

套件测试

套件测试无法在代码中定义,不像groups,但是它可以用一个xml文件表示,一个套件里面可以包含多个测试。是testng.xml的根标记,它的属性包含:

属性 描述
name 套件的名称,是强制属性
verbose 运行级别
parallel TestNG是否运行不同的线程来运行套件
thread-count 如果启用并行模式时所使用的线程数
annotations 测试中使用的注释类型
time-out 所有测试方法的默认超时时间

依赖测试

在@Test注释中使用dependsOnMethods和dependsOnGroups来进行依赖测试。只有所依赖的方法执行成功以后,才会执行此方法。比如:

1
2
3
4
5
6
7
8
9
10
//method2只有在method1执行成功以后才会被执行,否则就会忽略
@Test
public void method1() {
System.out.println("This is method 1");
}

@Test(dependsOnMethods = { "method1" })
public void method2() {
System.out.println("This is method 2");
}

参数化测试

可以用testng.xml将参数传给测试方法,比如在testng.xml中写了:

1
2
3
4
5
6
7
8
9
10
<suite>
<test name="testPara">
<parameter name="para1" value="value1"/>
<parameter name="para2" value="value2"/>

<classes>
<class name="com.test.api.ExceptionTest"/>
</classes>
</test>
</suite>

那么在ExceptionTest这个类的测试方法中就可以使用para1和para2这两个参数,要在方法前加注@Parameters,比如:

1
2
3
4
5
6
@Test
@Parameters({"para1", "para2"})
public void testPara(String para1, String para2) {
System.out.println(para1);
System.out.println(para2);
}

另一种传参的方式是通过@DataProvider,新建一个类,加注@DataProvider,要求必须返回一个Object[][]类型,使用这个返回值时,在@Test后面加上dataProvider的name,举个最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
@DataProvider(name = "provider")
public Object[][] provideData(){
return new Object[][] {{1, 1}, {1, 2}, {1, 3}};
}

@Test(dataProvider = "provider")
public void printData(int i, int j) {
System.out.println("i:" + i + "\t" + "j:" + j);
}
}

比如上述的例子,执行的结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[RemoteTestNG] detected TestNG version 6.14.3
i:1 j:1
i:1 j:2
i:1 j:3
PASSED: printData(1, 1)
PASSED: printData(1, 2)
PASSED: printData(1, 3)

===============================================
Default test
Tests run: 3, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================

@DataProvider要求必须返回一个Object[][]类型,所以如果是Map对象也是可以的,用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
@DataProvider(name = "provider1")
public Object[][] provideData1(){
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value2");
return new Object[][] {{map}};
}

@Test(dataProvider = "provider1")
public void printData1(Map<String, String> map) {
for(Map.Entry<String, String> entry : map.entrySet()){
System.out.println(entry.getKey() + "\t" + entry.getValue());
}
}
}

上面这个例子,测试的结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[RemoteTestNG] detected TestNG version 6.14.3
key1 value1
key2 value2
key3 value2
PASSED: printData1({key1=value1, key2=value2, key3=value2})

===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

如果一个DataProvider提供给多个test方法数据,也可以根据调用的方法名称,来提供不同的数据,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
@DataProvider(name = "provider2")
public Object[][] provideData2(Method method){
Object[][] obj = null;
if (method.getName() == "test1") {
obj = new Object[][] {{"case1", "valu1"}};
}else if (method.getName() == "test2") {
obj = new Object[][] {{"case2", "valu2"}};
}
return obj;
}

@Test(dataProvider = "provider2")
public void test1(String name, String value) {
System.out.println(name + "\t" + value);
}

@Test(dataProvider = "provider2")
public void test2(String name, String value) {
System.out.println(name + "\t" + value);
}
}

上面例子的执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[RemoteTestNG] detected TestNG version 6.14.3
case1 valu1
case2 valu2
PASSED: test1("case1", "valu1")
PASSED: test2("case2", "valu2")

===============================================
Default test
Tests run: 2, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

DataProvider还可以返回一个数组迭代器类型,我个人比较喜欢这种方式,比较容易理解,举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class TraverseCases extends TestApi{
//假设属性都已经定义好了(这里省略不写了)

@BeforeClass()
public void setUp() throws Exception {
//假设应该做的初始化工作都在这里做了(这里省略不写了)
}

@DataProvider(name="testData")
private Iterator<Object[]> testDataProvider() throws IOException {
List<Object[]> result= new ArrayList<Object[]>();
List<TestData> alldata = new ArrayList<TestData>();
TestData td = null;
for(int i = 1; i < excelData.length; i++) {
td = new TestData(i, excelData);
alldata.add(td);
}
Iterator it = alldata.iterator();
while(it.hasNext()){
result.add(new Object[] { it.next() });
}
return result.iterator();
}

@Test(dataProvider = "testData")
public void testDifferentCases(TestData testData) throws IOException {
//这里每一次传进来的都是迭代器取的下一个测试数据
if (testData != null) {
//这里你就可以对测试数据进行操作了(这里省略不写了)
}
}
}

上面这样的写法,执行的结果会是这样的,passed:方法名(数据来源):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PASSED: testDifferentCases(com.test.utils.TestData@6150c3ec)
PASSED: testDifferentCases(com.test.utils.TestData@139982de)
PASSED: testDifferentCases(com.test.utils.TestData@3eb738bb)
PASSED: testDifferentCases(com.test.utils.TestData@74e52ef6)
PASSED: testDifferentCases(com.test.utils.TestData@bcec361)
PASSED: testDifferentCases(com.test.utils.TestData@3d285d7e)
PASSED: testDifferentCases(com.test.utils.TestData@2aceadd4)
PASSED: testDifferentCases(com.test.utils.TestData@7817fd62)
PASSED: testDifferentCases(com.test.utils.TestData@77f1baf5)

===============================================
Default test
Tests run: 9, Failures: 0, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 9, Failures: 0, Skips: 0
===============================================

@Test中其余属性

除了上面提到的一些属性,其余还有:

@Test(alwaysRun = true) ,这个属性一般和dependsOnMethods一起用。如果这个属性设置了true,那么这个方法总是会被执行,哪怕它依赖的方法执行失败了,他也会被执行。如果它没有依赖的方法,那这个属性设置了也没用。不设置的情况下默认false。

@Test(description = “”) 这个属性是字面意思,如果设置了会出现在html报告里面,如果ourput等级大于等于2,也会出现。

@Test(ignoreMissingDependencies = true) 如果有依赖的方法,且该方法没有被执行,那这个方法也就被执行。

@Test(invocationCount = 2) 代表这个方法被执行多少次,默认是1次。

@Test(invocationTimeOut = 3000) 表示这个方法被执行的最高时长(一般是设置了执行次数,那么就表示执行这么多次总共能花的时间)。如果不设置执行次数,这个属性不会生效。如果超过了设置的时长还没有执行完,那么就当作方法failed。

@Test(skipFailedInvocations = true) 如果这个属性设置了true,且运行次数大于1,那么每次运行失败时,不标记成fail,而后跳过。

@Test(threadPoolSize = 4) 线程数,如果运行数没有设定的话,这个属性也不会生效。

TestNG的报告

个人认为报告这块不要话太多精力去装饰,如果不是需要上交报告给其他部门或者领导,只是自己看的话,直接明了就好。我的一个需求是执行方法的时候添加很多日志信息,不管成功失败,最后都方便查看追踪。只要适当添加一些log信息,我觉得这样的报告也够了。我在utils包里面添加了一个简单的类:

1
2
3
4
5
public class LogUtil {
public static void info(String msg) {
Reporter.log(msg.toString());
}
}

这样产生的报告里面就会有你想要打印的信息