You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java(Selenium)中多线程并行测试用例如何通过Apache POI并发访问同一Excel?

解决Java+Selenium并发测试时Apache POI的线程安全问题

这个问题我在搭建自动化测试框架时碰过好几次,核心原因就是Apache POI的绝大多数类(比如XSSFWorkbook、Sheet)都不是线程安全的——多个线程同时操作同一个POI实例或者同一个Excel文件,必然会抛出IO异常或者出现数据错乱。下面给你几个实用的解决方案,按优先级推荐:

1. 提前加载所有数据到线程安全的内存集合(最优方案)

这是我最推荐的方式:把Excel里的所有测试数据在测试启动阶段用单线程一次性加载到内存,之后所有并发测试线程直接从内存取数据,完全不碰POI和Excel文件。

实现步骤:

  • 在测试套件初始化时(比如TestNG的@BeforeSuite或者JUnit的@BeforeAll),用单线程读取整个Excel,解析成你需要的测试数据对象列表。
  • 把列表转换成线程安全的集合,比如CopyOnWriteArrayList或者ConcurrentHashMap(如果需要按用例ID快速查找)。
  • 每个测试线程直接从内存集合中获取自己的测试数据。

代码示例:

// 全局存储测试数据,用线程安全集合
private static List<TestCaseData> allTestData;

// 测试套件启动时加载数据(单线程执行)
@BeforeSuite
public void loadTestDataFromExcel() {
    try (FileInputStream fis = new FileInputStream("test_cases.xlsx");
         XSSFWorkbook workbook = new XSSFWorkbook(fis)) {
        // 自定义方法:把Workbook解析成测试数据对象列表
        List<TestCaseData> tempData = parseExcelToTestData(workbook);
        // 转成线程安全的CopyOnWriteArrayList,避免并发修改问题
        allTestData = new CopyOnWriteArrayList<>(tempData);
    } catch (IOException e) {
        throw new RuntimeException("初始化测试数据失败", e);
    }
}

// 测试用例中直接取数据
@Test
public void testLogin() {
    // 根据当前用例ID或者索引获取对应数据
    TestCaseData data = getTestDataForCurrentCase();
    // 执行Selenium测试逻辑:比如输入用户名密码
    driver.findElement(By.id("username")).sendKeys(data.getUsername());
}

优势:

  • 彻底避免多线程操作POI的冲突,性能最优(只做一次IO操作)。
  • 测试执行时不需要等待IO,速度更快。

注意:

  • 如果Excel数据量极大(比如几万条测试用例),可能会占用较多内存,但自动化测试场景下很少会有这么大的数据集。

2. 给POI操作加全局同步锁(简单但性能差)

如果不想提前加载数据(比如测试数据会动态修改),可以给所有操作POI的代码块加一个全局锁,确保同一时间只有一个线程在读取Excel。

代码示例:

// 全局锁对象,必须是静态的,确保所有线程用同一个锁
private static final Object POI_LOCK = new Object();

public TestCaseData getTestData(int caseId) {
    synchronized (POI_LOCK) {
        try (FileInputStream fis = new FileInputStream("test_cases.xlsx");
             XSSFWorkbook workbook = new XSSFWorkbook(fis)) {
            // 根据caseId查找对应数据
            return findTestDataById(workbook, caseId);
        } catch (IOException e) {
            throw new RuntimeException("读取测试数据失败", e);
        }
    }
}

优势:

  • 代码改动最小,不需要重构数据加载逻辑。

劣势:

  • 并发测试会变成串行读取数据,严重影响执行效率,只适合并发量极低的场景。

3. 每个线程独立创建POI实例(折中方案)

让每个测试线程自己打开Excel文件、创建Workbook实例,读完就关闭。这样每个线程有独立的POI资源,不会互相干扰。

代码示例:

@Test
public void testCheckout() {
    // 每个测试线程单独打开Excel和创建Workbook
    try (FileInputStream fis = new FileInputStream("test_cases.xlsx");
         XSSFWorkbook workbook = new XSSFWorkbook(fis)) {
        // 获取当前用例对应的测试数据
        TestCaseData data = parseDataForCurrentTest(workbook);
        // 执行测试逻辑
        driver.findElement(By.id("cart-checkout")).click();
        // ...
    } catch (IOException e) {
        throw new RuntimeException("读取测试数据失败", e);
    }
}

优势:

  • 不需要锁,线程之间互不影响。

劣势:

  • 每个线程都要做IO操作,频繁打开关闭Excel文件,性能不如提前加载方案。
  • 要注意用try-with-resources确保文件流和Workbook被正确关闭,避免文件句柄泄漏。

额外注意事项

  • 如果用TestNG做并发测试,确保你的@DataProvider是线程安全的,最好直接返回提前加载好的内存集合。
  • 尽量避免在测试执行过程中修改Excel文件,哪怕是单线程也可能导致数据不一致。

内容的提问来源于stack exchange,提问作者twistedninja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 06:56:21