测试框架TestNG学习笔记

发布时间 2023-12-12 11:34:00作者: hqq的进阶日记

一、TestNG的基本介绍和如何在maven中引用

1、适合测试人员使用的原因
1)比Junit涵盖功能更全面的测试框架
2)Junit更适合隔离性比较强的单元测试
3)TestNG更适合复杂的集成测试

2、TestNG的使用
pom.xml的依赖包将信息都填写好,maven框架将自动下载和使用。

二、TestNG基本注解与执行顺序实战

1、Idea创建一个项目和对应的modle,目录结构如下:

2.1 注解实战 @Test标签

1、创建包 com.course.testng
2、创建类 BasicAnnotation.java

package com.course.testng;
import org.testng.annotations.Test;

public class BasicAnnotation{
    // 最基本的注解,用来把方法标记成测试用例
    @Test
    public void testCase1(){
        System.out.println("这是测试用例1");
    }
}

1)@Test 需要 alt+enter 键,add ‘testng’ to classpath
pom.xml文件会自动增加依赖:

2)@Test 需要导入包
import org.testng.annotations.Test;

3、运行结果

@Test是最基本的注解,用来把方法标记成测试用例

2.2 注解实战 BeforeMethod和AfterMethod

1、在测试方法之前运行的标签 @BeforeMethod
2、在测试方法之后运行的标签 @AfterMethod

3、多个测试方法执行

说明:如果有多个测试方法,在每一个测试方法之前和之后,BeforeMethod、AfterMethod都运行一次。

2.3 注解实战BeforeClass和AfterClass

1、在测试类之前运行的标签 @BeforeClass
2、在测试类之后运行的标签 @AfterClass

说明:BeforeClass 和 AfterClass在类中,只运行一次。
注册对象,静态方法,变量赋值等都可以用到。

2.4 注解实战:BeforeSuite和AfterSuit

1、BeforeSuite 在整个测试套件开始之前运行
2、AfterSuite 在整个测试套件结束之后运行

说明:BeforeSuit 和 AfterSuit 在类中,只运行一次。

  • 关注点在于执行顺序:
  • beforeSuite --> beforeClass --> beforeMethod --> 测试方法1 --> afterMethod --> beforeMethod --> 测试方法2 --> afterMethod --> afterClass --> afterSuite

2.5 Before/After注解总结

代码举例:

package com.course.test;
import org.testng.annotations.*;

public class BasicAnnotation {

    @Test
    public void testCase1(){
        System.out.println("这是测试用例1");
    }

    @Test
    public void testCase2(){
        System.out.println("这是测试用例2");
    }

    @BeforeMethod
    public void beforeMethod(){
        System.out.println("beforeMethod在测试方法运行之前运行");
    }

    @AfterMethod
    public void afterMethod(){
        System.out.println("afterMethod在测试方法运行之后运行");
    }

    @BeforeClass
    public void beforeClass(){
        System.out.println("beforeClass在类运行之前运行");
    }

    @AfterClass
    public void afterClass(){
        System.out.println("afterClass在类运行之后运行");
    }

    @BeforeSuite
    public void beforeSuite(){
        System.out.println("beforeSuite在类运行之前运行");
    }

    @AfterSuite
    public void afterSuite(){
        System.out.println("afterSuite在类运行之后运行");
    }
}

结果:

beforeSuite在类运行之前运行
beforeClass在类运行之前运行
beforeMethod在测试方法运行之前运行
这是测试用例1
afterMethod在测试方法运行之后运行
beforeMethod在测试方法运行之前运行
这是测试用例2
afterMethod在测试方法运行之后运行
afterClass在类运行之后运行
afterSuite在类运行之后运行

三、套件测试

测试套件是用于测试软件程序的行为或一组行为的测试用例的集合。在TestNG中,我们无法定义一个套件,但它可以由一个XML文件表示,因为套件是执行的功能。它还允许灵活配置要运行的测试。 套件可以包含一个或多个测试,并由标记定义。

  • 是testng.xml的根标记。 它描述了一个测试套件,它又由几个部分组成。
  • 接收的所有定义的合法属性。

