1 TestNG简介

TestNG是Java中的一个测试框架,是一个目前很流行实用的单元测试框架,有完善的用例管理模块,配合Maven能够很方便管理依赖第三方插件。

TestNG消除了大部分的旧框架的限制,使开发人员能够编写更加灵活和强大的测试。 因为它在很大程度上借鉴了Java注解(JDK5.0引入的)来定义测试。例如,如果你在TestNG写好了一个测试方法,你只需要在这个方法的前面一行写上“@Test”,就相当于告诉TestNG, 这个是一个测试方法,只有添加了这个注解,才会被认为是一个测试用例,才会被执行。

详细使用说明请参考官方链接:https://testng.org/doc/index.html

2 TestNG优点

  1. TestNG可以生成HTML格式的测试报告。对测试结果的描述更加详细,方便定位错误。

  2. TestNG注解丰富,如@ExpectedExceptions、@DataProvider等。

  3. TestNG可以对测试用例进行分组或指定测试用例执行的先后顺序。JUnit 4测试的依赖性非常强,测试用例间有严格的先后顺序。前一个测试不成功,后续所有的依赖测试都会失败。TestNG 利用@Test 的dependsOnMethods属性来应对测试依赖性问题。某方法依赖的方法失败,可以设置它将被跳过,而不是标记为失败。

  4. TestNG可以通过Parallel属性设置并发测试。

  5. 对于n个不同参数组合的测试,JUnit 4要写n个测试用例。每个测试用例完成的任务基本是相同的,只是受测方法的参数有所改变。TestNG的数据参数化只需要一个测试用例,然后把所需要的参数加到TestNG的xml配置文件中。这样的好处是参数与测试代码分离,非程序员也可以修改参数,同时修改无需重新编译测试代码。

3 TestNG配置

新建/打开一个maven的工程

工程的pom.xml中前需要添加如下内容:

代码块

`<dependencies>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.8.8</version>
        <scope>test</scope>
    </dependency>
</dependencies>`

4 TestNG基本使用和运行
在/src/test/java下新建一个AAA类

一个简单的用例如下:

import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;public class AAA {
//注解,说明会调用testng
@Test
public void testHello() {
  //  System.currentTimeMillis: 自1970年1月1日0时起的毫秒数
assertTrue(System.currentTimeMillis()>100);
}
}


可以直接右键点击运行,也可以新增一个xml文件,运行xml文件(见5.1.3的例子以及6中xml配置):
在这里插入图片描述

查看运行结果:
在这里插入图片描述

5 TestNG注解

TestNG支持多种注解,可以进行各种组合,如下进行简单的说明:

@BeforeSuite

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

@AfterSuite

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

@BeforeClass

在调用当前类的第一个测试方法之前运行,注释方法仅运行一次

@AfterClass

在调用当前类的第一个测试方法之后运行,注释方法仅运行一次

@BeforeTest

注释的方法将在属于test标签内的类的所有测试方法运行之前运行

@AfterTest

注释的方法将在属于test标签内的类的所有测试方法运行之后运行

@BeforeGroups

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

@AfterGroups

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

@BeforeMethod

注释方法将在每个测试方法之前运行

@AfterMethod

注释方法将在每个测试方法之后运行

@DataProvider

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

@Factory

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

@Listeners

定义测试类上的侦听器

@Parameters

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

@Test

将类或方法标记为测试的一部分,此标记若放在类上,则该类所有公共方法都将被作为测试方法

5.1 @Test

5.1.1 忽略 @Test(enabled = false)

在方法前加@Test(enabled = false),不会运行此用例
在这里插入图片描述

5.1.2 预期异常 @Test(expectedExceptions = ClassName.class)

@ExpectedExceptions(value = ClassName.class) 不推荐使用

@Test(expectedExceptions = ClassName.class)
如果 ClassName 类抛出了异常,算测试通过,没有异常算测试不通过;

expectedExceptions的值也可以是一个数组:
@Test(expectedExceptions = {ClassName.class, ClassName2.class,… })

