Khi sử dụng Singleton Design Pattern TestNG để quản lý trình điều khiển Driver trong một framework phổ biến như là Selenium WebDriver, bạn cần nghĩ đến việc chạy test song song. Trong ví dụ ở bài Sử dụng Singleton design pattern trong Automation test bạn sẽ thấy khi chạy chỉ có duy nhất một driver instance, vì vậy bạn không thể thực thi việc chạy test song song. Vậy chúng ta sẽ làm thế nào đây?
Tuy nhiên như bạn biết đấy, Java có hỗ trợ một giải pháp, đó là sử dụng cấu trúc ThreadLocal.
Trong bài này tôi sẽ chia sẽ với cách bạn cách run test parallel (chạy song song) với Singleton design pattern sử dụng ThreadLocal.
Trước khi bắt đầu bạn cần biết:
- Java Maven project: bạn xem bài Tạo Selenium Automation Test với maven – Intellij IDEA
- Singleton Design Pattern: bạn xem bài Sử dụng Singleton design pattern trong Automation test nhé
- WebDriverManager: Đây là một thư viện Java nguồn mở thực hiện việc quản lý (Download, setup và bảo trì) các trình điều khiển driver theo yêu cầu của Selenium WebDriver (ví dụ: chromedriver, geckodriver, msedgedriver, v.v.) một cách hoàn toàn tự động.
- TestNG: dùng để thực hiện việc chạy test song song. Bạn tìm hiểu thêm ở đây.
Giờ chúng ta bắt đầu nhé.
ThreadLocal là gì?
Java documentation định nghĩa ThreadLocal như sau.
Class cung cấp các biến thread-local thì những biến này khác với nhứng bản sao thông thường của chúng ở mỗi luồng truy cập một (thông qua phương thức get() hoặc set() của nó) được khởi tạo một cách độc lập.
Có nghĩa là:
- Cấu trúc TheadLocal cho phép chúng ta lưu trữ dữ liệu mà chỉ một luồng (thread) cụ thể mới có thể truy cập được.
- Tiếp đến, khi muốn sử dụng giá trị này từ một luồng (thread), chúng ta chỉ cần gọi phương thức get() hoặc set().
Nói một cách đơn giản, bạn có thể xem như ThreadLocal lưu trữ dữ liệu bên trong map với thread là key.
Dựa vào định nghĩa trên để ứng dụng vào quản lý driver trong automation test là mỗi luồng sẽ có bản sao trình điều khiển driver riêng, giúp cho việc kiểm tra song song có thể thực hiện được. Đây là cách mà chúng ta có thể sử dụng Singleton design pattern để quản lý phiên bản trình điều khiển driver.
Ví dụ:
- Tạo một ThreadLocal có giá trị là WebDriver sẽ được gói trong 1 Thread cụ thể như sau:
ThreadLocal<WebDriver> driver = new ThreadLocal<>();
- Để lưu dữ liệu vào chúng ta sẽ dụng phương thức set()
driver.set(new ChromeDriver());
- Để lấy dữ liệu ra chúng ta sử dụng phương thức get()
driver.get();
- Khi sử dụng ThreadLocal xong, chúng sử dụng phương thức remove() để xoá dữ liệu trong ThreadLocal
driver.remove();
driver.remove();
Tạo một Singleton class sử dụng ThreadLocal quản lý WebDriver
Maven dependences cần tải về
- Selenium
- WebDriverManager
- TestNG
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>SingletonDesignPattern_Selenium</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>18</maven.compiler.source> <maven.compiler.target>18</maven.compiler.target> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>3.141.59</version> </dependency> <!-- https://mvnrepository.com/artifact/io.github.bonigarcia/webdrivermanager --> <dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>3.6.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.testng/testng --> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.5</version> </dependency> </dependencies> </project>
Tạo Singleton class
Chúng ta sẽ tạo một Singleton class tên SingletonThreadLocalDriver với:
- Biến private static thuộc cấu trúc ThreadLocal có giá trị là WebDriver
- private contructor để ngăn việc tạo mới một instance của Driver
- Phương thức public cho phép truy cập driver instance
- Phương thức quit() làm nhiệm vụ thoát hết tất cả browser sau khi làm việc và gỡ bỏ giá trị WebDriver hiện tại của Thread-local.
Class SingletonThreadLocalDriver.java
package co.devopsify.singletondesignpatter; import io.github.bonigarcia.wdm.WebDriverManager; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.edge.EdgeDriver; import org.openqa.selenium.firefox.FirefoxDriver; public class SingletonThreadLocalDriver { // 1. Using ThreadLocal to manage the driver private static ThreadLocal<WebDriver> driver = new ThreadLocal<>(); // 2. Define private Constructor to prevent the creation of new instances of Driver private SingletonThreadLocalDriver(){}; // 3. Define Public method to access the driver instance public static WebDriver getInstance(String browser){ if(driver.get()==null){ switch (browser){ case "edge":{ WebDriverManager.edgedriver().setup(); driver.set(new EdgeDriver()); break; } case "firefox":{ WebDriverManager.firefoxdriver().setup(); driver.set(new FirefoxDriver()); break; } default:{ WebDriverManager.chromedriver().setup(); driver.set(new ChromeDriver()); break; } } } return driver.get(); } // 4. Define method to quit the driver and remove the current thread's value for this thread-local variable public static void quit() { driver.get().quit(); driver.remove(); } }
Tạo class TestNG
Ví dụ một lớp test BrowserNavigation với các phương thức test được đánh dấu annotation @Test của TestNG:
- test1() – cho phép truy cập trang web https://devopsify.co/ với trình duyệt mặc định (chrome)
- test2() – cho phép truy cập trang web https://devopsify.co/ với trình duyệt firefox
- test3() – cho phép truy cập trang web https://devopsify.co/ với trình duyệt chrome
Lưu ý: Trong class này chúng ta định nghĩa phương thức tearDown() được đánh dấu annotation @AfterMethod với nhiệm vụ đóng tất cả trình duyệt và loại bỏ webDriver hiện tại của Thread-Local. Phương thức này sẽ được gọi sau khi thực hiện từng phương thức @test (vd: test1(), test2(), test3()), có nghĩa là với ví dụ này phươngt thức tearDown() sẽ được thực thi 3 lần.
Class BrowserNavigation.java
package co.devopsify.singletondesignpatter; import org.testng.annotations.*; public class BrowserNavigation { @Test void test1() { System.out.println("Run test 1"); SingletonThreadLocalDriver.getInstance("").get("https://devopsify.co/"); } @Test void test2() { System.out.println("Run test 2"); SingletonThreadLocalDriver.getInstance("firefox").get("https://devopsify.co/"); } @Test void test3() { System.out.println("Run test 3"); SingletonThreadLocalDriver.getInstance("chrome").get("https://devopsify.co/"); } @AfterMethod void tearDown(){ SingletonThreadLocalDriver.quit(); } }
Tạo file testng.xml điều khiển việc chạy test
Chúng ta sẽ cho chạy test song song các phương thức test được định nghĩa trong Class BrowserNavigation với
<suite name="Parallel Test Suite" parallel="methods">
testng.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"> <suite name="Parallel Test Suite" parallel="methods"> <test name="Test example with Singleton Driver"> <classes> <class name="co.devopsify.singletondesignpatter.BrowserNavigation"/> </classes> </test> </suite>
Khi chạy test suite trên, chúng ta sẽ thấy 3 test được chạy song song trên 3 browser khác nhau:
- test1() – trình duyệt Chrome được bật lên và truy cập vào trang web https://devopsify.co/
- test2() – trình duyệt firefox được bật lên và truy cập vào trang web https://devopsify.co/
- test3() – trình duyệt Chrome được bật lên và truy cập vào trang web https://devopsify.co/
- Sau khi chạy xong 3 browser tuần tự sẽ được đóng lại.
Cheers! 🙂