3.1 创建suite包

创建类:suiteConfig.java、LoginTest.java、PayTest.java

package com.course.test.suite;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;

// 测试套件之前需要运行的方法
public class SuiteConfig {
    @BeforeSuite
    public void beforeSuite(){
        System.out.println("before Suite运行了");
    }

    @AfterSuite
    public void afterSuite(){
        System.out.println("after Suite运行了");
    }

    @BeforeTest
    public void beforeTest(){
        System.out.println("beforeTest");
    }

    @AfterTest
    public void afterTest(){
        System.out.println("afterTest");
    }
}
package com.course.test.suite;
import org.testng.annotations.Test;

// 写测试类
public class LoginTest {
    @Test
    public void loginTaobao(){
        System.out.println("淘宝登录成功!");
    }
}
package com.course.test.suite;
import org.testng.annotations.Test;

public class PayTest {
    @Test
    public void paySuccess(){
        System.out.println("淘宝支付成功!");
    }
}

3.2 在 resources中创建suite.xml

目录:src/main/resources/suite.xml

<?xml version="1.0" encoding="UTF-8" ?>

<suite name="test">
    <test name="login">
        <classes>
            <class name="com.course.test.suite.SuiteConfig" />
            <class name="com.course.test.suite.LoginTest"/>
        </classes>
    </test>

    <test name="pay">
        <classes>
            <class name="com.course.test.suite.SuiteConfig" />
            <class name="com.course.test.suite.PayTest"/>
        </classes>
    </test>
</suite>
  • 说明:使用suite.xml文件,该文件用来管理测试用例,并与运行testNG。
    套件就是将所有测试类整理在一起,形成一套测试用例
    测试集是指测试模块,一般一个项目可以按照模块分几部分,即不同的test
    测试集下的所有测试类
    具体测试类,name 属性指定测试类的路径
    测试类下具体的测试方法,如果不写此标签,则默认包含测试类下的所有方法

3.3 运行suite.xml文件

运行结果:

说明:
测试套件test中,有2个测试模块login和pay。
login模块下的测试类是 SuiteConfig.java和LoginTest.java,执行该测试类下所有方法。
pay模块下的测试类是 SuiteConfig.java和PayTest.java,执行该测试类下所有方法。
由于suite.xml只有一个测试套件test,所以beforeSuite()/afterSuite()只运行一次。beforeTest()/afterTest()针对每个测试方法都执行一次。测试方法 loginTaobao()、paySuccess() 分别执行一次。

四、忽略测试

1、什么时忽略测试:本次测试执行不想执行该用例,或者编写的代码没有准备就绪,并且测试用例要测试该方法/代码是否成功或失败。
使用注释 @Test(enable=false) 禁用此测试用例,绕过该测试用例。

2、新建 IgnoreTest.java

package com.course.testng;
import org.testng.annotations.Test;

public class IgnoreTest {
    @Test
    public void ignore1(){
        System.out.println("ignore1 执行!");
    }

    @Test(enabled = false)
    public void ignore2(){
        System.out.println("ignore2 执行!");
    }

    @Test(enabled = true)
    public void ignore3(){
        System.out.println("ignore3 执行!");
    }
}

执行结果:

ignore1 执行!
ignore3 执行!

说明: @Test(enabled = false) 的用例会被忽略

五、分组测试

分组测试允许你将方法调度到适当的部分,并执行复杂的测试方法分组。
它不仅可以声明属于某个分组的方法,还可以指定包含其他组的组。然后调用 TestNG,并要求其包含一组特定的组(或正则表达式),同时排除另一个分组。
组测试提供了如何分区测试的最大灵活性,如果您想要背靠背运行两组不同的测试,则不需要重新编译任何内容。
使用 标记在testng.xml文件中指定分组。 它可以在标签下找到。
标签中指定分组适用于其下的所有标签。

5.1 分组测试-方法分组测试

1、新建组groups,新建类 GroupsOnMethod.java

package com.course.testng.groups;
import org.testng.annotations.AfterGroups;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;

