使用ThreadLocal并行测试时线程ID不匹配与空指针异常如何解决?
问题描述
我使用TestNG+Cucumber执行并行测试,通过ThreadLocal管理AndroidDriver对象,但发现setDriver与getDriver方法运行在不同线程ID下,导致getDriver()返回null,并行模式下线程不兼容引发该问题。关闭并行后测试正常,但ThreadLocal在并行模式下似乎存在兼容问题。此外,@Parameters注解无法读取testng.xml文件中配置的参数。
相关代码
DriverManager.java
package utilities; import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.options.UiAutomator2Options; import java.net.URL; import java.time.Duration; public class DriverManager { private static ThreadLocal<AndroidDriver> driver = new ThreadLocal<>(); public static void setDriver (String deviceName,String platformName,String platformVersion,String port) { if (driver.get() == null) { try { System.out.println("setDriver - Thread ID: " + Thread.currentThread().getId()); UiAutomator2Options options = new UiAutomator2Options(); options.setPlatformName(platformName); options.setPlatformVersion(platformVersion); options.setDeviceName(deviceName); options.setAppPackage(ConfigReader.getProperty("appPackage")); options.setAppActivity(ConfigReader.getProperty("appActivity")); options.setAutomationName("UiAutomator2"); options.setNewCommandTimeout(Duration.ofSeconds(3600)); options.setAutoGrantPermissions(true); options.setApp(ConfigReader.getProperty("app")); options.setFullReset(true); String appiumServerURL = ConfigReader.getProperty("appiumServerURL"); //String url="http://"+ConfigReader.getProperty("ip")+":"+port+"/wd/hub"; AndroidDriver driverInstance = new AndroidDriver(new URL(appiumServerURL), options); driverInstance.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); driver.set(driverInstance); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("驱动初始化失败:" + e.getMessage()); } } } public static AndroidDriver getDriver() { System.out.println("getDriver - Thread ID: " + Thread.currentThread().getId()); return driver.get(); } public static void quitDriver() { if (driver.get() != null) { driver.get().quit(); driver.remove(); } } }
TestBase.java
package utilities; import io.cucumber.testng.AbstractTestNGCucumberTests; import org.testng.annotations.BeforeTest; import org.testng.annotations.Parameters; public class TestBase extends AbstractTestNGCucumberTests { @BeforeTest @Parameters({"deviceName","platformName","platformVersion","port"}) public void beforeMethod(String deviceName,String platformName,String platformVersion,String port){ try { System.out.println("TestNG: @BeforeTest 已执行。"); DriverManager.setDriver(deviceName,platformName,platformVersion,port); }catch (Exception e){ e.printStackTrace(); } } }
TestNGRunner.java
package runners; import io.cucumber.testng.AbstractTestNGCucumberTests; import io.cucumber.testng.CucumberOptions; import org.testng.annotations.DataProvider; import utilities.TestBase; @CucumberOptions( features = "src/test/resources/features", glue = {"steps","utilities"}, plugin = {"pretty","html:target/cucumber-reports-testng.html"}, monochrome = true, dryRun = false, tags = "@hepsiburada1" ) public class TestNGRunner extends TestBase { @Override @DataProvider(parallel = true) public Object[][] scenarios() { return super.scenarios(); } }
TestNGRunner2.java
package runners; import io.cucumber.testng.AbstractTestNGCucumberTests; import io.cucumber.testng.CucumberOptions; import org.testng.annotations.DataProvider; import utilities.TestBase; @CucumberOptions( features = "src/test/resources/features", glue = {"steps","utilities"}, plugin = {"pretty","html:target/cucumber-reports-testng.html"}, monochrome = true, dryRun = false, tags = "@hepsiburada2" ) public class TestNGRunner2 extends TestBase { @Override @DataProvider(parallel = true) public Object[][] scenarios() { return super.scenarios(); } }
testng.xml
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Cucumber Test Suite" parallel="tests" thread-count="2"> <test name="Cucumber Tests"> <parameter name="deviceName" value="emulator-5554"></parameter> <parameter name="platformName" value="Android"></parameter> <parameter name="platformVersion" value="14.0"></parameter> <parameter name="port" value="4723"></parameter> <classes> <class name="runners.TestNGRunner" /> </classes> </test> <test name="Cucumber Tests 2"> <parameter name="deviceName" value="emulator-5556"></parameter> <parameter name="platformName" value="Android"></parameter> <parameter name="platformVersion" value="14.0"></parameter> <parameter name="port" value="4725"></parameter> <classes> <class name="runners.TestNGRunner" /> </classes> </test> </suite>
问题分析与解决方案
1. ThreadLocal线程不匹配问题
问题根源:@BeforeTest是在Test级别的线程执行初始化,但Cucumber通过@DataProvider(parallel=true)启动的场景会在新线程中运行,导致场景线程无法获取到@BeforeTest中设置的ThreadLocal变量。
解决方法:
- 改用Cucumber的
@Before钩子替代TestNG的@BeforeTest,确保Driver在场景执行的线程中初始化:
package utilities; import io.cucumber.java.Before; import io.cucumber.java.After; import io.cucumber.testng.AbstractTestNGCucumberTests; import org.testng.ITestContext; import org.testng.annotations.Test; public class TestBase extends AbstractTestNGCucumberTests { @Before public void beforeScenario(ITestContext context) { String deviceName = context.getCurrentXmlTest().getParameter("deviceName"); String platformName = context.getCurrentXmlTest().getParameter("platformName"); String platformVersion = context.getCurrentXmlTest().getParameter("platformVersion"); String port = context.getCurrentXmlTest().getParameter("port"); System.out.println("Cucumber: @BeforeScenario 已执行。"); DriverManager.setDriver(deviceName, platformName, platformVersion, port); } @After public void afterScenario() { DriverManager.quitDriver(); } }
2. @Parameters注解无法读取参数问题
问题根源:并行模式下@Parameters注解的参数传递机制失效,且testng.xml中第二个<test>标签错误引用了同一个TestNGRunner,导致参数传递混乱。
解决方法:
- 直接通过
ITestContext获取testng.xml参数,替代@Parameters注解(代码如上); - 修正testng.xml中第二个
<test>标签的Runner类引用:
<test name="Cucumber Tests 2"> <parameter name="deviceName" value="emulator-5556"></parameter> <parameter name="platformName" value="Android"></parameter> <parameter name="platformVersion" value="14.0"></parameter> <parameter name="port" value="4725"></parameter> <classes> <class name="runners.TestNGRunner2" /> </classes> </test>
内容的提问来源于stack exchange,提问作者Meliqe
相关产品推荐
相关产品推荐

