TestNG基础Skill testng-fundamentals

这个技能提供了TestNG测试框架的全面基础知识,涵盖注解、断言、测试生命周期和XML配置,适用于Java单元测试、集成测试和功能测试开发,关键词包括TestNG、Java测试、注解、断言、测试生命周期、XML配置、单元测试、集成测试。

测试 0 次安装 0 次浏览 更新于 3/25/2026

name: testng基础 user-invocable: false description: 用于处理TestNG注解、断言、测试生命周期和Java测试配置时使用。 allowed-tools: [Read, Write, Edit, Bash, Glob, Grep]

TestNG基础

掌握TestNG基础知识,包括注解、断言、测试生命周期和Java测试的XML配置。这个技能提供了专业TestNG开发所需的基本概念、模式和最佳实践的全面覆盖。

概述

TestNG是一个强大的Java测试框架,受JUnit和NUnit启发,设计用于覆盖更广泛的测试类别:单元测试、功能测试、端到端测试和集成测试。它支持注解、数据驱动测试、参数化和并行执行。

安装和设置

Maven配置

将TestNG添加到Maven项目:

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.9.0</version>
    <scope>test</scope>
</dependency>

配置Surefire插件用于TestNG:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.2.5</version>
    <configuration>
        <suiteXmlFiles>
            <suiteXmlFile>testng.xml</suiteXmlFile>
        </suiteXmlFiles>
    </configuration>
</plugin>

Gradle配置

将TestNG添加到Gradle项目:

dependencies {
    testImplementation 'org.testng:testng:7.9.0'
}

test {
    useTestNG()
}

核心注解

测试生命周期注解

TestNG提供了全面的生命周期注解:

import org.testng.annotations.*;

public class LifecycleTest {

    @BeforeSuite
    public void beforeSuite() {
        // 在整个测试套件之前运行一次
        System.out.println("Before Suite");
    }

    @AfterSuite
    public void afterSuite() {
        // 在整个测试套件之后运行一次
        System.out.println("After Suite");
    }

    @BeforeTest
    public void beforeTest() {
        // 在testng.xml中的每个<test>标签之前运行
        System.out.println("Before Test");
    }

    @AfterTest
    public void afterTest() {
        // 在testng.xml中的每个<test>标签之后运行
        System.out.println("After Test");
    }

    @BeforeClass
    public void beforeClass() {
        // 在类中第一个测试方法之前运行一次
        System.out.println("Before Class");
    }

    @AfterClass
    public void afterClass() {
        // 在类中最后一个测试方法之后运行一次
        System.out.println("After Class");
    }

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

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

    @Test
    public void testMethod() {
        System.out.println("Test Method");
    }
}

测试注解属性

@Test注解支持各种属性:

public class TestAttributesExample {

    @Test(description = "验证用户登录功能")
    public void testLogin() {
        // 带描述的测试
    }

    @Test(enabled = false)
    public void disabledTest() {
        // 此测试不会运行
    }

    @Test(priority = 1)
    public void firstTest() {
        // 首先运行(优先级越低,执行越早)
    }

    @Test(priority = 2)
    public void secondTest() {
        // 其次运行
    }

    @Test(groups = {"smoke", "regression"})
    public void groupedTest() {
        // 测试属于多个组
    }

    @Test(dependsOnMethods = {"testLogin"})
    public void testDashboard() {
        // 仅在testLogin通过时运行
    }

    @Test(dependsOnGroups = {"setup"})
    public void dependentTest() {
        // 仅在“setup”组中所有测试通过时运行
    }

    @Test(timeOut = 5000)
    public void timedTest() {
        // 如果超过5秒则失败
    }

    @Test(invocationCount = 3)
    public void repeatedTest() {
        // 运行3次
    }

    @Test(invocationCount = 100, threadPoolSize = 10)
    public void parallelRepeatedTest() {
        // 在10个线程上运行100次
    }

    @Test(expectedExceptions = IllegalArgumentException.class)
    public void exceptionTest() {
        throw new IllegalArgumentException("Expected");
    }

    @Test(expectedExceptions = RuntimeException.class,
          expectedExceptionsMessageRegExp = ".*invalid.*")
    public void exceptionWithMessageTest() {
        throw new RuntimeException("This is invalid input");
    }
}