public class GroupsOnMethod {
    @Test(groups = "server")
    public void test1(){
        System.out.println("这是服务端组的测试方法1111");
    }

    @Test(groups = "server")
    public void test2(){
        System.out.println("这是服务端组的测试方法2222");
    }

    @Test(groups = "client")
    public void test3(){
        System.out.println("这是客户端组的测试方法3333");
    }

    @Test(groups = "client")
    public void test4(){
        System.out.println("这是客户端组的测试方法4444");
    }

    @BeforeGroups("server")
    public void beforeGroupsOnServer(){
        System.out.println("这是服务端组运行之前运行的方法:beforeGroupsOnServer");
    }

    @AfterGroups("server")
    public void afterGroupsOnServer(){
        System.out.println("这是服务端组运行之后运行的方法:afterGroupsOnServer");
    }

    @BeforeGroups("client")
    public void beforeGroupsOnClient(){
        System.out.println("这是客户端组运行之前运行的方法:beforeGroupsOnClient");
    }

    @AfterGroups("client")
    public void afterGroupsOnClient(){
        System.out.println("这是客户端组运行之后运行的方法:afterGroupsOnClient");
    }
}

结果:

这是服务端组运行之前运行的方法:beforeGroupsOnServer
这是服务端组的测试方法1111
这是服务端组的测试方法2222
这是服务端组运行之后运行的方法:afterGroupsOnServer
这是客户端组运行之前运行的方法:beforeGroupsOnClient
这是客户端组的测试方法3333
这是客户端组的测试方法4444
这是客户端组运行之后运行的方法:afterGroupsOnClient

说明:

  • 方法test1()、test2() 属于分组server,beforeGroupsOnServer()在server组的方法执行之前运行,afterGroupsOnServer()在server组的方法执行之后运行;
  • 方法test3()、test4() 属于分组client,beforeGroupsOnClient()在server组的方法执行之前运行,afterGroupsOnClient()在client组的方法执行之后运行。

5.2 分组测试中-类分组测试

1、新建组:groups,新建类 GroupsOnClass1.java、 GroupsOnClass2.java、GroupsOnClass3.java
其中 GroupsOnClass1.java和GroupsOnClass2.java是一个分组stu;GroupsOnClass3.java是一个分组teacher

package com.course.testng.groups;
import org.testng.annotations.Test;

@Test(groups = "stu")
public class GroupsOnClass1 {
    public void stu1(){
        System.out.println("GroupsOnClass1 中的stu1111运行");
    }

    public void stu2(){
        System.out.println("GroupsOnClass1 中的stu2222运行");
    }
}
package com.course.testng.groups;
import org.testng.annotations.Test;
@Test(groups = "stu")
public class GroupsOnClass2 {
    public void stu1(){
        System.out.println("GroupsOnClass2 中的stu1111运行");
    }

    public void stu2(){
        System.out.println("GroupsOnClass2 中的stu2222运行");
    }
}
package com.course.testng.groups;
import org.testng.annotations.Test;

@Test(groups = "teacher")
public class GroupsOnClass3 {
    public void teacher1(){
        System.out.println("GroupsOnClass3 中的teacher1111运行");
    }

    public void teacher2(){
        System.out.println("GroupsOnClass3 中的teacher2222运行");
    }
}

2、新建 groupsOnClass.xml

<?xml version="1.0" encoding="UTF-8" ?>

<suite name="suitename">
    <test name="runAll">
        <classes>
            <class name="com.course.testng.groups.GroupsOnClass1"/>
            <class name="com.course.testng.groups.GroupsOnClass2"/>
            <class name="com.course.testng.groups.GroupsOnClass3"/>
        </classes>
    </test>

    <test name="OnlyRunStu">
        <groups>
            <run>
                <include name="stu"/>
            </run>
        </groups>
        <classes>
            <class name="com.course.testng.groups.GroupsOnClass1"/>
            <class name="com.course.testng.groups.GroupsOnClass2"/>
            <class name="com.course.testng.groups.GroupsOnClass3"/>
        </classes>
    </test>
</suite>

