TestNG+Selenium方法级并行测试WebDriver空指针问题排查
问题产生原因
空指针异常的核心原因是ThreadLocal的初始化位置错误:
- 代码中将
ThreadLocal<WebDriver>声明为类的普通成员变量,且在@BeforeMethod注解的setup方法中每次执行都重新new一个新的ThreadLocal实例赋值给this.driver - 两个测试线程并行执行时,线程1先进入setup方法,创建ThreadLocalA并存入自己的ChromeDriver实例;随后线程2进入setup方法,直接把类实例中driver的引用替换为全新的空ThreadLocalB。此时线程1持有的driver引用已经变成了没有存任何值的ThreadLocalB,调用
get()方法自然返回null,触发空指针。
至于不使用ThreadLocal时出现的操作串浏览器问题,本质是WebDriver本身不具备线程安全性,多个测试线程共享同一个WebDriver实例、或者共享同一个存储WebDriver的普通成员变量时,后启动的线程会覆盖之前线程存入的driver引用,最终所有线程的操作都会落到最后一个初始化的浏览器窗口上。
修复方案
只需要调整ThreadLocal的初始化位置,补充资源清理逻辑即可,具体修改点:
- 将
ThreadLocal<WebDriver>的初始化提前到类加载阶段,声明为static final类型,禁止在setup方法中重复实例化ThreadLocal - 在
@BeforeMethod中仅做WebDriver实例的创建和ThreadLocal的set操作,不重新创建ThreadLocal对象 - 新增
@AfterMethod注解的清理方法,测试执行完成后关闭当前线程对应的浏览器,同时调用ThreadLocal的remove()方法清理引用,避免内存泄漏
修复后的完整可运行代码如下:
import io.github.bonigarcia.wdm.WebDriverManager; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.Duration; public class LoginTest { // 类加载时就初始化ThreadLocal,全局唯一,禁止重复实例化 private static final ThreadLocal<WebDriver> driver = new ThreadLocal<>(); @BeforeMethod() public void setup() { WebDriverManager.chromedriver().setup(); WebDriver webDriver = new ChromeDriver(); webDriver.manage().window().maximize(); webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); webDriver.get("https://www.saucedemo.com/"); // 仅往已初始化的ThreadLocal中存入当前线程的driver实例 driver.set(webDriver); } @Test(invocationCount = 2, threadPoolSize = 2) public void test() { driver.get().findElement(By.id("user-name")).sendKeys("standard_user"); driver.get().findElement(By.id("password")).sendKeys("secret_sauce"); // 原示例代码仅定位了登录按钮未点击,补充点击动作 driver.get().findElement(By.id("login-button")).click(); } @AfterMethod public void teardown() { WebDriver currentDriver = driver.get(); if (currentDriver != null) { // 关闭当前线程对应的浏览器 currentDriver.quit(); // 清理ThreadLocal中的引用,避免线程复用、内存泄漏问题 driver.remove(); } } }
方法级并行测试下WebDriver的标准使用规范
- 强制线程隔离:WebDriver是非线程安全对象,必须保证每个测试线程持有独立的WebDriver实例,绝对禁止多线程共享同一个WebDriver实例
- ThreadLocal正确初始化:存储WebDriver的ThreadLocal必须声明为
static final类型,在类加载阶段完成初始化,禁止在@BeforeMethod/@BeforeTest等生命周期方法中重复创建ThreadLocal实例,避免引用覆盖导致的取值为空问题 - 生命周期严格对齐:WebDriver实例的初始化放在
@BeforeMethod中执行,保证每个测试方法运行前拿到全新的浏览器实例;测试方法执行完成后,在@AfterMethod中关闭浏览器、清理ThreadLocal存储的引用,避免出现脏数据、内存泄漏问题 - 避免使用普通成员变量存储WebDriver:普通成员变量在多线程访问类实例时会出现写覆盖,直接导致不同测试线程的操作互相干扰。
内容的提问来源于stack exchange,提问作者Katerina Zharkaya
相关产品推荐
相关产品推荐

