testng-parallel
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTestNG Parallel Execution
TestNG 并行测试执行
Master TestNG parallel test execution including thread pool configuration, suite-level parallelism, method-level parallelism, and thread safety patterns. This skill covers techniques for maximizing test throughput while maintaining test reliability.
掌握TestNG并行测试执行的相关内容,包括线程池配置、套件级并行、方法级并行以及线程安全模式。本技能涵盖在保证测试可靠性的同时最大化测试吞吐量的技巧。
Overview
概述
TestNG supports parallel execution at multiple levels: suite, test, class, and method. Proper parallel configuration can significantly reduce test execution time, but requires careful consideration of thread safety and resource management.
TestNG支持多个层级的并行执行:套件、测试、类和方法。合理的并行配置可以显著缩短测试执行时间,但需要仔细考虑线程安全和资源管理问题。
Parallel Execution Modes
并行执行模式
Suite-Level Parallelism
套件级并行
Run multiple tags in parallel:
<test>xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Suite" parallel="tests" thread-count="3">
<test name="Chrome Tests">
<classes>
<class name="com.example.tests.BrowserTest"/>
</classes>
</test>
<test name="Firefox Tests">
<classes>
<class name="com.example.tests.BrowserTest"/>
</classes>
</test>
<test name="Safari Tests">
<classes>
<class name="com.example.tests.BrowserTest"/>
</classes>
</test>
</suite>并行运行多个标签:
<test>xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Suite" parallel="tests" thread-count="3">
<test name="Chrome Tests">
<classes>
<class name="com.example.tests.BrowserTest"/>
</classes>
</test>
<test name="Firefox Tests">
<classes>
<class name="com.example.tests.BrowserTest"/>
</classes>
</test>
<test name="Safari Tests">
<classes>
<class name="com.example.tests.BrowserTest"/>
</classes>
</test>
</suite>Class-Level Parallelism
类级并行
Run test classes in parallel:
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Classes" parallel="classes" thread-count="4">
<test name="All Tests">
<classes>
<class name="com.example.tests.UserServiceTest"/>
<class name="com.example.tests.ProductServiceTest"/>
<class name="com.example.tests.OrderServiceTest"/>
<class name="com.example.tests.PaymentServiceTest"/>
</classes>
</test>
</suite>并行运行测试类:
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Classes" parallel="classes" thread-count="4">
<test name="All Tests">
<classes>
<class name="com.example.tests.UserServiceTest"/>
<class name="com.example.tests.ProductServiceTest"/>
<class name="com.example.tests.OrderServiceTest"/>
<class name="com.example.tests.PaymentServiceTest"/>
</classes>
</test>
</suite>Method-Level Parallelism
方法级并行
Run test methods in parallel:
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Methods" parallel="methods" thread-count="5">
<test name="Service Tests">
<classes>
<class name="com.example.tests.IndependentMethodsTest"/>
</classes>
</test>
</suite>并行运行测试方法:
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Methods" parallel="methods" thread-count="5">
<test name="Service Tests">
<classes>
<class name="com.example.tests.IndependentMethodsTest"/>
</classes>
</test>
</suite>Instance-Level Parallelism
实例级并行
Run test instances in parallel (useful with Factory):
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Instances" parallel="instances" thread-count="3">
<test name="Factory Tests">
<classes>
<class name="com.example.tests.FactoryGeneratedTest"/>
</classes>
</test>
</suite>并行运行测试实例(结合Factory使用时很有用):
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Instances" parallel="instances" thread-count="3">
<test name="Factory Tests">
<classes>
<class name="com.example.tests.FactoryGeneratedTest"/>
</classes>
</test>
</suite>Thread Pool Configuration
线程池配置
Basic Thread Configuration
基础线程配置
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Thread Pool Suite" parallel="methods" thread-count="10">
<!-- Global thread pool configuration -->
<test name="Test Group 1" thread-count="5">
<!-- Override for this specific test -->
<classes>
<class name="com.example.tests.Test1"/>
</classes>
</test>
<test name="Test Group 2">
<!-- Uses suite-level thread-count -->
<classes>
<class name="com.example.tests.Test2"/>
</classes>
</test>
</suite>xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Thread Pool Suite" parallel="methods" thread-count="10">
<!-- 全局线程池配置 -->
<test name="Test Group 1" thread-count="5">
<!-- 针对此特定测试的覆盖配置 -->
<classes>
<class name="com.example.tests.Test1"/>
</classes>
</test>
<test name="Test Group 2">
<!-- 使用套件级别的thread-count -->
<classes>
<class name="com.example.tests.Test2"/>
</classes>
</test>
</suite>Data Provider Parallel Execution
数据提供器并行执行
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="DataProvider Suite" data-provider-thread-count="20">
<test name="Data Driven Tests">
<classes>
<class name="com.example.tests.ParallelDataProviderTest"/>
</classes>
</test>
</suite>java
public class ParallelDataProviderTest {
@DataProvider(name = "largeDataSet", parallel = true)
public Object[][] provideLargeDataSet() {
Object[][] data = new Object[100][2];
for (int i = 0; i < 100; i++) {
data[i] = new Object[]{"User" + i, "user" + i + "@example.com"};
}
return data;
}
@Test(dataProvider = "largeDataSet")
public void testWithParallelData(String name, String email) {
System.out.println(Thread.currentThread().getName() + " - Testing: " + name);
// Each data row runs in parallel
}
}xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="DataProvider Suite" data-provider-thread-count="20">
<test name="Data Driven Tests">
<classes>
<class name="com.example.tests.ParallelDataProviderTest"/>
</classes>
</test>
</suite>java
public class ParallelDataProviderTest {
@DataProvider(name = "largeDataSet", parallel = true)
public Object[][] provideLargeDataSet() {
Object[][] data = new Object[100][2];
for (int i = 0; i < 100; i++) {
data[i] = new Object[]{"User" + i, "user" + i + "@example.com"};
}
return data;
}
@Test(dataProvider = "largeDataSet")
public void testWithParallelData(String name, String email) {
System.out.println(Thread.currentThread().getName() + " - Testing: " + name);
// 每一行数据都会并行运行
}
}Thread Safety Patterns
线程安全模式
Thread-Local Storage
线程本地存储
java
import org.testng.annotations.*;
public class ThreadLocalTest {
// Thread-local storage for test-specific resources
private static ThreadLocal<WebDriver> driverThread = new ThreadLocal<>();
private static ThreadLocal<String> sessionThread = new ThreadLocal<>();
@BeforeMethod
public void setUp() {
// Initialize thread-local resources
driverThread.set(createWebDriver());
sessionThread.set(generateSessionId());
}
@AfterMethod
public void tearDown() {
// Clean up thread-local resources
WebDriver driver = driverThread.get();
if (driver != null) {
driver.quit();
}
driverThread.remove();
sessionThread.remove();
}
@Test
public void testParallelBrowser() {
WebDriver driver = driverThread.get();
String session = sessionThread.get();
System.out.println("Thread: " + Thread.currentThread().getName() +
" Session: " + session);
// Use thread-local driver
}
private WebDriver createWebDriver() {
// Create browser instance
return new ChromeDriver();
}
private String generateSessionId() {
return UUID.randomUUID().toString();
}
}java
import org.testng.annotations.*;
public class ThreadLocalTest {
// 用于存储测试专属资源的线程本地存储
private static ThreadLocal<WebDriver> driverThread = new ThreadLocal<>();
private static ThreadLocal<String> sessionThread = new ThreadLocal<>();
@BeforeMethod
public void setUp() {
// 初始化线程本地资源
driverThread.set(createWebDriver());
sessionThread.set(generateSessionId());
}
@AfterMethod
public void tearDown() {
// 清理线程本地资源
WebDriver driver = driverThread.get();
if (driver != null) {
driver.quit();
}
driverThread.remove();
sessionThread.remove();
}
@Test
public void testParallelBrowser() {
WebDriver driver = driverThread.get();
String session = sessionThread.get();
System.out.println("Thread: " + Thread.currentThread().getName() +
" Session: " + session);
// 使用线程本地的driver
}
private WebDriver createWebDriver() {
// 创建浏览器实例
return new ChromeDriver();
}
private String generateSessionId() {
return UUID.randomUUID().toString();
}
}Synchronized Shared Resources
同步共享资源
java
import org.testng.annotations.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ConcurrentHashMap;
public class SynchronizedResourceTest {
// Thread-safe counter
private static AtomicInteger testCounter = new AtomicInteger(0);
// Thread-safe collection
private static ConcurrentHashMap<String, String> sharedCache = new ConcurrentHashMap<>();
// Lock object for critical sections
private static final Object lock = new Object();
@Test(threadPoolSize = 5, invocationCount = 100)
public void testAtomicOperations() {
int count = testCounter.incrementAndGet();
System.out.println("Test count: " + count);
}
@Test(threadPoolSize = 3, invocationCount = 50)
public void testConcurrentMap() {
String threadName = Thread.currentThread().getName();
sharedCache.put(threadName, String.valueOf(System.currentTimeMillis()));
// Thread-safe without explicit synchronization
}
@Test(threadPoolSize = 2, invocationCount = 10)
public void testSynchronizedBlock() {
synchronized (lock) {
// Critical section - only one thread at a time
performCriticalOperation();
}
}
private void performCriticalOperation() {
// Operations that require exclusive access
}
}java
import org.testng.annotations.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ConcurrentHashMap;
public class SynchronizedResourceTest {
// 线程安全的计数器
private static AtomicInteger testCounter = new AtomicInteger(0);
// 线程安全的集合
private static ConcurrentHashMap<String, String> sharedCache = new ConcurrentHashMap<>();
// 用于临界区的锁对象
private static final Object lock = new Object();
@Test(threadPoolSize = 5, invocationCount = 100)
public void testAtomicOperations() {
int count = testCounter.incrementAndGet();
System.out.println("Test count: " + count);
}
@Test(threadPoolSize = 3, invocationCount = 50)
public void testConcurrentMap() {
String threadName = Thread.currentThread().getName();
sharedCache.put(threadName, String.valueOf(System.currentTimeMillis()));
// 无需显式同步即可保证线程安全
}
@Test(threadPoolSize = 2, invocationCount = 10)
public void testSynchronizedBlock() {
synchronized (lock) {
// 临界区 - 同一时间仅一个线程可进入
performCriticalOperation();
}
}
private void performCriticalOperation() {
// 需要独占访问的操作
}
}Immutable Test Data
不可变测试数据
java
import java.util.Collections;
import java.util.List;
import java.util.Arrays;
public class ImmutableDataTest {
// Immutable test data - inherently thread-safe
private static final List<String> TEST_USERS = Collections.unmodifiableList(
Arrays.asList("user1", "user2", "user3", "user4", "user5")
);
private static final Map<String, String> CONFIG = Collections.unmodifiableMap(
Map.of(
"url", "https://api.example.com",
"timeout", "30000",
"retries", "3"
)
);
@Test(threadPoolSize = 5, invocationCount = 20)
public void testWithImmutableData() {
// Safe to read from multiple threads
int userIndex = ThreadLocalRandom.current().nextInt(TEST_USERS.size());
String user = TEST_USERS.get(userIndex);
String url = CONFIG.get("url");
System.out.println(Thread.currentThread().getName() +
" - User: " + user + ", URL: " + url);
}
}java
import java.util.Collections;
import java.util.List;
import java.util.Arrays;
public class ImmutableDataTest {
// 不可变测试数据 - 天生线程安全
private static final List<String> TEST_USERS = Collections.unmodifiableList(
Arrays.asList("user1", "user2", "user3", "user4", "user5")
);
private static final Map<String, String> CONFIG = Collections.unmodifiableMap(
Map.of(
"url", "https://api.example.com",
"timeout", "30000",
"retries", "3"
)
);
@Test(threadPoolSize = 5, invocationCount = 20)
public void testWithImmutableData() {
// 可安全地从多个线程读取
int userIndex = ThreadLocalRandom.current().nextInt(TEST_USERS.size());
String user = TEST_USERS.get(userIndex);
String url = CONFIG.get("url");
System.out.println(Thread.currentThread().getName() +
" - User: " + user + ", URL: " + url);
}
}Test Isolation Patterns
测试隔离模式
Independent Test Methods
独立测试方法
java
public class IndependentTestsExample {
// Each test method is completely independent
@Test
public void testFeatureA() {
// Create its own resources
UserService service = new UserService();
User user = service.createUser("testA");
assertNotNull(user);
// Clean up
service.deleteUser(user.getId());
}
@Test
public void testFeatureB() {
// Completely separate from testFeatureA
ProductService service = new ProductService();
Product product = service.createProduct("testB");
assertNotNull(product);
service.deleteProduct(product.getId());
}
@Test
public void testFeatureC() {
// No shared state with other tests
OrderService service = new OrderService();
Order order = service.createOrder();
assertNotNull(order);
service.cancelOrder(order.getId());
}
}java
public class IndependentTestsExample {
// 每个测试方法完全独立
@Test
public void testFeatureA() {
// 创建自己的资源
UserService service = new UserService();
User user = service.createUser("testA");
assertNotNull(user);
// 清理资源
service.deleteUser(user.getId());
}
@Test
public void testFeatureB() {
// 与testFeatureA完全隔离
ProductService service = new ProductService();
Product product = service.createProduct("testB");
assertNotNull(product);
service.deleteProduct(product.getId());
}
@Test
public void testFeatureC() {
// 与其他测试无共享状态
OrderService service = new OrderService();
Order order = service.createOrder();
assertNotNull(order);
service.cancelOrder(order.getId());
}
}Isolated Database Tests
隔离的数据库测试
java
import org.testng.annotations.*;
public class IsolatedDatabaseTest {
private Connection connection;
private String testSchema;
@BeforeMethod
public void setUp() throws SQLException {
// Create isolated schema for each test
testSchema = "test_" + Thread.currentThread().getId() + "_" + System.currentTimeMillis();
connection = DriverManager.getConnection(DB_URL, USER, PASSWORD);
connection.createStatement().execute("CREATE SCHEMA " + testSchema);
connection.setCatalog(testSchema);
initializeTestData();
}
@AfterMethod
public void tearDown() throws SQLException {
// Drop isolated schema
connection.createStatement().execute("DROP SCHEMA " + testSchema + " CASCADE");
connection.close();
}
@Test
public void testDatabaseOperation1() throws SQLException {
// Operations in isolated schema
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO users (name) VALUES (?)"
);
ps.setString(1, "TestUser1");
ps.executeUpdate();
ResultSet rs = connection.createStatement().executeQuery(
"SELECT COUNT(*) FROM users"
);
rs.next();
assertEquals(rs.getInt(1), 1);
}
@Test
public void testDatabaseOperation2() throws SQLException {
// Completely isolated from testDatabaseOperation1
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO products (name) VALUES (?)"
);
ps.setString(1, "TestProduct");
ps.executeUpdate();
}
private void initializeTestData() throws SQLException {
// Create tables in isolated schema
}
}java
import org.testng.annotations.*;
public class IsolatedDatabaseTest {
private Connection connection;
private String testSchema;
@BeforeMethod
public void setUp() throws SQLException {
// 为每个测试创建隔离的schema
testSchema = "test_" + Thread.currentThread().getId() + "_" + System.currentTimeMillis();
connection = DriverManager.getConnection(DB_URL, USER, PASSWORD);
connection.createStatement().execute("CREATE SCHEMA " + testSchema);
connection.setCatalog(testSchema);
initializeTestData();
}
@AfterMethod
public void tearDown() throws SQLException {
// 删除隔离的schema
connection.createStatement().execute("DROP SCHEMA " + testSchema + " CASCADE");
connection.close();
}
@Test
public void testDatabaseOperation1() throws SQLException {
// 在隔离的schema中执行操作
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO users (name) VALUES (?)"
);
ps.setString(1, "TestUser1");
ps.executeUpdate();
ResultSet rs = connection.createStatement().executeQuery(
"SELECT COUNT(*) FROM users"
);
rs.next();
assertEquals(rs.getInt(1), 1);
}
@Test
public void testDatabaseOperation2() throws SQLException {
// 与testDatabaseOperation1完全隔离
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO products (name) VALUES (?)"
);
ps.setString(1, "TestProduct");
ps.executeUpdate();
}
private void initializeTestData() throws SQLException {
// 在隔离的schema中创建表
}
}Parallel Execution with Dependencies
带依赖的并行执行
Preserving Order Within Groups
保留组内执行顺序
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Ordered Parallel" parallel="classes" thread-count="3">
<test name="Ordered Test" preserve-order="true">
<classes>
<!-- Classes run in parallel, methods in order -->
<class name="com.example.tests.OrderedTest1">
<methods>
<include name="step1"/>
<include name="step2"/>
<include name="step3"/>
</methods>
</class>
<class name="com.example.tests.OrderedTest2"/>
</classes>
</test>
</suite>xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Ordered Parallel" parallel="classes" thread-count="3">
<test name="Ordered Test" preserve-order="true">
<classes>
<!-- 类并行运行,方法按顺序执行 -->
<class name="com.example.tests.OrderedTest1">
<methods>
<include name="step1"/>
<include name="step2"/>
<include name="step3"/>
</methods>
</class>
<class name="com.example.tests.OrderedTest2"/>
</classes>
</test>
</suite>Group Threading
实例分组线程
xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Group Threading" parallel="methods" thread-count="4" group-by-instances="true">
<test name="Instance Grouped">
<classes>
<class name="com.example.tests.InstanceGroupTest"/>
</classes>
</test>
</suite>java
public class InstanceGroupTest {
private String instanceId;
@Factory
public Object[] createInstances() {
return new Object[] {
new InstanceGroupTest("instance1"),
new InstanceGroupTest("instance2"),
new InstanceGroupTest("instance3")
};
}
public InstanceGroupTest() {}
public InstanceGroupTest(String instanceId) {
this.instanceId = instanceId;
}
@Test
public void step1() {
System.out.println(instanceId + " - Step 1");
}
@Test(dependsOnMethods = "step1")
public void step2() {
System.out.println(instanceId + " - Step 2");
}
@Test(dependsOnMethods = "step2")
public void step3() {
System.out.println(instanceId + " - Step 3");
}
}xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Group Threading" parallel="methods" thread-count="4" group-by-instances="true">
<test name="Instance Grouped">
<classes>
<class name="com.example.tests.InstanceGroupTest"/>
</classes>
</test>
</suite>java
public class InstanceGroupTest {
private String instanceId;
@Factory
public Object[] createInstances() {
return new Object[] {
new InstanceGroupTest("instance1"),
new InstanceGroupTest("instance2"),
new InstanceGroupTest("instance3")
};
}
public InstanceGroupTest() {}
public InstanceGroupTest(String instanceId) {
this.instanceId = instanceId;
}
@Test
public void step1() {
System.out.println(instanceId + " - Step 1");
}
@Test(dependsOnMethods = "step1")
public void step2() {
System.out.println(instanceId + " - Step 2");
}
@Test(dependsOnMethods = "step2")
public void step3() {
System.out.println(instanceId + " - Step 3");
}
}Performance Optimization
性能优化
Optimal Thread Count
最优线程数
java
public class ThreadCountOptimization {
// Determine optimal thread count based on available resources
public static int getOptimalThreadCount() {
int availableProcessors = Runtime.getRuntime().availableProcessors();
// For CPU-bound tests
int cpuBoundThreads = availableProcessors;
// For I/O-bound tests (network, file, database)
int ioBoundThreads = availableProcessors * 2;
// For mixed workloads
int mixedThreads = (int) (availableProcessors * 1.5);
return mixedThreads;
}
}java
public class ThreadCountOptimization {
// 根据可用资源确定最优线程数
public static int getOptimalThreadCount() {
int availableProcessors = Runtime.getRuntime().availableProcessors();
// 针对CPU密集型测试
int cpuBoundThreads = availableProcessors;
// 针对I/O密集型测试(网络、文件、数据库)
int ioBoundThreads = availableProcessors * 2;
// 针对混合工作负载
int mixedThreads = (int) (availableProcessors * 1.5);
return mixedThreads;
}
}Resource Pool Pattern
资源池模式
java
import java.util.concurrent.*;
public class ResourcePoolTest {
// Connection pool for parallel tests
private static BlockingQueue<Connection> connectionPool;
@BeforeSuite
public void setUpSuite() {
int poolSize = 10;
connectionPool = new ArrayBlockingQueue<>(poolSize);
for (int i = 0; i < poolSize; i++) {
connectionPool.offer(createConnection());
}
}
@AfterSuite
public void tearDownSuite() {
Connection conn;
while ((conn = connectionPool.poll()) != null) {
closeConnection(conn);
}
}
@Test(threadPoolSize = 5, invocationCount = 50)
public void testWithPooledConnection() throws InterruptedException {
Connection conn = connectionPool.take(); // Borrow
try {
// Use connection
performDatabaseOperation(conn);
} finally {
connectionPool.offer(conn); // Return
}
}
private Connection createConnection() {
// Create database connection
return null;
}
private void closeConnection(Connection conn) {
// Close connection
}
private void performDatabaseOperation(Connection conn) {
// Database operations
}
}java
import java.util.concurrent.*;
public class ResourcePoolTest {
// 用于并行测试的连接池
private static BlockingQueue<Connection> connectionPool;
@BeforeSuite
public void setUpSuite() {
int poolSize = 10;
connectionPool = new ArrayBlockingQueue<>(poolSize);
for (int i = 0; i < poolSize; i++) {
connectionPool.offer(createConnection());
}
}
@AfterSuite
public void tearDownSuite() {
Connection conn;
while ((conn = connectionPool.poll()) != null) {
closeConnection(conn);
}
}
@Test(threadPoolSize = 5, invocationCount = 50)
public void testWithPooledConnection() throws InterruptedException {
Connection conn = connectionPool.take(); // 借用连接
try {
// 使用连接
performDatabaseOperation(conn);
} finally {
connectionPool.offer(conn); // 归还连接
}
}
private Connection createConnection() {
// 创建数据库连接
return null;
}
private void closeConnection(Connection conn) {
// 关闭连接
}
private void performDatabaseOperation(Connection conn) {
// 数据库操作
}
}Reporting for Parallel Tests
并行测试报告
Custom Reporter for Parallel Execution
并行执行的自定义报告器
java
import org.testng.*;
import java.util.concurrent.ConcurrentHashMap;
public class ParallelTestReporter implements ITestListener {
private static ConcurrentHashMap<Long, List<String>> threadTestMap =
new ConcurrentHashMap<>();
@Override
public void onTestStart(ITestResult result) {
long threadId = Thread.currentThread().getId();
threadTestMap.computeIfAbsent(threadId, k -> new CopyOnWriteArrayList<>())
.add(result.getName());
}
@Override
public void onFinish(ITestContext context) {
System.out.println("\n=== Thread Distribution Report ===");
threadTestMap.forEach((threadId, tests) -> {
System.out.println("Thread " + threadId + ": " + tests.size() + " tests");
tests.forEach(test -> System.out.println(" - " + test));
});
System.out.println("Total threads used: " + threadTestMap.size());
}
}java
import org.testng.*;
import java.util.concurrent.ConcurrentHashMap;
public class ParallelTestReporter implements ITestListener {
private static ConcurrentHashMap<Long, List<String>> threadTestMap =
new ConcurrentHashMap<>();
@Override
public void onTestStart(ITestResult result) {
long threadId = Thread.currentThread().getId();
threadTestMap.computeIfAbsent(threadId, k -> new CopyOnWriteArrayList<>())
.add(result.getName());
}
@Override
public void onFinish(ITestContext context) {
System.out.println("\n=== 线程分布报告 ===");
threadTestMap.forEach((threadId, tests) -> {
System.out.println("线程 " + threadId + ": " + tests.size() + " 个测试");
tests.forEach(test -> System.out.println(" - " + test));
});
System.out.println("使用的总线程数: " + threadTestMap.size());
}
}Timeout Configuration
超时配置
java
public class TimeoutTest {
@Test(timeOut = 5000)
public void testWithTimeout() {
// Fails if takes more than 5 seconds
}
@Test(timeOut = 10000, threadPoolSize = 3, invocationCount = 10)
public void testParallelWithTimeout() {
// Each invocation has 10 second timeout
}
}xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Timeout Suite" time-out="60000">
<!-- Suite-level timeout: 60 seconds total -->
<test name="Quick Tests" time-out="10000">
<!-- Test-level timeout: 10 seconds for all tests in this group -->
<classes>
<class name="com.example.tests.QuickTest"/>
</classes>
</test>
</suite>java
public class TimeoutTest {
@Test(timeOut = 5000)
public void testWithTimeout() {
// 如果执行时间超过5秒则失败
}
@Test(timeOut = 10000, threadPoolSize = 3, invocationCount = 10)
public void testParallelWithTimeout() {
// 每次调用都有10秒超时时间
}
}xml
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Timeout Suite" time-out="60000">
<!-- 套件级超时:总时长60秒 -->
<test name="Quick Tests" time-out="10000">
<!-- 测试级超时:此组中所有测试的超时时间为10秒 -->
<classes>
<class name="com.example.tests.QuickTest"/>
</classes>
</test>
</suite>Best Practices
最佳实践
- Design for independence - Tests should not depend on shared mutable state
- Use ThreadLocal for per-thread resources - Drivers, sessions, connections
- Prefer immutable data - Thread-safe by design
- Set appropriate thread counts - Based on resource availability
- Implement proper cleanup - Prevent resource leaks in parallel execution
- Use thread-safe collections - ConcurrentHashMap, CopyOnWriteArrayList
- Configure timeouts - Prevent hung tests from blocking threads
- Monitor thread distribution - Ensure balanced workload
- Test locally first - Verify thread safety before CI/CD
- Document thread safety requirements - Clear expectations for test authors
- 按独立原则设计 - 测试不应依赖共享的可变状态
- 为每个线程的资源使用ThreadLocal - 驱动、会话、连接
- 优先使用不可变数据 - 天生线程安全
- 设置合适的线程数 - 基于资源可用性
- 实现正确的清理逻辑 - 防止并行执行中的资源泄漏
- 使用线程安全的集合 - ConcurrentHashMap、CopyOnWriteArrayList
- 配置超时时间 - 防止挂起的测试阻塞线程
- 监控线程分布 - 确保工作负载均衡
- 先在本地测试 - 在CI/CD之前验证线程安全
- 记录线程安全要求 - 为测试作者明确预期
Common Pitfalls
常见陷阱
- Shared mutable state - Causes race conditions and flaky tests
- Static fields without synchronization - Not thread-safe
- Resource contention - Too many threads competing for limited resources
- Order dependencies - Tests that assume execution order
- Missing cleanup - ThreadLocal resources not removed
- Insufficient isolation - Database tests affecting each other
- Too many threads - Overhead exceeds benefits
- Ignoring timeouts - Hung tests blocking execution
- Non-deterministic failures - Hard to reproduce parallel issues
- Improper connection pooling - Connection leaks or exhaustion
- 共享可变状态 - 导致竞态条件和不稳定的测试
- 未同步的静态字段 - 非线程安全
- 资源竞争 - 过多线程竞争有限资源
- 顺序依赖 - 假设执行顺序的测试
- 缺失清理逻辑 - ThreadLocal资源未被移除
- 隔离不足 - 数据库测试互相影响
- 线程数过多 - 开销超过收益
- 忽略超时配置 - 挂起的测试阻塞执行
- 非确定性失败 - 难以复现的并行问题
- 连接池配置不当 - 连接泄漏或耗尽
When to Use This Skill
何时使用本技能
- Reducing test suite execution time
- Configuring CI/CD parallel test execution
- Implementing thread-safe test infrastructure
- Designing parallel-friendly test architecture
- Troubleshooting parallel test failures
- Optimizing resource utilization in tests
- Building scalable test frameworks
- Implementing cross-browser parallel testing
- 缩短测试套件执行时间
- 配置CI/CD中的并行测试执行
- 实现线程安全的测试基础设施
- 设计支持并行的测试架构
- 排查并行测试失败问题
- 优化测试中的资源利用率
- 构建可扩展的测试框架
- 实现跨浏览器并行测试