说明:
测试套件suitename中有2个测试模块,runAllOnlyRunStu
runAll模块下的测试类是 GroupsOnClass1.java、GroupsOnClass2.java、GroupsOnClass3.java,执行该测试类下所有方法。
OnlyRunStu模块下的测试类是 GroupsOnClass1.java、GroupsOnClass2.java、GroupsOnClass3.java,只执行分组为 stu 的类下的方法。

3、运行xml文件,结果:

GroupsOnClass1 中的stu1111运行
GroupsOnClass1 中的stu2222运行
GroupsOnClass2 中的stu1111运行
GroupsOnClass2 中的stu2222运行
GroupsOnClass3 中的teacher1111运行
GroupsOnClass3 中的teacher2222运行
GroupsOnClass1 中的stu1111运行
GroupsOnClass1 中的stu2222运行
GroupsOnClass2 中的stu1111运行
GroupsOnClass2 中的stu2222运行

六、异常测试

1、什么时候会用到异常测试
在我们期望结果为某个异常时,比如:我们传入了某些不合法参数,程序抛出了异常。也就是说预期结果就是该异常。
2、运行时异常和非运行时异常

3、举例
1)新建 ExpectedException.java

package com.course.testng;
import org.testng.annotations.Test;

public class ExpectedException {
    // 这是一个结果会失败的异常
    @Test(expectedExceptions = RuntimeException.class)
    public void runTimeExceptionFailed(){
        System.out.println("这是一个失败的异常测试");
    }

    // 这是一个成功的异常测试
    @Test(expectedExceptions = RuntimeException.class)
    public void runTimeExceptionSuccess(){
        System.out.println("这是我的异常测试!");
        throw new RuntimeException();
    }
}

2)结果:两个用例,pass一个,fail一个

七、依赖测试

7.1 依赖测试

有时我们可能需要以特定顺序调用测试用例中的方法,或者希望在方法之间共享一些数据和状态。
TestNG支持这种依赖关系,因为它支持在测试方法之间显式依赖的声明。
TestNG允许指定依赖关系:

  • @Test注解中使用属性 dependsOnMethods
  • @Test注解中使用属性 dependsOnGroups
    在TestNG中,我们使用dependOnMethodsdependsOnGroups 来实现依赖测试。 如果依赖方法失败,则将跳过所有后续测试方法。
    举例:test1() 依赖于 test2()

7.2 dependsOnMethods

1、如果test1()成功,则执行test2()
新建 DependTest.java

package com.course.testng;
import org.testng.annotations.Test;

public class DependTest {
    @Test
    public void test1(){
        System.out.println("test1 run");
    }

    @Test(dependsOnMethods = {"test1"})
    public void test2(){
        System.out.println("test2 run");
    }
}

只运行 test2() ,结果:

2、如果test1()失败,则跳过test2()
修改test1(),使得其运行失败;test2 依赖的方法失败,自己也不会运行成功

package com.course.testng;
import org.testng.annotations.Test;

public class DependTest {
    @Test
    public void test1(){
        System.out.println("test1 run");
        throw new RuntimeException();
    }

    @Test(dependsOnMethods = {"test1"})
    public void test2(){
        System.out.println("test2 run");
    }
}

只运行 test2() ,结果:用例执行失败一个,忽略一个

7.3 dependsOnGroups

1、创建java文件:TestServer.java

package com.course.testng.depend;

import org.testng.annotations.Test;

@Test(groups = "deploy")
public class TestServer {
    @Test
    public void deployServer(){
        System.out.println("运行服务");
    }

    @Test(dependsOnMethods = "deployServer")
    public void deployBackUpServer(){
        System.out.println("如果方法deployServer()成功则运行");
    }
}

2、创建 TestDatabase.java

package com.course.testng.depend;

import org.testng.annotations.Test;

public class TestDatabase {

    @Test(groups = "db",dependsOnGroups = "deploy")
    public void initDB(){
        System.out.println("这是initDB方法");
    }

    @Test(dependsOnMethods = {"initDB"},groups = "db")
    public void testConnection(){
        System.out.println("这是testConnection方法");
    }
}

3、创建TestApp.java

package com.course.testng.depend;

