Introduction
In the previous post, we have seen how maximum beginners and experienced Selenium professionals apply inheritance with TestNG annotations while setup test scripts and end up with encountering NullPointerException. We have solved NullPointerException by converting instance variables to Static variables.
We also solved the above problem using a Singleton design pattern as well. I have used a static reference of class SharedVariables which will create a problem while running in parallel for sure. So in this post, we will use ThreadLocal static instance of class SharedVariables.
Dependency version details
I used below version of TestNG in this post:-
org.testng testng 7.1.0 test
Problem with static variables
Before we jump to ThreadLocal implementation, Let’s create a scenario in which we will face incorrect mapping of data while running in parallel.
There is no change in class SharedVariable and SharedVariableMap.
Class SharedVariable
package SecondTestNullPointerExceptionWithSingletonParallelProblem; public class SharedVariables { // Private instance of class private static final SharedVariables instance = new SharedVariables(); // Private constructor to control object creation of class private SharedVariables() { } // Return unidealized instance of class public static SharedVariables getInstance() { return instance; } // A private variable with getter and setter methods private String someVariable; public String getSomeVariable() { return someVariable; } public void setSomeVariable(String someVariable) { this.someVariable = someVariable; } }
Class SharedVariableMap
package SecondTestNullPointerExceptionWithSingletonParallelProblem; import java.util.HashMap; public class SharedVariablesMap { // Private instance of class private static final SharedVariablesMap instance = new SharedVariablesMap(); private static HashMapdataStore = new HashMap<>(); // Private constructor to control object creation of class private SharedVariablesMap() { } // Return unidealized instance of class public static SharedVariablesMap getInstance() { return instance; } public void store(String key, String value) { dataStore.put(key, value); } public Object get(String key) { return dataStore.get(key); } }
Add thread details for understanding calling flow well and random wait in Setup class and only thread details in test methods classes. I will also change annotation BeforeSuite to BeforeMethod in Setup class. I will run methods in parallel so there will be a call to setupVariable() for each method by a unique thread and may override other’s values.
Class Setup
package SecondTestNullPointerExceptionWithSingletonParallelProblem; import java.util.Random; import org.testng.annotations.BeforeMethod; public class Setup { // Get same instance of SharedVariables always SharedVariables sharedVariables = SharedVariables.getInstance(); SharedVariablesMap sharedVariablesMap = SharedVariablesMap.getInstance(); @BeforeMethod public void setupVariable() throws InterruptedException { System.out.println("Executing setupVariable By Thread "+Thread.currentThread().getId()); String somevalue = "someValue" + new Random().nextInt(); String name = "Amod" + new Random().nextInt(); System.out.println("Value set for SomeVariable by Thread "+Thread.currentThread().getId() + " as " + somevalue); System.out.println("Value set for Name by Thread "+Thread.currentThread().getId() + " as "+name); // Making thread sleep int waitTime = new Random().nextInt((9 - 1) + 1) + 1; System.out.println("Waiting for : "+waitTime+" sec"); Thread.sleep(waitTime*1000); sharedVariables.setSomeVariable(somevalue); sharedVariablesMap.store("Name", name); } }
Class FirstTestNGClass
package SecondTestNullPointerExceptionWithSingletonParallelProblem; import org.testng.annotations.Test; public class FirstTestNGClass extends Setup{ @Test public void firstMethodOfFirstTestNGClass() { System.out.println("Executing firstMethodOfFirstTestNGClass by Thread "+ Thread.currentThread().getId()); System.out.println("Value of someVariable is : "+ sharedVariables.getSomeVariable()); System.out.println("Value of name is :"+ sharedVariablesMap.get("Name")); } @Test public void secondMethodOfFirstTestNGClass() { System.out.println("Executing secondMethodOfFirstTestNGClass by Thread " + Thread.currentThread().getId()); System.out.println("Value of someVariable is : "+ sharedVariables.getSomeVariable()); System.out.println("Value of name is :"+ sharedVariablesMap.get("Name")); } }
Class SecondTestNGClass
package SecondTestNullPointerExceptionWithSingletonParallelProblem; import org.testng.annotations.Test; public class SecondTestNGClass extends Setup{ @Test public void firstMethodOfSecondTestNGClass() { System.out.println("Executing firstMethodOfSecondTestNGClass by Thread "+Thread.currentThread().getId()); System.out.println("Value of someVariable is : "+ sharedVariables.getSomeVariable()); System.out.println("Value of name is :"+ sharedVariablesMap.get("Name")); } @Test public void secondMethodOfSecondTestNGClass() { System.out.println("Executing secondMethodOfSecondTestNGClass by Thread "+ Thread.currentThread().getId()); System.out.println("Value of someVariable is : "+ sharedVariables.getSomeVariable()); System.out.println("Value of name is :"+ sharedVariablesMap.get("Name")); } }
Create a TestNG xml just to run methods in parallel.
Output
[RemoteTestNG] detected TestNG version 7.0.1 Executing setupVariable By Thread 12 Executing setupVariable By Thread 14 Value set for SomeVariable by Thread 12 as someValue566752212 Value set for Name by Thread 12 as Amod759456508 Executing setupVariable By Thread 11 Executing setupVariable By Thread 13 Value set for SomeVariable by Thread 11 as someValue181282116 Value set for Name by Thread 11 as Amod89764154 Waiting for : 7 sec Value set for SomeVariable by Thread 14 as someValue547002893 Value set for Name by Thread 14 as Amod-778378531 Waiting for : 8 sec Value set for SomeVariable by Thread 13 as someValue-146738648 Waiting for : 2 sec Value set for Name by Thread 13 as Amod322004321 Waiting for : 7 sec Executing secondMethodOfSecondTestNGClass by Thread 14 Value of someVariable is : someValue547002893 Value of name is :Amod-778378531 Executing secondMethodOfFirstTestNGClass by Thread 12 Value of someVariable is : someValue566752212 Value of name is :Amod759456508 Executing firstMethodOfSecondTestNGClass by Thread 13 Value of someVariable is : someValue566752212 Value of name is :Amod759456508 Executing firstMethodOfFirstTestNGClass by Thread 11 Value of someVariable is : someValue181282116 Value of name is :Amod89764154 Executing setupVariable By Thread 16 Value set for SomeVariable by Thread 16 as someValue-253812863 Value set for Name by Thread 16 as Amod-2053621935 Waiting for : 4 sec Executing setupVariable By Thread 15 Value set for SomeVariable by Thread 15 as someValue158143974 Value set for Name by Thread 15 as Amod1765012539 Waiting for : 3 sec Executing setupVariable By Thread 17 Value set for SomeVariable by Thread 17 as someValue-1485281520 Executing setupVariable By Thread 18 Value set for SomeVariable by Thread 18 as someValue340008154 Value set for Name by Thread 18 as Amod846378800 Waiting for : 2 sec Value set for Name by Thread 17 as Amod1362387569 Waiting for : 1 sec Executing firstMethodOfSecondTestNGClass by Thread 17 Value of someVariable is : someValue-1485281520 Value of name is :Amod1362387569 Executing secondMethodOfSecondTestNGClass by Thread 18 Value of someVariable is : someValue340008154 Value of name is :Amod846378800 Executing firstMethodOfFirstTestNGClass by Thread 15 Value of someVariable is : someValue158143974 Value of name is :Amod1765012539 Executing secondMethodOfFirstTestNGClass by Thread 16 Value of someVariable is : someValue-253812863 Value of name is :Amod-2053621935 =============================================== Suite Total tests run: 8, Passes: 8, Failures: 0, Skips: 0 ===============================================
Lengthy output to understand and I may not be able to explain well. Let’s group console output by thread ids as below.
Thread 11
Executing setupVariable By Thread 11 alue set for SomeVariable by Thread 11 as someValue181282116 Value set for Name by Thread 11 as Amod89764154 Executing firstMethodOfFirstTestNGClass by Thread 11 Value of someVariable is : someValue181282116 Value of name is :Amod89764154
Thread 12
Executing setupVariable By Thread 12 Value set for SomeVariable by Thread 12 as someValue566752212 Value set for Name by Thread 12 as Amod759456508 Executing secondMethodOfFirstTestNGClass by Thread 12 Value of someVariable is : someValue566752212 Value of name is :Amod759456508
Thread 13
Executing setupVariable By Thread 13 Value set for SomeVariable by Thread 13 as someValue-146738648 Value set for Name by Thread 13 as Amod322004321 Executing firstMethodOfSecondTestNGClass by Thread 13 Value of someVariable is : someValue566752212 Value of name is :Amod759456508
Thread 14
Executing setupVariable By Thread 14 Value set for SomeVariable by Thread 14 as someValue547002893 Value set for Name by Thread 14 as Amod-778378531 Executing secondMethodOfSecondTestNGClass by Thread 14 Value of someVariable is : someValue547002893 Value of name is :Amod-778378531
I have just listed for 4 thread ids. Values initialized by Thread 11 and Thread 14 are consumed by test method called by Thread 11 and Thread 14 respectively. You can see values are matched. This behavior will change in each run.
Now observe carefully for Thread 12 and Thread 13. Thread 12 has no issues but test methods called by Thread 13 have not used values initialized by Thread 13 but by Thread 12. As we are running methods in parallel so each Test method should have their own values but here it is messed. This is a problem while running parallel with static variables.
Solving Problem with static variables during parallel run using ThreadLocal
I have explained ThreadLocal in my previous posts. You can check those in the prerequisite section. In this section, we will just focus on the implementation of ThreadLocal.
Let’s have instance variables as ThreadLocal in both SharedVariable and SharedvariableMap classes.
Class SharedVariables
package SecondTestNullPointerExceptionWithSingletonParallelProblemSolution; public class SharedVariables { // Private instance of class private static final ThreadLocalinstance = new ThreadLocal (); // Private constructor to control object creation of class private SharedVariables() { } // Return unidealized instance of class public static SharedVariables getInstance() { if(instance.get() == null) instance.set(new SharedVariables()); return instance.get(); } // A private variable with getter and setter methods private String someVariable; public String getSomeVariable() { return someVariable; } public void setSomeVariable(String someVariable) { this.someVariable = someVariable; } }
Class SharedVariablesMap
package SecondTestNullPointerExceptionWithSingletonParallelProblemSolution; import java.util.HashMap; public class SharedVariablesMap { // Private instance of class private static final ThreadLocalinstance = new ThreadLocal (); private static final ThreadLocal > dataStore = new ThreadLocal >(); // Private constructor to control object creation of class private SharedVariablesMap() { } // Return unidealized instance of class public static SharedVariablesMap getInstance() { if(instance.get() == null) { instance.set(new SharedVariablesMap()); dataStore.set(new HashMap ()); } return instance.get(); } public void store(String key, String value) { dataStore.get().put(key, value); } public Object get(String key) { return dataStore.get().get(key); } }
Class Setup
I have not initialized class variables sharedVariables and sharedVariablesMap as we are running methods in parallel. If we initialized while declaration, these will be initialized commonly for all test methods when class Setup will be loaded into memory and you will see incorrect data mapping among threads again.
package SecondTestNullPointerExceptionWithSingletonParallelProblemSolution; import java.util.Random; import org.testng.annotations.BeforeMethod; public class Setup { // Get same instance of SharedVariables always SharedVariables sharedVariables; SharedVariablesMap sharedVariablesMap; @BeforeMethod public void setupVariable() throws InterruptedException { sharedVariables = SharedVariables.getInstance(); sharedVariablesMap = SharedVariablesMap.getInstance(); System.out.println("Executing setupVariable By Thread "+Thread.currentThread().getId()); String somevalue = "someValue" + new Random().nextInt(); String name = "Amod" + new Random().nextInt(); System.out.println("Value set for SomeVariable by Thread "+Thread.currentThread().getId() + " as " + somevalue); System.out.println("Value set for Name by Thread "+Thread.currentThread().getId() + " as "+name); // Making thread sleep int waitTime = new Random().nextInt((9 - 1) + 1) + 1; System.out.println("Waiting for : "+waitTime+" sec"); Thread.sleep(waitTime*1000); sharedVariables.setSomeVariable(somevalue); sharedVariablesMap.store("Name", name); } }
There is no change in Test classes. Below is TestNG xml.
TestNG XML
Output
[RemoteTestNG] detected TestNG version 7.0.1 Executing setupVariable By Thread 12 Executing setupVariable By Thread 11 Executing setupVariable By Thread 14 Executing setupVariable By Thread 13 Value set for SomeVariable by Thread 14 as someValue-1231067331 Value set for Name by Thread 14 as Amod-396767176 Value set for SomeVariable by Thread 12 as someValue-904666002 Value set for SomeVariable by Thread 11 as someValue200196728 Value set for Name by Thread 11 as Amod-88766989 Waiting for : 3 sec Value set for Name by Thread 12 as Amod-1484453836 Value set for SomeVariable by Thread 13 as someValue1523456635 Value set for Name by Thread 13 as Amod107048814 Waiting for : 5 sec Waiting for : 6 sec Waiting for : 9 sec Executing secondMethodOfSecondTestNGClass by Thread 14 Value of someVariable is : someValue-1231067331 Value of name is :Amod-396767176 Executing firstMethodOfSecondTestNGClass by Thread 13 Value of someVariable is : someValue1523456635 Value of name is :Amod107048814 Executing secondMethodOfFirstTestNGClass by Thread 12 Value of someVariable is : someValue-904666002 Value of name is :Amod-1484453836 Executing firstMethodOfFirstTestNGClass by Thread 11 Value of someVariable is : someValue200196728 Value of name is :Amod-88766989 Executing setupVariable By Thread 16 Value set for SomeVariable by Thread 16 as someValue1686235698 Value set for Name by Thread 16 as Amod-1623769295 Waiting for : 2 sec Executing setupVariable By Thread 15 Value set for SomeVariable by Thread 15 as someValue-394860423 Value set for Name by Thread 15 as Amod879328108 Waiting for : 1 sec Executing setupVariable By Thread 18 Value set for SomeVariable by Thread 18 as someValue-798221713 Value set for Name by Thread 18 as Amod173745892 Waiting for : 9 sec Executing setupVariable By Thread 17 Value set for SomeVariable by Thread 17 as someValue-214829912 Value set for Name by Thread 17 as Amod292220200 Waiting for : 6 sec Executing firstMethodOfFirstTestNGClass by Thread 15 Value of someVariable is : someValue-394860423 Value of name is :Amod879328108 Executing secondMethodOfFirstTestNGClass by Thread 16 Value of someVariable is : someValue1686235698 Value of name is :Amod-1623769295 Executing firstMethodOfSecondTestNGClass by Thread 17 Value of someVariable is : someValue-214829912 Value of name is :Amod292220200 Executing secondMethodOfSecondTestNGClass by Thread 18 Value of someVariable is : someValue-798221713 Value of name is :Amod173745892 =============================================== Suite Total tests run: 8, Passes: 8, Failures: 0, Skips: 0 ===============================================
Let’s group them by thread ids.
Executing setupVariable By Thread 11 Value set for SomeVariable by Thread 11 as someValue200196728 Value set for Name by Thread 11 as Amod-88766989 Executing firstMethodOfFirstTestNGClass by Thread 11 Value of someVariable is : someValue200196728 Value of name is :Amod-88766989 Executing setupVariable By Thread 12 Value set for SomeVariable by Thread 12 as someValue-904666002 Value set for Name by Thread 12 as Amod-1484453836 Executing secondMethodOfFirstTestNGClass by Thread 12 Value of someVariable is : someValue-904666002 Value of name is :Amod-1484453836 Executing setupVariable By Thread 13 Value set for SomeVariable by Thread 13 as someValue1523456635 Value set for Name by Thread 13 as Amod107048814 Executing firstMethodOfSecondTestNGClass by Thread 13 Value of someVariable is : someValue1523456635 Value of name is :Amod107048814 Executing setupVariable By Thread 14 Value set for SomeVariable by Thread 14 as someValue-1231067331 Value set for Name by Thread 14 as Amod-396767176 Executing secondMethodOfSecondTestNGClass by Thread 14 Value of someVariable is : someValue-1231067331 Value of name is :Amod-396767176 Executing setupVariable By Thread 15 Value set for SomeVariable by Thread 15 as someValue-394860423 Value set for Name by Thread 15 as Amod879328108 Executing firstMethodOfFirstTestNGClass by Thread 15 Value of someVariable is : someValue-394860423 Value of name is :Amod879328108 Executing setupVariable By Thread 16 Value set for SomeVariable by Thread 16 as someValue1686235698 Value set for Name by Thread 16 as Amod-1623769295 Executing secondMethodOfFirstTestNGClass by Thread 16 Value of someVariable is : someValue1686235698 Value of name is :Amod-1623769295 Executing setupVariable By Thread 17 Value set for SomeVariable by Thread 17 as someValue-214829912 Value set for Name by Thread 17 as Amod292220200 Executing firstMethodOfSecondTestNGClass by Thread 17 Value of someVariable is : someValue-214829912 Value of name is :Amod292220200 Executing setupVariable By Thread 18 Value set for SomeVariable by Thread 18 as someValue-798221713 Value set for Name by Thread 18 as Amod173745892 Executing secondMethodOfSecondTestNGClass by Thread 18 Value of someVariable is : someValue-798221713 Value of name is :Amod173745892
This time you can see there is no wrong mapping of data and each thread was able to access correct values.
You can download/clone the above sample project from here.
If you have any doubt, feel free to comment below.
If you like my posts, please like, comment, share and subscribe.
#ThanksForReading
#HappyLearning
Find all Selenium related posts here, all API manual and automation related posts here, and find frequently asked Java Programs here.
Many other topics you can navigate through the menu.
Hi Amod Nice article, Can we able to use same singleton pattern in Selenium C# to handle threadsafe webdriver?