import org.testng.annotations.ExpectedExceptions;
import org.testng.annotations.Test;public class TestExpectedException {
    @Test
    public void testcase1(){
        throw new ExceptionInInitializerError();
    }@ExpectedExceptions(value = ExceptionInInitializerError.class)
    @Test
    public void testcase2(){
        throw new ExceptionInInitializerError();
    }@Test(expectedExceptions = ExceptionInInitializerError.class)
    public void testcase3(){
        throw new ExceptionInInitializerError();
    }}

结果:
在这里插入图片描述

5.1.2 超时 @Test(timeOut = 超时时间)

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

在这里插入图片描述

5.1.3 分组 @Test(groups = “组名”)

可以在xml中指定组运行。可以跨class。

import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;public class AAA {
    @Test(groups = "group1")
    public void test1() {
        System.out.println("第一组测试");
    }
    @Test(groups = "group2")
    public void test2() {
        System.out.println("第二组测试");
    }}

TestNG.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Default Suite">
    <test name="Test">
        <groups>
            <run>
                <include name="group1" />
            </run>
        </groups>
        <classes>
            <class name="AAA">
            </class>
        </classes>
    </test>
</suite> 

运行TestNG.xml后结果:

在这里插入图片描述

5.1.4 特定顺序执行测试用例 @Test(priority = 优先级)

如果不设置优先级,那在执行用例的时候,默认的执行顺序是按照测试方法名的字母顺序由小到大执行的。

如果一个测试集中有的用了priority参数有的没用,那优先会执行未设置priority的case,执行顺序按照首字母排序。

@Test(priority = 0/1/2/3/4/…),priority是int类型

按照数字大小顺序优先执行,优先执行1,然后是2…
在这里插入图片描述

5.1.5 依赖 @Test(dependsOnMethod = {“方法名称”})/@Test(dependsOnGroups = {“group名称”})

@Test(dependsOnMethod = {“方法名称”}),被依赖的方法优先于此方法执行,可以跨class,方法名称中写清路径

@Test(dependsOnGroups = {“group名称”}),依赖的组优先于此方法执行

依赖关系的规则是:优先被依赖的方法->没有依赖关系的方法->需要依赖关系的方法

如下图所示,按顺序应该是test1先执行,然后执行test2,添加了依赖关系之后test2先执行,再执行test1。
在这里插入图片描述

跳过失败的依赖:test2出现异常,test1被跳过

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;public class TestDependsOnMethods {@Test(dependsOnMethods = "test2")
    public void test1() {
        System.out.println("第一组测试");
    }
    @Test()
    public void test2() {
        throw new ExceptionInInitializerError();
    }
}

结果:
在这里插入图片描述

对于大型测试套件,TestNG 这种不标记为失败,而只是跳过的处理方法可以减轻很多压力。可以快速定位到是被跳过的方法所依赖的方法出了问题。

5.1.6 测试并发 @Test(invocationCount = 方法执行次数, threadPoolSize = 线程池大小,timeout=毫秒数)

invocationCount:要让这个方法执行的次数 (多线程执行的总次数)

threadPoolSize :要开启多少线程执行这个方法(多线程数)若线程数为1,则在一个线程里连续执行方法invocationCount次,若线程数大于invocationCount,则最多启动invocationCount个线程来执行测试方法。

timeout:执行超时时间

import org.testng.annotations.Test;
import java.lang.Thread;public class TestThread  {@Test(invocationCount = 8,threadPoolSize = 2)
    public void test1() throws InterruptedException {
        System.out.println("Current Thread Id: " + Thread.currentThread().getId());
        Thread.sleep(5000);
    }}

结果:
在这里插入图片描述

5.2 传参 @Parameters({“参数1”, “参数2”})

使用注解Parameters定义参数名称如下:



import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;public class AAA {
    @Test()
    @Parameters({"param1", "param2"})
    public void test1(String param1, int param2) {
        System.out.println("第一组测试,参数1:"+param1+"; 参数2:"+param2);
    }
    @Test()
    public void test2() {
        System.out.println("第二组测试");
    }

在TestNg.xml中配置参数值:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite" time-out="100">
    <test name="Test">
        <parameter name="param1" value="啦啦啦"></parameter>
        <parameter name="param2" value="10"></parameter>
        <classes>
            <class name="AAA">
            </class>
        </classes>
    </test>
</suite>

运行xml,结果如下:
在这里插入图片描述

TestNG 对TestNG.xml 的参数的类型指定的值会自动尝试转换:

String

int/Integer

boolean/Boolean

byte/Byte

char/Character

double/Double

float/Float

long/Long

short/Short

5.3 传参 @DataProvider(name = “provideNumbers”) & @Test(dataProvider = “provideNumbers”)

更多用法:https://www.cnblogs.com/beifucangqiong/p/11246988.html

@DataProvider(name = “provideNumbers”,dataProviderClass=类名.class) 是数据提供者,@Test(dataProvider = “provideNumbers”,parallel=true)是消费者


import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;public class AAA {
    //如果dataProvider和测试方法在一个类下,dataProviderClass可以不写
    @Test(dataProvider = "provideNumbers")
    public void test(int number, int expected) {
        Assert.assertEquals(number + 10, expected);
    }@DataProvider(name = "provideNumbers",parallel=false)
    public Object[][] provideData() {
      //三组测试数据
        return new Object[][] { { 10, 20 }, { 100, 110 }, { 200, 210 } };
    }}

直接运行,显示:

5.4 传参 @Factory

如果不使用@Factory,运行普通的被@Test标注的方法时,实际上是TestNG框架调用了该类的构造函数构造出一个对象,然后再执行对象的这个方法。

使用了@Factory后,可以看到被@Factory标注的方法返回了一个Object数组,数组中每一个元素是一个被测试类的对象。也就是说@Factory构造了多个被测试类对象,然后把每一个对象都传递给了TestNG框架,然后TestNG框架在分别执行这些对象中被@Test标注的方法。
通过上面的描述,我们就知道了@Factory可以帮助我们实现简单的数据驱动测试(根据测试数据,执行对应的程序)。

测试类代码:

Person.java

import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
public class Person{
    String name;
    int age;@Parameters({"name","age"})
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }@Test()
    public void say() {
        System.out.print("我是"+name+" ");
        if(age<18){
            System.out.println("我未成年");
        }else if(age>=18&&age<=45){
            System.out.println("我是青年人");
        }else if(age>45&&age<=60){
            System.out.println("我是中年人");
        }else if(age>60){
            System.out.println("我是老年人");
        }
    }
}

factory代码:

PersonFactory.java



import java.util.ArrayList;import org.testng.annotations.Factory;public class PersonFactory {
    @Factory
    public Object[] factory() {
        ArrayList<Person> testList = new ArrayList<Person>();
        Person tp = new Person("明明",10);
        testList.add(tp);
        Person tp2 = new Person("杨子",20);
        testList.add(tp2);
        Person tp3 = new Person("刘创",50);
        testList.add(tp3);
        Person tp4 = new Person("朱爷爷",70);
        testList.add(tp4);
        return testList.toArray();
    }
}

运行PersonFactory.java后结果:

5.5 @BeforeSuite->@BeforeTest->@BeforeClass->@BeforeMethod->@Test->@AfterMethod->@AfterClass->@AfterTest->@AfterSuite

@BeforeSuite->@BeforeTest->@BeforeClass->{@BeforeMethod->@Test->@AfterMethod}->@AfterClass->@AfterTest->@AfterSuite(其中{}内的与多少个@Test,就循环执行多少次)。

凡是在类方法中添加了 @Test 注解的就是我们需要测试的方法。

@BeforeMethod->@Test->@AfterMethod 需要在同一个class下。

Suite、Test在TestNG.xml中配置

代码:

TestNGClass1
Java