import org.testng.annotations.Test;

public class TestApp {
    @Test(dependsOnGroups = {"deploy","db"})
    public void method1(){
        System.out.println("这是方法1");
    }

    @Test(dependsOnMethods = "method1")
    public void method2(){
        System.out.println("这是方法2");
    }
}

4、在resource文件下创建depend.xml

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="TestDependency">
    <test name="TestCase1">
        <classes>
            <class name="com.course.testng.depend.TestApp">
            </class>
            <class name="com.course.testng.depend.TestDatabase">
            </class>
            <class name="com.course.testng.depend.TestServer">
            </class>
        </classes>
    </test>
</suite>

5、执行结果:

八、参数化测试

8.1 参数化测试-xml文件参数化

1、如何通过外部或内部传递参数
2、创建包 parameter,创建类 ParameterTest.java

package com.course.testng.parameter;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class ParameterTest {
    @Test
    @Parameters({"name","age"})
    public void paramTest1(String name,String age){
        System.out.println("name = "+name+"; age = "+age);
    }
}

3、创建Parameter.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite">
    <test verbose="2" preserve-order="true" name="D:/testngDemo">
        <parameter name="name" value="tom"/>
        <parameter name="age" value="18"/>
        <classes>
            <class name="com.course.testng.parameter.ParameterTest"/>
        </classes>
    </test>
</suite>

4、运行xml文件,运行结果

8.2 参数化测试-DataProvider

1、第一种参数化方式其实比较鸡肋,第二种方式才是TestNG参数化的灵魂,用到了@DataProvider,它会返回一个二维数组:

package org.example;

import org.testng.Assert;
import org.testng.annotations.*;
 
public class AppTest {
    // 定义一个数据提供器,叫test01,返回二维数组
    @DataProvider(name = "test01")
    public Object[][] data() {
        return new Object[][] {
                {"tom", 18},
                {"jack", 20}
        };
    }
    
    // 引用这个数据提供器
    @Test(dataProvider = "test01")
    public void test01(String name, int age){
        System.out.println(name + ": " + age);
    }
}

结果:

切记:

  • @DataProvider用于生产数据,name是唯一标识。
  • 在@Test中通过dataProvider属性指定name。
  • 测试方法的入参跟数组中元素一一对应。

2、Iterator
@DataProvide的返回值(参数类型)除了已经提到的Object[][],还可以时Iterator,它不会一次性生成所有数据,而是每调用一次生成一次,节约内存。

package com.course.testng.parameter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.Iterator;

public class ParameterTest3 {
    @DataProvider(name = "test01")
    public Iterator<Object[]> data(){
        Object[][] myObject = new Object[][]{
                {"tom",18},
                {"jack",20}
        };
        return Arrays.asList(myObject).iterator();
    }
    @Test(dataProvider = "test01")
    public void test01(String name,int age){
        System.out.println(name+":"+age);
    }
}

结果:

总结:DataProvider支持数组、迭代器

3、通过方法名传递参数

package com.course.testng.parameter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.lang.reflect.Method;

public class ParameterTest4 {
    @Test(dataProvider = "methodData")
    public void test1(String name,int age){
        System.out.println("test1方法 name="+name+";age="+age);
    }

    @Test(dataProvider = "methodData")
    public void test2(String name,int age){
        System.out.println("test2方法 name="+name+";age="+age);
    }

    @DataProvider(name = "methodData")
    public Object[][] methodDataTest(Method method){
        Object[][] result = null;
        if (method.getName().equals("test1")){
            result = new Object[][]{
                    {"hqq1",18},
                    {"hqq2",19}
            };
        }else if (method.getName().equals("test2")){
            result = new Object[][]{
                    {"guihua1",20},
                    {"guihua2",22}
            };
        }
        return  result;
    }
}

结果:
test1方法 name=hqq1;age=18
test1方法 name=hqq2;age=19
test2方法 name=guihua1;age=20
test2方法 name=guihua2;age=22

九、多线程测试

实现testng多线程的两种方式:

  • 注解实现
  • xml实现

9.1 注解实现

package com.course.testng.thread;
import org.testng.annotations.Test;

