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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 21:15:55