断言

基本断言

TestNG提供了全面的断言方法:

import org.testng.Assert;
import org.testng.annotations.Test;

public class AssertionExamples {

    @Test
    public void testBasicAssertions() {
        // 相等性
        Assert.assertEquals(5, 5);
        Assert.assertEquals("hello", "hello");
        Assert.assertEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3});

        // 布尔值
        Assert.assertTrue(true);
        Assert.assertFalse(false);

        // 空检查
        Assert.assertNull(null);
        Assert.assertNotNull("value");

        // 相同引用
        String s1 = "test";
        String s2 = s1;
        Assert.assertSame(s1, s2);
        Assert.assertNotSame(new String("test"), new String("test"));
    }

    @Test
    public void testAssertionsWithMessages() {
        // 带自定义失败消息的断言
        Assert.assertEquals(5, 5, "值应该相等");
        Assert.assertTrue(true, "条件应该为真");
        Assert.assertNotNull("value", "值不应该为空");
    }

    @Test
    public void testCollectionAssertions() {
        // 数组断言
        String[] expected = {"a", "b", "c"};
        String[] actual = {"a", "b", "c"};
        Assert.assertEquals(actual, expected);

        // 无序比较
        String[] array1 = {"a", "b", "c"};
        String[] array2 = {"c", "a", "b"};
        Assert.assertEqualsNoOrder(array1, array2);
    }
}

软断言

软断言允许在失败前收集多个断言:

import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;

public class SoftAssertExample {

    @Test
    public void testWithSoftAssert() {
        SoftAssert softAssert = new SoftAssert();

        // 执行所有断言
        softAssert.assertEquals(1, 1, "第一次检查");
        softAssert.assertEquals(2, 3, "第二次检查 - 将失败");
        softAssert.assertTrue(false, "第三次检查 - 将失败");
        softAssert.assertNotNull(null, "第四次检查 - 将失败");

        // 在最后报告所有失败
        softAssert.assertAll();
    }
}

TestNG XML配置

基本testng.xml结构

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="My Test Suite" verbose="1">

    <test name="Unit Tests">
        <classes>
            <class name="com.example.tests.UserServiceTest"/>
            <class name="com.example.tests.ProductServiceTest"/>
        </classes>
    </test>

    <test name="Integration Tests">
        <packages>
            <package name="com.example.integration.*"/>
        </packages>
    </test>

</suite>

组配置

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Group Suite">

    <test name="Smoke Tests">
        <groups>
            <run>
                <include name="smoke"/>
            </run>
        </groups>
        <packages>
            <package name="com.example.tests.*"/>
        </packages>
    </test>

    <test name="Regression Tests">
        <groups>
            <run>
                <include name="regression"/>
                <exclude name="broken"/>
            </run>
        </groups>
        <packages>
            <package name="com.example.tests.*"/>
        </packages>
    </test>

</suite>

testng.xml中的参数

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parameterized Suite">

    <parameter name="browser" value="chrome"/>
    <parameter name="environment" value="staging"/>

    <test name="Chrome Tests">
        <parameter name="browser" value="chrome"/>
        <classes>
            <class name="com.example.tests.BrowserTest"/>
        </classes>
    </test>

    <test name="Firefox Tests">
        <parameter name="browser" value="firefox"/>
        <classes>
            <class name="com.example.tests.BrowserTest"/>
        </classes>
    </test>

</suite>

在测试中使用参数:

import org.testng.annotations.*;

public class BrowserTest {

    @Parameters({"browser", "environment"})
    @BeforeClass
    public void setUp(String browser, @Optional("production") String env) {
        System.out.println("Browser: " + browser);
        System.out.println("Environment: " + env);
    }

    @Test
    public void testBrowser() {
        // 测试实现
    }
}

测试组

定义和使用组

public class GroupExample {

    @BeforeGroups("database")
    public void setUpDatabase() {
        System.out.println("Setting up database");
    }

    @AfterGroups("database")
    public void tearDownDatabase() {
        System.out.println("Tearing down database");
    }