public class MultiThreadByAnnotation {
    /**
     * threadPoolSize 为线程池内可使用的线程数
     * 使用threadPoolSize个线程,将test方法执行invocationCount次
     * timeOut配置的是每次执行该测试方法所耗费时间的阈值,超过阈值则测试失败
     */
    @Test(invocationCount = 10,threadPoolSize = 3,timeOut = 1000)
    public void test(){
        System.out.println("hello");
        System.out.println("Thread Id:"+Thread.currentThread().getId());
    }
}

执行结果:使用了3个线程,将测试方法test执行了10次。

hello
hello
hello
Thread Id:12
Thread Id:13
Thread Id:14
hello
hello
Thread Id:12
hello
Thread Id:14
Thread Id:13
hello
Thread Id:12
Thread Id:14
hello
hello
Thread Id:13
hello
Thread Id:12

9.2 xml实现test、class、method级别的并发

1)需要在 testng.xml中suite tag下设置

<suite name="Testng Parallel Test" parallel="tests" thread-count="5">
<suite name="Testng Parallel Test" parallel="classes" thread-count="5">
<suite name="Testng Parallel Test" parallel="methods" thread-count="5">

他们的共同点都是最多起5个线程去同时执行不同的用例。thread-count 代表最大并发线程数。
他们的区别如下:

  • method级别:所有用例都可以在不同的线程去执行
  • class级别:不同class tag 下的用例,在不同的线程执行;相同class tag下的用例只能在同一个线程中执行
  • tests级别:不同test tag 下的用例,在不同的线程执行;相同test tag下的用例只能在同一个线程中执行
    意义:可以将非线程安全的测试类或group统一放到一个test中,这样在并发的同时又可以保证这些类里面的用例是单线程执行。也可以根据需要设定class级别的并发,让同一个测试类里的用例在同一个线程中执行。
    补充:xml文件配置这种方式不能指定线程池,只有方法上才可以指定线程池。

2)举例实现1:methods

package com.course.testng.thread;
import org.testng.annotations.Test;

public class MultiThreadByXml {
    @Test
    public void test1(){
        System.out.println("test1---Thread Id :  " + Thread.currentThread().getId());
    }

    @Test
    public void test2(){
        System.out.println("test2---Thread Id :  " + Thread.currentThread().getId());
    }

    @Test
    public void test3(){
        System.out.println("test3---Thread Id :  " + Thread.currentThread().getId());
    }
}

新增multiThread.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<!--parallel="methods",表示多线程级别为方法级别-->
<!--thread-count="2",表示线程数为2-->
<suite name="thread" parallel="methods" thread-count="2">
    <test name="demo1">
        <classes>
            <class name="com.course.testng.thread.MultiThreadByXml"/>
        </classes>
    </test>
</suite>

执行xml文件:

说明:所有用例都可以在不同的线程去执行。

3)举例实现2:classes

新增 MultiThreadByXml.java、MultiThreadByXml2.java、MultiThreadByXml3.java

package com.course.testng.thread;
import org.testng.annotations.Test;

public class MultiThreadByXml {
    @Test
    public void test1(){
        System.out.println("test1---Thread Id :  " + Thread.currentThread().getId());
    }
    @Test
    public void test2(){
        System.out.println("test2---Thread Id :  " + Thread.currentThread().getId());
    }
    @Test
    public void test3(){
        System.out.println("test3---Thread Id :  " + Thread.currentThread().getId());
    }
}
package com.course.testng.thread;
import org.testng.annotations.Test;

public class MultiThreadByXml2 {
    @Test
    public void test1(){
        System.out.println("test21---Thread Id :  " + Thread.currentThread().getId());
    }
    @Test
    public void test2(){
        System.out.println("test22---Thread Id :  " + Thread.currentThread().getId());
    }
    @Test
    public void test3(){
        System.out.println("test23---Thread Id :  " + Thread.currentThread().getId());
    }
}
package com.course.testng.thread;
import org.testng.annotations.Test;