import org.testng.annotations.*;public class TestNGClass1 {
    @BeforeClass
    public void setUp(){
        System.out.println("beforeClass——>>>>>>来自TestNGClass1类");
    }
    @AfterClass
    public void tearDown(){
        System.out.println("afterClass——>>>>>>来自TestNGClass1类");
    }
    @BeforeMethod
    public void beforeMethod() {
        System.out.println("beforeMethod——>>>>>>来自TestNGClass1类>>>>beforeMethod");
    }
    @AfterMethod
    public void afterMethod() {
        System.out.println("afterMethod——>>>>>>来自TestNGClass1类>>>>afterMethod");
    }
    @Test()
    public void testAdd(){
        System.out.println("这是TestNGClass1类中的第一个测试方法------------->>>>>>");
    }
    @Test
    public void testMethod(){
        System.out.println("这是TestNGClass1类中的第二个测试方法------------->>>>>>");
    }@BeforeGroups
    public void BeforeGroups1(){
        System.out.println("BeforeGroups——>>>>>>来自TestNGClass1类");
    }
    @AfterGroups
    public void AfterGroups2(){
        System.out.println("AfterGroups——>>>>>>来自TestNGClass1类");
    }
}

TestNGClass2



import org.testng.annotations.*;public class TestNGClass2 {
    @BeforeSuite
    public void setUp(){
        System.out.println("BeforeSuite——>>>>>>来自TestNGClass2类");
    }
    @AfterSuite
    public void tearDown(){
        System.out.println("AfterSuite——>>>>>>来自TestNGClass2类");
    }
    @BeforeTest
    public void BeforeTest1(){
        System.out.println("BeforeTest——>>>>>>来自TestNGClass2类");
    }
    @AfterTest
    public void AfterTest2(){
        System.out.println("AfterTest——>>>>>>来自TestNGClass2类");
    }
​
​
}

TestNG.xml



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite1" time-out="100">
    <test name="Test">
        <classes>
            <class name="TestNGClass1">
            </class>
            <class name="TestNGClass2">
            </class>
        </classes></test>
</suite>

运行结果:

5.6 @BeforeGroups->groups->@AfterGroups

代码:

TestNGClass1
Java

import org.testng.annotations.*;public class TestNGClass1 {
    
    @Test(groups = "group1")
    public void testAdd(){
        System.out.println("这是TestNGClass1类中的第一个测试方法------------->>>>>>");
    }
    @Test
    public void testMethod(){
        System.out.println("这是TestNGClass1类中的第二个测试方法------------->>>>>>");
    }@BeforeGroups(groups = "group1")
    public void BeforeGroups1(){
        System.out.println("BeforeGroups——>>>>>>来自TestNGClass1类");
    }
    @AfterGroups(groups = "group1")
    public void AfterGroups2(){
        System.out.println("AfterGroups——>>>>>>来自TestNGClass1类");
    }
}

TestNG.xml
XML



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite1" time-out="100">
    <test name="Test">
        <groups>
            <run>
                <include name="group1" />
            </run>
        </groups>
        <classes>
            <class name="TestNGClass1">
            </class>
        </classes>
    </test>
</suite>

结果:

6 TestNG.xml中的配置

测试套件(Test Suite)是用于测试软件程序的行为或一组行为的测试用例的集合。 在TestNG中,我们无法在测试源代码中定义一个套件,但它可以由一个XML文件表示,因为套件是执行的功能。 它还允许灵活配置要运行的测试。 套件可以包含一个或多个测试,并由标记定义。是TestNG.xml的根标记。 它描述了一个测试套件,它又由几个部分组成。

下表列出了接受的所有定义的合法属性:

6.1 Suite 标签

name

必选项,套件的名字,将出现在reports里

name=“XXX”

suite名字

junit

是否执行Junit模式(识别setup()等)

junit=“true”

true和false,默认false

verbose

控制台输出的详细内容等级,0-10级(0无,10最详细)

verbose=“5”

0到10

parallel

是否在不同的线程并行进行测试,要与thread-count配套使用

parallel=“mehods”

methods、tests、classes

parent-module

和Guice框架有关,只运行一次,创建一个parent injector给所有guice injectors

guice-stage

和Guice框架有关

guice-stage=“DEVELOPMENT”

DEVELOPMENT,PRODUCTION,TOOL,默认"DEVELOPMENT"

configfailurepolicy

测试失败后是再次执行还是跳过,值skip和continue

configfailurepolicy=“skip”

skip、continue,默认skip

thread-count

与parallel配套使用,线程池的大小,决定并行线程数量

thread-count=“10”

整数,默认5

annotations

获取注解,值为javadoc时,使用JavaDoc的注释;否则用JDK5注释

annotations=“javadoc”

javadoc

time-out