    @Test(groups = {"smoke", "frontend"})
    public void testHomePage() {
        System.out.println("Testing home page");
    }

    @Test(groups = {"smoke", "api"})
    public void testHealthEndpoint() {
        System.out.println("Testing health endpoint");
    }

    @Test(groups = {"regression", "database"})
    public void testDataPersistence() {
        System.out.println("Testing data persistence");
    }

    @Test(groups = {"slow", "integration"})
    public void testEndToEnd() {
        System.out.println("Testing end-to-end flow");
    }
}

组依赖

public class GroupDependencyExample {

    @Test(groups = {"init"})
    public void initializeSystem() {
        System.out.println("Initializing");
    }

    @Test(groups = {"init"})
    public void configureSystem() {
        System.out.println("Configuring");
    }

    @Test(dependsOnGroups = {"init"}, groups = {"core"})
    public void coreTest1() {
        System.out.println("Core test 1");
    }

    @Test(dependsOnGroups = {"init"}, groups = {"core"})
    public void coreTest2() {
        System.out.println("Core test 2");
    }

    @Test(dependsOnGroups = {"core"}, groups = {"final"})
    public void finalTest() {
        System.out.println("Final test");
    }
}

监听器

ITestListener

import org.testng.*;

public class TestListener implements ITestListener {

    @Override
    public void onTestStart(ITestResult result) {
        System.out.println("Starting: " + result.getName());
    }

    @Override
    public void onTestSuccess(ITestResult result) {
        System.out.println("Passed: " + result.getName());
    }

    @Override
    public void onTestFailure(ITestResult result) {
        System.out.println("Failed: " + result.getName());
        System.out.println("Reason: " + result.getThrowable().getMessage());
    }

    @Override
    public void onTestSkipped(ITestResult result) {
        System.out.println("Skipped: " + result.getName());
    }

    @Override
    public void onStart(ITestContext context) {
        System.out.println("Test suite starting: " + context.getName());
    }

    @Override
    public void onFinish(ITestContext context) {
        System.out.println("Test suite finished: " + context.getName());
    }
}

使用监听器

// 使用注解
@Listeners(TestListener.class)
public class MyTest {
    @Test
    public void testMethod() {
        // 测试实现
    }
}

或在testng.xml中:

<suite name="Suite">
    <listeners>
        <listener class-name="com.example.listeners.TestListener"/>
    </listeners>
    <test name="Test">
        <classes>
            <class name="com.example.tests.MyTest"/>
        </classes>
    </test>
</suite>

最佳实践

  1. 使用描述性测试名称 - 清晰命名测试以指示它们验证的内容
  2. 分组相关测试 - 使用组按功能或类型组织测试
  3. 避免测试依赖 - 尽可能保持测试独立
  4. 明智使用软断言 - 用于一个测试中的多个相关检查
  5. 配置超时 - 防止测试无限期挂起
  6. 使用BeforeClass/AfterClass - 用于昂贵的设置/清理操作
  7. 利用testng.xml - 用于套件级配置和组织
  8. 实现监听器 - 用于自定义报告和测试生命周期钩子
  9. 谨慎使用优先级 - 优先依赖声明而非优先级
  10. 记录测试目的 - 使用描述属性

常见陷阱

  1. 测试顺序依赖 - 依赖隐式测试执行顺序
  2. 共享可变状态 - 测试修改共享资源
  3. 缺少断言 - 测试没有验证
  4. 过于宽泛的组 - 组过于泛化而无用
  5. 循环依赖 - 测试相互依赖形成循环
  6. 长时间运行的测试 - 测试没有适当的超时
  7. 糟糕的失败消息 - 断言没有描述性消息
  8. 忽略测试失败 - 使用enabled=false隐藏失败测试
  9. 硬编码测试数据 - 未使用参数或数据提供者
  10. 缺少清理 - 未在@After方法中正确释放资源

何时使用此技能

  • 在新Java项目中设置TestNG
  • 使用TestNG编写单元和集成测试
  • 使用testng.xml配置测试套件
  • 使用组和依赖组织测试
  • 实现自定义测试监听器
  • 故障排除TestNG测试失败
  • 从JUnit迁移到TestNG
  • 培训团队成员TestNG基础知识