public class MultiThreadByXml3 {
    @Test
    public void test1(){
        System.out.println("test31---Thread Id :  " + Thread.currentThread().getId());
    }
    @Test
    public void test2(){
        System.out.println("test32---Thread Id :  " + Thread.currentThread().getId());
    }
    @Test
    public void test3(){
        System.out.println("test33---Thread Id :  " + Thread.currentThread().getId());
    }
}

说明:改xml的配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<!--parallel="classes",表示多线程级别为class-->
<!--thread-count="2",表示线程数为2-->
<suite name="thread" parallel="classes" thread-count="2">
    <test name="demo1">
        <classes>
            <class name="com.course.testng.thread.MultiThreadByXml"></class>
            <class name="com.course.testng.thread.MultiThreadByXml2"></class>
        </classes>
    </test>

    <test name="demo2">
        <classes>
            <class name="com.course.testng.thread.MultiThreadByXml3"></class>
        </classes>
    </test>
</suite>

结果:

说明:不同class tag下的用例可以在不同线程中执行;同一个class tag标签下的用例只能在同一个线程中执行。

4)举例实现3:tests

代码同上一个例子,只改xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<!--parallel="tests",表示多线程级别为test级别-->
<!--thread-count="2",表示线程数为2-->
<suite name="thread" parallel="tests" thread-count="2">
    <test name="demo1">
        <classes>
            <class name="com.course.testng.thread.MultiThreadByXml"></class>
        </classes>
    </test>

    <test name="demo2">
        <classes>
            <class name="com.course.testng.thread.MultiThreadByXml2"></class>
        </classes>
    </test>
</suite>

说明:不同test tag下的用例可以在不同的线程执行,相同test tag下的用例只能在同一个线程中执行。

9.3 多线程总结

参考文档:https://testerhome.com/topics/7895
1、多线程并发执行
必须要指出的是,通过多线程执行用例时虽然可以大大提升用例的执行效率,但是我们在设计用例时也要考虑到这些用例是否适合并发执行,以及要注意多线程方式的通病:线程安全与共享变量的问题。建议是在测试代码中,尽可能地避免使用共享变量。如果真的用到了,要慎用 synchronized 关键字来对共享变量进行加锁同步。否则,难免你的用例执行时可能会出现不稳定的情景(经常听到有人提到用例执行地不稳定,有时 100% 通过,有时只有 90% 通过,猜测可能有一部分原因也是这个导致的)。

2、不同级别的并发
通常,在 TestNG 的执行中,测试的级别由上至下可以分为suite -> test -> class -> method,箭头的左边元素跟右边元素的关系是一对多的包含关系。
这里的 test 指的是 testng.xml 中的 test tag,而不是测试类里的一个@Test。测试类里的一个@Test实际上对应这里的 method。所以我们在使用@BeforeSuite、@BeforeTest、@BeforeClass、@BeforeMethod这些标签的时候,它们的实际执行顺序也是按照这个级别来的。
1)suite
一般情况下,一个 testng.xml 只包含一个 suite。如果想起多个线程执行不同的 suite,官方给出的方法是:通过命令行的方式来指定线程池的容量。
java org.testng.TestNG -suitethreadpoolsize 3 testng1.xml testng2.xml testng3.xml
即可通过三个线程来分别执行 testng1.xml、testng2.xml、testng3.xml。
实际上这种情况在实际中应用地并不多见,我们的测试用例往往放在一个 suite 中,如果真需要执行不同的 suite,往往也是在不同的环境中去执行,届时也自然而然会做一些其他的配置(如环境变量)更改,会有不同的进程去执行。因此这种方式不多赘述。
2)test, class, method
test,class,method 级别的并发,可以通过在 testng.xml 中的 suite tag 下设置,如:


它们的共同点都是最多起 5 个线程去同时执行不同的用例。
它们的区别如下:

  • tests 级别:不同 test tag 下的用例可以在不同的线程执行,相同 test tag 下的用例只能在同一个线程中执行。
  • classs 级别:不同 class tag 下的用例可以在不同的线程执行,相同 class tag 下的用例只能在同一个线程中执行。
  • methods 级别:所有用例都可以在不同的线程去执行。

