Java-单元测试框架:TestNG详解(对比JUnit)

Java中常用测试框架为JUnit和TestNG,两个框架都能提供断言、执行、报告等能力。

  • 单元测试:对软件中的最小可测试单元进行测试,比如方法、类等
  • JUnit:适合传统的、独立的、无依赖的单元测试。
  • TestNG:更适合测试场景,尤其是需要数据驱动、测试依赖、分组、并发等复杂功能的自动化测试项目。

1、TestNG 基本结构与执行顺序

执行顺序:@BeforeSuite -> @BeforeTest -> @BeforeClass -> @BeforeMethod -> @Test -> @AfterMethod -> @AfterClass -> @AfterTest -> @AfterSuite

import org.testng.annotations.*;

public class TestNGStructureDemo {

    @BeforeSuite
    public void beforeSuite() { System.out.println("1. BeforeSuite - 整个套件前执行一次"); }

    @BeforeTest
    public void beforeTest() { System.out.println("2. BeforeTest - 整个测试前执行"); }

    @BeforeClass
    public void beforeClass() { System.out.println("3. BeforeClass - 当前类前执行一次"); }

    @BeforeMethod
    public void beforeMethod() { System.out.println("4. BeforeMethod - 每个测试方法前执行"); }

    @Test
    // 标记方法为测试方法,标记类为测试类(测试类下的所有方法都是测试方法)
    public void testA() { System.out.println("5. Test - testA"); }
}

2、 数据驱动测试(@DataProvider - TestNG 核心优势)

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;

public class DataProviderDemo {
    // 1. 定义数据提供者,返回 Object[][] 
    @DataProvider(name = "userData")
    public Object[][] provideUserData() {
        return new Object[][] {
            {"admin", "password123", true},
            {"test", "wrong", false},
            {"user", "pass", false}
        };
    }

    // 2. 测试方法使用 dataProvider 属性引用数据
    @Test(dataProvider = "userData")
    public void testLogin(String username, String password, boolean expectedResult) {
        // 模拟登录验证逻辑
        boolean actualResult = loginService.login(username, password);
        assertEquals(actualResult, expectedResult);
    }
}

注意:如果@DataProvider和@Test在不同的类,需要通过dataProviderClass属性指定@DataProvider所在的类

  • @Test(dataProvider = "xx", dataProviderClass = xx.class)

4、测试依赖(dependsOnMethods)

public class DependencyDemo {
    @Test
    public void testLogin() {
        System.out.println("登录成功");
        // 假设登录成功,设置一个标志
    }

    @Test(dependsOnMethods = "testLogin")
    public void testQueryOrder() {
        System.out.println("查询订单(依赖登录)");
        // 依赖testLogin,只有 testLogin 成功,此方法才会执行
    }

    @Test(dependsOnMethods = {"testLogin", "testQueryOrder"})
    public void testLogout() {
        System.out.println("退出登录(依赖前两个)");
    }
}

5、分组测试(groups)

执行顺序:@BeforeSuite -> @BeforeTest -> @BeforeClass -> @BeforeGroups -> @Test

public class GroupDemo {
    @Test(groups = "smoke")
    public void testLogin() { System.out.println("冒烟测试:登录"); }

    @Test(groups = "regression")
    public void testUserSearch() { System.out.println("回归测试:用户搜索"); }

    @Test(groups = {"smoke", "regression"})
    public void testAddToCart() { System.out.println("同时属于冒烟和回归:加入购物车"); }

    @BeforeGroups(groups = "regression")
    // @BeforeGroups() 会在 regression组(groups = "regression") 第一个测试方法执行前自动运行
    // @BeforeGroups() 只会执行一次
    public void beforeRegression() { System.out.println("执行回归测试前的初始化工作..."); }
}

6、常用的断言

断言是测试的核心,用于验证实际结果是否与预期一致。TestNG 提供了丰富的断言方法,都在 org.testng.Assert 类中。

  • 带消息的断言(建议使用):Assert.assertEquals(actualName, expectedName, "用户名验证失败,期望值不匹配");
  • 软断言:org.testng.asserts.SoftAssert类,断言失败时,后续代码会继续执行,等待所有断言都执行完,再汇总报告失败。

 

断言方法作用示例
assertEquals(actual, expected) 判断两个值是否相等(调用 equals() 方法) Assert.assertEquals(actualSum, 100);
assertTrue(condition) 判断条件是否为 true Assert.assertTrue(isLoginSuccess);
assertNull(object) 判断对象是否为 null Assert.assertNull(user);
assertSame(actual, expected) 判断两个引用是否指向同一个对象== Assert.assertSame(obj1, obj2);
fail() 直接让测试失败,常用于标记“不该执行到的代码” Assert.fail("测试不应走到这里");