设置parallel时,终止执行单元之前的等待时间(毫秒)

time-out=“10000”

整数,单位毫秒

skipfailedinvocationcounts

是否跳过失败的调用

skipfailedinvocationcounts=“true”

true和false,默认false

data-provider-thread-count

并发时data-provider的线程池数量

data-provider-thread-count=“5”

整数

object-factory

一个实现IObjectFactory接口的类,实例化测试对象

object-factory=“classname”

类名

allow-return-values

是否允许返回函数值

all-return-values=“true”

true和false

preserve-order

是否按照排序执行

preserve-order=“true”

true和false,默认true

group-by-instances

按照实例分组

group-by-instances=“true”

true和false,默认false

标签下面必须是标签,test 标签的合法属性有:

6.2 test 标签

name

此属性属于必须要有的,值可以自行设定,此名字会在testNG的报告中看到

verbose

此属性为指定testNG报告的详细程度,从0开始到10,其中10为最详细,默认生成的xml此属性值为1

threadPoolSize

该属性指定此test的线程池大小,为数字

invocationCount

该属性指定此test的运行次数,为数字

time-out

此属性用于指定超时时间,该suite下所有的用例的超时时间,范例如上面的代码所示

group-by-instances

此项用于那些有依赖的方法,且被依赖的对象有多个重载对象,因为如果是依赖方法,且该方法有多个重载方法,则默认是会将所有重载方法都跑完再运行被依赖方法,但有时候我们不想这样,则将此项设置为true即可

preserve-order

值可输入true或者false,如果为true,则用例执行会按照在xml中的顺序执行,否则会乱序执行,不添加此属性的话默认是按顺序执行的

6.3 group标签

此标签必然是在标签下的,用于标识哪些组会被用于测试或者被排除在测试之外,其同级必然要包含一个标签或者标签,用于指定groups来自于哪些包或者类;
如下即为包含一个group,排除一个group的例子:

代码块


<groups>
  <run>
     <include name = "group1" />
     <exclude name = "group2" />
  </run>
</groups>


6.4 样例
6.4.1 选择一个包中的全部测试脚本(包含子包)
xml

<suite>
<test name = "allTestsInAPackage" >
<packages>
  <package name = "whole.path.to.package.* />
</packages>
</test>
</suite>


6.4.2 选择一个类中的全部测试脚本

XML


<test name = "allTestsInAClass" >
   <classes>
  <class name="whole.path.to.package.className />
   </classes>
</test>


6.4.3 选择一个类中的部分测试脚本

XML



<test name = "aFewTestsFromAClass" >
   <classes>
  <class name="whole.path.to.package.className >
      <methods>
         <include name = "firstMethod" />
         <include name = "secondMethod" />
         <include name = "thirdMethod" />
      </methods>
  </class>
   </classes>
</test>


6.4.4 选择一个包中的某些组
代码块
XML



<test name = "includedGroupsInAPackage" >
   <groups>
      <run>
         <include name = "includedGroup" />
      </run>
   </groups>
   <packages>
      <package name = "whole.path.to.package.* />
   </packages>
</test>

6.4.5 排除一个包中的某些组

<test name = "excludedGroupsInAPackage" >
   <groups>
      <run>
         <exclude name = "excludedGroup" />
      </run>
   </groups>
   <packages>
      <package name = "whole.path.to.package.* />
   </packages>
</test>

7 生成测试报告配置
run-Edit Configurations-listeners-Use default reports打钩-Apply
在这里插入图片描述
在这里插入图片描述

运行代码后生成目录:
在这里插入图片描述

测试报告在index.xml
加粗样式
**

8 失败的测试方法重运行

在大型测试套件中,这种重新运行失败测试的能力显得尤为方便。这是 TestNG 独有的一个特性。在 JUnit 4 中,如果测试套件包括 1000 项测试,其中 3 项失败,很可能就会迫使您重新运行整个测试套件(修改错误以后)。这样的工作可能会耗费几个小时。一旦 TestNG 中出现失败,它就会创建一个 XML 配置文件,对失败的测试加以说明。如果利用这个文件执行 TestNG 运行程序,TestNG 就只运行失败的测试。
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