十、超时测试

10.1 套件级别的超时测试示例

public class TimeoutSuite 
{
    @Test
    public void timeTestOne() throws InterruptedException {
        Thread.sleep(1000);
        System.out.println("Time test method one");
    }
    
    @Test
    public void timeTestTwo() throws InterruptedException {
        Thread.sleep(400);
        System.out.println("Time test method two");
    }
}

添加对应的testng.xml文件

<suite name="Time test Suite" time-out="500" verbose="1" >
  <test name="Timeout Test" >
    <classes>
      <class name="com.howtodoinjava.test.TimeoutSuite"/>
    </classes>
  </test>
</suite>

运行结果:

[TestNG] Running: C:\somepath\TestNGExamples\testng.xml

Time test method two

===============================================
Time test Suite
Total tests run: 2, Failures: 1, Skips: 0
===============================================

从测试结果中可以看出,只有timeTestTwo()被执行,因为它的执行时间少于testng.xml文件中定义的超时时间。
timeTestOne()执行被取消,因为执行完成所需的时间,超过配置的超时时间。

10.2 方法级别的超时测试

public class TimeoutSuite 
{
    @Test(timeOut=500)
    public void timeTestOne() throws InterruptedException {
        Thread.sleep(1000);
        System.out.println("Time test method one");
    }
    
    @Test(timeOut=500)
    public void timeTestTwo() throws InterruptedException {
        Thread.sleep(400);
        System.out.println("Time test method two");
    }
}

执行结果:

[[TestNG] Running: C:\Users\somepath\testng-customsuite.xml
Time test method two
PASSED: timeTestTwo
FAILED: timeTestOne

org.testng.internal.thread.ThreadTimeoutException: Method org.testng.internal.TestNGMethod.timeTestOne() didn't finish within the time-out 500

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

说明:@Test(timeOut=xxx) 指定超时时间,单位为毫秒。

十一、软断言和硬断言

TestNG提供两种断言方式,分别是硬断言或或者软断言,类似于pytest里面的断言和多重断言。
硬断言时Assert直接调用静态方法,软断言需要实例化,才能调用断言方法。
硬断言(Assert):当一个测试用例中存在多个断言,当有一个断言失败时,则会抛出异常,不再执行该用例中后续的断言;
软断言(SoftAssert):当一个测试用例中存在多个断言,当有一个断言失败时,会执行后续的断言。

11.1 硬断言

Assert.assertEquals(actual,expected)            查看两个对象是否相等;类似于字符串比较实用equals()方法
Assert.assertNotEquals(actual,expected)    查看两个对象是否不相等;
Assert.assertNull(object)                              查看对象是否为空;
Assert.assertNotNull(object)                查看对象是否不为空;
Assert.assertSame(actual,expected)        查看两个对象的引用是否相等,类似于使用“==”比较两个对象
Assert.assertNotSame(actual,expected)    查看两个对象的引用是否不相等,类似于使用“!=”比较两个对象
Assert.assertTrue(condition)                判断条件是否为true
Assert.assertFalse(condition)                判断条件是否为false
assertArrayEquals(actual,expected)        判断两个数组是否相等。
Assert.fail();                            让测试用例失败

11.2 软断言

SoftAssert的特点:

  1. 如果一个断言失败,会继续执行这个断言下的其他语句或者断言
  2. 也就是一个用例有多个断言,失败了其中一个,不影响其他断言的运行
  3. 千万不要忘记在该用例的最后一个断言后面调用assertAll(),否则断言不执行

总结
在实际的测试工作中,一个测试用例中往往包含多个断言,更适合用软断言,一个断言失败,不影响其他的断言。可能会有疑问,不管是软断言还是硬断言,当用例失败时,都需要我们人为的去检查错误,认为两者是没有区别的。
其实还是有区别的,硬断言报错时只知道当前断言异常,并不知道后续断言是否成功。只调整了当前的断言,之后再次运行脚本,后续断言还有出错的概率,需要反复运行确定断言的正确性,很是耽误测试时间,影响工作效率。而软断言呢?可以知道所有失败的断言,可以统一进行调整。