7、testng.xml 配置文件

testng.xml 是 TestNG 的核心配置文件,通过它可以灵活组织和执行测试,而无需修改代码。

属性作用示例值
name 套件/测试的名称 "测试套件"
parallel 并发执行级别 "methods"(方法级)、"classes""tests"
thread-count 并发线程数(配合 parallel 使用)

"5"

//使用5个线程并发执行

verbose 日志详细程度(0-10,越高越详细) "2"
preserve-order 是否保持测试方法在类中定义的顺序执行 "true" / "false"
group-by-instances 是否按实例分组 "true"
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="测试套件" parallel="methods" thread-count="5" verbose="2">
    
    <!-- 1. 定义全局参数 -->
    <parameter name="browser" value="chrome" />
    <parameter name="env" value="test" />

    <!-- 2. 监听器配置(可选项,用于日志、报告等) -->
    <listeners>
        <listener class-name="org.testng.reporters.XMLReporter" />
    </listeners>

    <!-- 3. 测试标签,可以包含多个测试 -->
    <test name="接口测试">
        <!-- 定义该测试范围内的参数 -->
        <parameter name="baseUrl" value="https://api-test.example.com" />
        
        <!-- 定义哪些包/类/方法要执行 -->
        <packages>
            <package name="com.example.api.tests" />
        </packages>
    </test>

    <test name="数据库验证测试">
        <parameter name="dbUrl" value="jdbc:mysql://test-db:3306" />
        
        <!-- 指定具体的测试类 -->
        <classes>
            <class name="com.example.db.UserDaoTest" />
            <class name="com.example.db.OrderDaoTest">
                <!-- 只执行类中特定方法,没有任何<include>,则默认执行所有,可以使用 <exclude> 来跳过特定方法 -->
                <methods>
                    <include name="testInsertOrder" />
                    <include name="testQueryOrder" />
<!-- 这里的 exclude没用,因为 testDeleteOrder 本来就没被 include,默认不会被执行 -->
                    <exclude name="testDeleteOrder" />
                </methods>
            </class>
        </classes>
    </test>

    <!-- 按分组执行 -->
    <test name="冒烟测试">
        <groups>
            <run>
                <include name="smoke" />
                <include name="login" />
                <exclude name="broken" />  <!--有必要,一个方法可能同时属于 smoke 和 broken 两个组 -->
            </run>
        </groups>
    <!-- 必须包含<pakages>或者<classes>,指定groups来自于哪个包或者类 -->
        <packages>
            <package name="com.example.*" />
        </packages>
    </test>
</suite>

8、在代码中接收 testng.xml 的参数:

import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class ParameterizedTest {
    @Test
    @Parameters({"browser", "env"})
    public void testWithParameters(String browser, String env) {
        System.out.println("当前浏览器:" + browser);
        System.out.println("当前环境:" + env);
        // 根据参数执行不同逻辑
    }
}

 运行方式

命令作用典型场景
mvn test

默认会执行符合命名规则的测试类中的 @Test 方法

1、默认命名规则(如 *Test.java*Tests.java)的测试类

本地开发验证、CI 全量回归测试
mvn test -Dtest=TestClassName 执行指定的测试类(支持通配符) 只运行某个模块的测试,或调试单个类
mvn test -Dgroups=smoke 执行指定分组的测试方法(按 groups 属性过滤) 只运行冒烟测试(groups = "smoke"

mvn test -Dsurefire.suiteXmlFiles=testng.xml

或IDE中运行testng.xml文件

执行指定的 testng.xml 配置文件(最常用,推荐)

--执行testng.xml中的测试方法

执行回归测试套件、多环境测试
mvn clean test 先清理 target 目录,再执行测试 确保完全重新编译和运行,避免缓存影响

Maven 执行测试依赖 maven-surefire-plugin 插件;在 pom.xml 中可以配置该插件,以指定 testng.xml 文件路径、测试报告格式等。

  • maven-surefire-plugin 插件中配置testng.xml文件路径:再执行 mvn test,会自动加载项目根目录下的 testng.xml 文件执行测试。
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.0.0-M7</version>
            <configuration>
                <suiteXmlFiles>
                    <suiteXmlFile>testng.xml</suiteXmlFile>
                </suiteXmlFiles>
            </configuration>
        </plugin>
    </plugins>
</build>

 

posted @ 2026-08-17 11:31  Fēngwèi  阅读(7)  评论(0)    收藏  举报