Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
198 views
in Technique[技术] by (71.8m points)

java - Is ThreadLocal required for automation framework that uses Cucumber, Jmeter and FailSafe?

Sorry if this question sounds silly. We are developing an automation framework that would use Java,Cucumber, Junit and Failsafe suite etc. There's been a suggestion to use ThreadLocal. But I am bit confused as why we need to use ThreadLocal when Junit runs the cucumber features in its own threads..

The suggestion was to use ThreadLocal as shown below;-

public class WebDriverFactory {

    private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();

    public static synchronized void setDriver(String browser) {

        switch (browser) {
            case "chrome":
                driver = ThreadLocal.withInitial(() -> {
                    WebDriverManager.chromedriver().setup();
                    return new ChromeDriver(BrowserOptions.getChromeOptions());
                });
        
                break;
           
            default:
                throw new IllegalStateException("Unexpected value: " + browser);
        }
    }

    public static synchronized WebDriver getDriver(){
        return driver.get();
    }

Can anyone confirm if this is really required to run tests parallelly.? Also, is 'synchronized' required when using ThreadLocal?

question from:https://stackoverflow.com/questions/65541200/is-threadlocal-required-for-automation-framework-that-uses-cucumber-jmeter-and

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It depends.

When using a static field, there is only a single instance of that field in the JVM. It refers to one specific address in memory. Unlike the field of an object which refers to a field of a specific object and for each object there is a unique address in memory for that field.

When using a ThreadLocal each thread has its own instance of the variable.

So by using static WebDriver driver you have exactly one webdriver for all tests and all threads. By using static ThreadLocal<WebDriver> driver you have exactly one webdriver for each thread, effectively sharing the webdriver between scenarios that are executed on that thread.

When executing tests in parallel there are multiple threads executing scenarios so a single webdriver would be a problem. Scenarios running in parallel would make the webdriver do different things at the same time or they'd have to wait for the webdriver to be available, making them effectively run serially again.

So if a webdriver is to be shared between scenarios and if those scenarios run in parallel you have to use a ThreadLocal.

However it appears that are unfamiliar with programming concurrent systems. So you may want to consider a different approach. Rather then sharing the webdriver between scenarios, consider starting a new webdriver in each scenario instead. This is much safer and from a test perspective much cleaner because each scenario starts without any state from the previous scenario.

This means you now have the problem of sharing information between steps. You were using a static field to share to share the webdriver. But you can't use use static fields because there are now multiple threads running.

To solve this problem Cucumber supports dependency injection. The easiest to use is probably cucumber-pico. When using dependency injection cucumber will instantiate each step definition class for each scenario with a isolated set of dependencies.

    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-picocontainer</artifactId>
        <version>${cucumber.version}</version>
        <scope>test</scope>
    </dependency>

So for example when your step definitions depend on a WebDriverFactory, cucumber will instantiate a WebDriverFactory for you and instantiate both StepDefinition and OtherStepDefinition with the same factory.

public class StepDefinition {

    private final WebDriverFactory webdriverFactory;

    public StepDefinitions(WebDriverFactory webdriverFactory) {
        this.webdriverFactory = webdriverFactory;
    }

    @Given("I do a thing with a webdriver")
    public void useTheWebDriver() {
        // get the webdriver from the factory and use it
    }

}

public class OtherStepDefinition {

    private final WebDriverFactory webdriverFactory; // Same instance as in StepDefinition

    public OtherStepDefinition(WebDriverFactory webdriverFactory) {
        this.webdriverFactory = webdriverFactory; 
    }

    @Given("I do another thing with a webdriver")
    public void useTheWebDriver() {
        // get the webdriver from the factory and use it
    }

}

Then in the web driver factory you keep a reference to the webdriver for use in both step definitions.

public class WebDriverFactory implements Startable {

    private WebDriver driver;

    public setDriver(String browser) {
        // create the web driver here 
    }

    public static synchronized WebDriver getDriver(){
        return driver;
    }

    public void start() { 
      // do nothing
    } 
    
    public void stop() { 
       // stop web driver if it was created
    } 
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...