Breaking free from .staticAbuse()
in test automation frameworks
Hello!
I am Abhijeet Vaikar
● Software Quality Engineer since 6 years and 7
months with a focus on Test Automation
● Quality Engineer @ Carousell
● https://www.linkedin.com/in/abhijeetvaikar/
Southeast Asia's largest and fastest
growing mobile marketplace,
and a highly-rated iPhone & Android app
that makes selling as simple as taking a
photo and buying as simple as chat.
https://www.carousell.com
What are we here for?
● Why we should avoid abusing static methods in
automation frameworks
● What we can do about it.
● How we can use Dependency Injection
frameworks to simplify automation scripts.
Why
we should avoid abusing static in automation frameworks
Does this look familiar?
protected void grabScreenshot(){
ScreenshotUtil.captureScreenshot(currentSuiteScreenshotsDirector
yPath);
}
AppiumUtil.scrollDown();
User user = UserService.getUser(expectedUserKey);
Does this look familiar?
itemName =
TestDataService.extract(itemName);
private static
AndroidDriver<WebElement> driver;
So what’s wrong with that?
Concurrency issues
All static objects are shared between threads. Imagine the race condition issues with
parallel test executions.
private static WebDriver driver;
private static HashMap<String, String> testResultsMap;
Thread 1
Thread 2 Thread 3
Expect something :)
Get something else :’(
Design
Code becomes procedural instead of object oriented.
public class LoginTest {
@Test(priority = 0)
public void loginfail() {
LoginPage.goTo("http://127.0.0.1:8080/login");
LoginPage.loginAs("wrong username", "wrongpassword");
boolean didLoginFail = LoginPage.loginErrorDisplayed();
Assert.assertTrue(didLoginFail == true, "Bad login was successful");
if (didLoginFail){
LoginPage.getLoginErrorMessage();
}
}
@Test(priority = 1)
public void loginsuccess() {
LoginPage.loginAs("correct_username", "correctpass");
boolean didLoginFail = LoginPage.loginErrorDisplayed();
Assert.assertTrue(didLoginFail == false, "Valid Login was unsuccessful");
}
Mutable state
Static objects if kept mutable leaves their values open to change by other code.
/** Contains all the constant system property names */
public final class SystemPropertyConstants {
public static String loginEndPoint = "/api/login/";
}
// In some other code
SystemPropertyConstants.loginEndPoint = "/api/logout/";
What
Can we do about it?
“Make the framework smart so that the
test code can be short and simple.
Test code must be so concise that it
doesn't matter which language is used to
run it”
- Martin Schneider ( Java Ninja @
Carousell )
For concurrency issues
public static ThreadLocal<WebDriver> driver;
driver =
new ThreadLocal<WebDriver>() {
@Override
protected WebDriver initialValue() {
return new FirefoxDriver(); // You
can use other driver based on your requirement.
}
};
OR use Dependency Injection with non-static object
Design
Use Object-Oriented programming concepts (abstraction, encapsulation, inheritance, polymorphism) to make your
test code maintainable.
new AuthPage().login("username","password");
SellingPage sellingPage = new HomePage().startSelling();
sellingPage
.setCategory(Category.EVERYTHING_ELSE)
.setItemDetails("Carousell Test Item",ConditionType.NEW,"Test Description")
.setPrice("10.12")
.setDealDetails(DealType.MEETUP,"Lets meetup at 5 PM")
.listIt();
AND enhance them using Dependency Injection
Mutable state
To avoid static objects being overwritten by other code, declare them final
/** Contains all the constant system property names */
public final class SystemPropertyConstants {
public static final String loginEndPoint = "/api/login/";
}
What is
Dependency
Injection????
Dependency Injection
Dependency Non-Injection
public class Employee {
private Address address;
public Employee() {
address = new Address();
}
}
Employee employee = new Employee(new address);
Employee employee = new Employee();
employee.setAddress(new Address());
SellingPage sellingPage = new SellingPage(driver);
// Injecting driver dependency in pageobjects
@BeforeMethod
public void setUp(ITestContext testContext){
...
}
// DI used by TestNG
Dependency Injection using a framework
● Use a DI framework instead of managing dependencies manually.
● DI frameworks: Spring, Google Guice, PicoContainer, Dagger
● These frameworks use IoC (Inversion of Control) container in which all the
dependencies are registered, initialized and managed.
● IoC (Inversion of Control) - “Don’t call us, we call you”
● Once the dependencies are instantiated, the container injects them using
approaches like:
○ Constructor injection
○ Setter method injection
○ Field injection
Benefits of Dependency Injection
● Single Responsibility Principle
● Clean, Readable code
● Isolated components which become easy for testing as dependencies can be
mocked without modifying depending class.
How
we can use Dependency Injection frameworks to simplify automation scripts.
22
Spring Dependency Injection
Spring IoC Container
(where all the magic
happens!)
Configuration in Java
class or XML
Read dependency and configuration details
Create dependency
objects and inject them
POJOs/ Java
Classes part of your
application
code/test code
Usable system with
all dependencies
available
@Component @Configuration
@Autowired @Bean
Applied to fields, setter methods, and
constructors. The @Autowired annotation
injects object dependency implicitly.
Used as config for
Spring. Can have
methods to instantiate
and configure the
dependencies
Marks the Java class
as a bean or
component (i.e., you
want Spring to
manage this class
instance)
Applied to fields, setter methods, and
constructors. The @Autowired annotation
injects object dependency implicitly.
Commonly used annotations in Spring
Used for conditional
loading of classes
based on usecase
(environment,
platform etc)
@Profile
@Test()
public void testNewListingAppearsInSearch() throws Exception {
new WelcomePage(driver).beginSignUpOrLoginWithEmail();
new AuthPage(driver).login("username","password");
new
HomePage(driver).startSellingWithPhotoFromCamera();
new CameraPage(driver).capturePhoto();
new CameraPhotoPreviewEditPage(driver).moveForward();
}
}
● Driver is a dependency for PageObjects.
● PageObjects are a dependency for the test class
(Is it necessary to create new instance of pageobject every time?)
public class WelcomePage extends BasePage {
private WebDriver driver;
WelcomePage(WebDriver driver){
super(driver);
this.driver = driver;
}
. . . .
}
@Configuration
@ComponentScan(basePackages = "com.carousell")
public class SpringContext {
@Autowired
private TestConfiguration configuration;
private WebDriver driver;
@Bean
public WebDriver getWebDriver() {
if(configuration.isWeb()){
driver = new ChromeDriver();
}
else if(configuration.isAndroid() {
driver = new AndroidDriver();
}
else if(configuration.isIOS()) {
driver = new IOSDriver();
}
return driver;
}
Create a configuration class for Spring to manage dependencies
public class WelcomePage extends BasePage {
@Autowired
private WebDriver driver;
//You can move this to BasePage too
. . . .
}
29
@Component
public class WelcomePage extends BasePage {
@Autowired
private WebDriver driver;
//You can move this to BasePage too
. . . .
}
@Autowired
WelcomePage welcomePage;
@Autowired
AuthPage authPage;
. . . . .
@Test()
public void testNewListingAppearsInSearch() throws Exception {
welcomPage.beginSignUpOrLoginWithEmail();
authPage.login("username","password");
homePage.startSellingWithPhotoFromCamera();
cameraPage.capturePhoto();
cameraPhotoPreviewEditPage.moveForward();
//Use references directly as they are already injected with instance by
Spring
}
public abstract class WelcomePage
extends BasePage {
}
@Component
@Profile(Platform.ANDROID)
public class AndroidWelcomePage
extends WelcomePage {
}
@Component
@Profile(Platform.IOS)
public class IOSWelcomePage
extends WelcomePage {
}
spring.profiles.active=ANDROID - Initializes AndroidWelcomePage and injects into an
Autowired variable.
Sounds good. Where can I find real
world implementation of such
framework?
Check out https://www.justtestlah.qa/
Python: dependency_injector, Spring Python
Ruby: sinject, dry-container
C#: Spring.Net , Castle Windsor, Unity, Autofac
Javascript: BottleJS, InversifyJS, DI-Ninja
Thank you. Questions?
Reference
Inversion of Control and Dependency Injection
https://martinfowler.com/bliki/InversionOfControl.html
https://martinfowler.com/articles/injection.html
https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring
https://dzone.com/articles/a-guide-to-spring-framework-annotations
https://stackoverflow.com/questions/52720198/how-does-spring-profile-work-with-inheritance
Static methods and variables
https://stackoverflow.com/questions/7026507/why-are-static-variables-considered-evil
https://stackoverflow.com/questions/2671496/java-when-to-use-static-methods
https://stackoverflow.com/questions/4002201/why-arent-static-methods-considered-good-oo-practice
https://softwareengineering.stackexchange.com/questions/336701/static-services-and-testability
Use of Dependency Injection in automation frameworks
www.justtestlah.qa by Martin Schneider
https://peterkedemo.wordpress.com/2013/03/30/writing-good-selenium-tests-with-page-objects-and-spring/

Breaking free from static abuse in test automation frameworks and using Spring DI

  • 1.
    Breaking free from.staticAbuse() in test automation frameworks
  • 2.
    Hello! I am AbhijeetVaikar ● Software Quality Engineer since 6 years and 7 months with a focus on Test Automation ● Quality Engineer @ Carousell ● https://www.linkedin.com/in/abhijeetvaikar/
  • 3.
    Southeast Asia's largestand fastest growing mobile marketplace, and a highly-rated iPhone & Android app that makes selling as simple as taking a photo and buying as simple as chat. https://www.carousell.com
  • 4.
    What are wehere for? ● Why we should avoid abusing static methods in automation frameworks ● What we can do about it. ● How we can use Dependency Injection frameworks to simplify automation scripts.
  • 5.
    Why we should avoidabusing static in automation frameworks
  • 7.
    Does this lookfamiliar? protected void grabScreenshot(){ ScreenshotUtil.captureScreenshot(currentSuiteScreenshotsDirector yPath); } AppiumUtil.scrollDown(); User user = UserService.getUser(expectedUserKey);
  • 8.
    Does this lookfamiliar? itemName = TestDataService.extract(itemName); private static AndroidDriver<WebElement> driver;
  • 9.
  • 10.
    Concurrency issues All staticobjects are shared between threads. Imagine the race condition issues with parallel test executions. private static WebDriver driver; private static HashMap<String, String> testResultsMap; Thread 1 Thread 2 Thread 3 Expect something :) Get something else :’(
  • 11.
    Design Code becomes proceduralinstead of object oriented. public class LoginTest { @Test(priority = 0) public void loginfail() { LoginPage.goTo("http://127.0.0.1:8080/login"); LoginPage.loginAs("wrong username", "wrongpassword"); boolean didLoginFail = LoginPage.loginErrorDisplayed(); Assert.assertTrue(didLoginFail == true, "Bad login was successful"); if (didLoginFail){ LoginPage.getLoginErrorMessage(); } } @Test(priority = 1) public void loginsuccess() { LoginPage.loginAs("correct_username", "correctpass"); boolean didLoginFail = LoginPage.loginErrorDisplayed(); Assert.assertTrue(didLoginFail == false, "Valid Login was unsuccessful"); }
  • 12.
    Mutable state Static objectsif kept mutable leaves their values open to change by other code. /** Contains all the constant system property names */ public final class SystemPropertyConstants { public static String loginEndPoint = "/api/login/"; } // In some other code SystemPropertyConstants.loginEndPoint = "/api/logout/";
  • 13.
    What Can we doabout it?
  • 14.
    “Make the frameworksmart so that the test code can be short and simple. Test code must be so concise that it doesn't matter which language is used to run it” - Martin Schneider ( Java Ninja @ Carousell )
  • 15.
    For concurrency issues publicstatic ThreadLocal<WebDriver> driver; driver = new ThreadLocal<WebDriver>() { @Override protected WebDriver initialValue() { return new FirefoxDriver(); // You can use other driver based on your requirement. } }; OR use Dependency Injection with non-static object
  • 16.
    Design Use Object-Oriented programmingconcepts (abstraction, encapsulation, inheritance, polymorphism) to make your test code maintainable. new AuthPage().login("username","password"); SellingPage sellingPage = new HomePage().startSelling(); sellingPage .setCategory(Category.EVERYTHING_ELSE) .setItemDetails("Carousell Test Item",ConditionType.NEW,"Test Description") .setPrice("10.12") .setDealDetails(DealType.MEETUP,"Lets meetup at 5 PM") .listIt(); AND enhance them using Dependency Injection
  • 17.
    Mutable state To avoidstatic objects being overwritten by other code, declare them final /** Contains all the constant system property names */ public final class SystemPropertyConstants { public static final String loginEndPoint = "/api/login/"; }
  • 18.
    What is Dependency Injection???? Dependency Injection DependencyNon-Injection public class Employee { private Address address; public Employee() { address = new Address(); } } Employee employee = new Employee(new address); Employee employee = new Employee(); employee.setAddress(new Address());
  • 19.
    SellingPage sellingPage =new SellingPage(driver); // Injecting driver dependency in pageobjects @BeforeMethod public void setUp(ITestContext testContext){ ... } // DI used by TestNG
  • 20.
    Dependency Injection usinga framework ● Use a DI framework instead of managing dependencies manually. ● DI frameworks: Spring, Google Guice, PicoContainer, Dagger ● These frameworks use IoC (Inversion of Control) container in which all the dependencies are registered, initialized and managed. ● IoC (Inversion of Control) - “Don’t call us, we call you” ● Once the dependencies are instantiated, the container injects them using approaches like: ○ Constructor injection ○ Setter method injection ○ Field injection
  • 21.
    Benefits of DependencyInjection ● Single Responsibility Principle ● Clean, Readable code ● Isolated components which become easy for testing as dependencies can be mocked without modifying depending class.
  • 22.
    How we can useDependency Injection frameworks to simplify automation scripts. 22
  • 23.
    Spring Dependency Injection SpringIoC Container (where all the magic happens!) Configuration in Java class or XML Read dependency and configuration details Create dependency objects and inject them POJOs/ Java Classes part of your application code/test code Usable system with all dependencies available
  • 24.
    @Component @Configuration @Autowired @Bean Appliedto fields, setter methods, and constructors. The @Autowired annotation injects object dependency implicitly. Used as config for Spring. Can have methods to instantiate and configure the dependencies Marks the Java class as a bean or component (i.e., you want Spring to manage this class instance) Applied to fields, setter methods, and constructors. The @Autowired annotation injects object dependency implicitly. Commonly used annotations in Spring Used for conditional loading of classes based on usecase (environment, platform etc) @Profile
  • 25.
    @Test() public void testNewListingAppearsInSearch()throws Exception { new WelcomePage(driver).beginSignUpOrLoginWithEmail(); new AuthPage(driver).login("username","password"); new HomePage(driver).startSellingWithPhotoFromCamera(); new CameraPage(driver).capturePhoto(); new CameraPhotoPreviewEditPage(driver).moveForward(); } } ● Driver is a dependency for PageObjects. ● PageObjects are a dependency for the test class (Is it necessary to create new instance of pageobject every time?)
  • 26.
    public class WelcomePageextends BasePage { private WebDriver driver; WelcomePage(WebDriver driver){ super(driver); this.driver = driver; } . . . . }
  • 27.
    @Configuration @ComponentScan(basePackages = "com.carousell") publicclass SpringContext { @Autowired private TestConfiguration configuration; private WebDriver driver; @Bean public WebDriver getWebDriver() { if(configuration.isWeb()){ driver = new ChromeDriver(); } else if(configuration.isAndroid() { driver = new AndroidDriver(); } else if(configuration.isIOS()) { driver = new IOSDriver(); } return driver; } Create a configuration class for Spring to manage dependencies
  • 28.
    public class WelcomePageextends BasePage { @Autowired private WebDriver driver; //You can move this to BasePage too . . . . }
  • 29.
    29 @Component public class WelcomePageextends BasePage { @Autowired private WebDriver driver; //You can move this to BasePage too . . . . }
  • 30.
    @Autowired WelcomePage welcomePage; @Autowired AuthPage authPage; .. . . . @Test() public void testNewListingAppearsInSearch() throws Exception { welcomPage.beginSignUpOrLoginWithEmail(); authPage.login("username","password"); homePage.startSellingWithPhotoFromCamera(); cameraPage.capturePhoto(); cameraPhotoPreviewEditPage.moveForward(); //Use references directly as they are already injected with instance by Spring }
  • 31.
    public abstract classWelcomePage extends BasePage { } @Component @Profile(Platform.ANDROID) public class AndroidWelcomePage extends WelcomePage { } @Component @Profile(Platform.IOS) public class IOSWelcomePage extends WelcomePage { } spring.profiles.active=ANDROID - Initializes AndroidWelcomePage and injects into an Autowired variable.
  • 32.
    Sounds good. Wherecan I find real world implementation of such framework? Check out https://www.justtestlah.qa/
  • 33.
    Python: dependency_injector, SpringPython Ruby: sinject, dry-container C#: Spring.Net , Castle Windsor, Unity, Autofac Javascript: BottleJS, InversifyJS, DI-Ninja
  • 34.
  • 35.
    Reference Inversion of Controland Dependency Injection https://martinfowler.com/bliki/InversionOfControl.html https://martinfowler.com/articles/injection.html https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring https://dzone.com/articles/a-guide-to-spring-framework-annotations https://stackoverflow.com/questions/52720198/how-does-spring-profile-work-with-inheritance Static methods and variables https://stackoverflow.com/questions/7026507/why-are-static-variables-considered-evil https://stackoverflow.com/questions/2671496/java-when-to-use-static-methods https://stackoverflow.com/questions/4002201/why-arent-static-methods-considered-good-oo-practice https://softwareengineering.stackexchange.com/questions/336701/static-services-and-testability Use of Dependency Injection in automation frameworks www.justtestlah.qa by Martin Schneider https://peterkedemo.wordpress.com/2013/03/30/writing-good-selenium-tests-with-page-objects-and-spring/

Editor's Notes

  • #11 Static objects represent global state and scope-less.
  • #24 this is just the tip of the iceberg that Spring offers. It's grown to become much more than a framework, Spring is actually an entire application development ecosystem. We only use spring-core to get the Spring container. Whether or not to use Spring in a testing framework depends on multiple factors, for example what other functionality is needed (for example, Spring has a good way of accessing web services). You could mention that Spring is not the most lightweight solution but one that's been around for many years and quite well maintained. Also chances are that engineers have experience with it (plus the experience they gain by using it is more valuable than e.g. just knowing Cucumber DI).