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>
最佳实践
- 使用描述性测试名称 - 清晰命名测试以指示它们验证的内容
- 分组相关测试 - 使用组按功能或类型组织测试
- 避免测试依赖 - 尽可能保持测试独立
- 明智使用软断言 - 用于一个测试中的多个相关检查
- 配置超时 - 防止测试无限期挂起
- 使用BeforeClass/AfterClass - 用于昂贵的设置/清理操作
- 利用testng.xml - 用于套件级配置和组织
- 实现监听器 - 用于自定义报告和测试生命周期钩子
- 谨慎使用优先级 - 优先依赖声明而非优先级
- 记录测试目的 - 使用描述属性
常见陷阱
- 测试顺序依赖 - 依赖隐式测试执行顺序
- 共享可变状态 - 测试修改共享资源
- 缺少断言 - 测试没有验证
- 过于宽泛的组 - 组过于泛化而无用
- 循环依赖 - 测试相互依赖形成循环
- 长时间运行的测试 - 测试没有适当的超时
- 糟糕的失败消息 - 断言没有描述性消息
- 忽略测试失败 - 使用enabled=false隐藏失败测试
- 硬编码测试数据 - 未使用参数或数据提供者
- 缺少清理 - 未在@After方法中正确释放资源
何时使用此技能
- 在新Java项目中设置TestNG
- 使用TestNG编写单元和集成测试
- 使用testng.xml配置测试套件
- 使用组和依赖组织测试
- 实现自定义测试监听器
- 故障排除TestNG测试失败
- 从JUnit迁移到TestNG
- 培训团队成员TestNG基础知识