31 tháng 5 năm 2024 Công nghệ thông tin
Bài viết trước “Làm Thế Nào Để Sử Dụng Cucumber Java Cho Kiểm Tra Giao Diện Người Dùng?” đã minh họa cách tích hợp Cucumber Java với Selenium, cũng như quá trình thiết lập dự án kiểm tra giao diện và viết các trường hợp kiểm thử bằng ví dụ đăng nhập vào GitHub và tạo Issue trên trang web. Bạn có thể nhận thấy rằng, trong bài viết trước, công cụ dependency injection không được sử dụng, và việc tạo đối tượng đều được thực hiện theo cách thủ công bằng từ khóa new
. Điều này có thể trở nên phức tạp và khó quản lý khi áp dụng cho các dự án lớn hơn. Bài viết này sẽ tập trung vào cách tích hợp Cucumber Java với khung làm việc dependency injection PicoContainer, đồng thời cải tiến lại dự án kiểm tra từ bài viết trước, thay thế tất cả các phần tạo đối tượng thủ công bằng cách tự động hóa thông qua PicoContainer.
Trước tiên, chúng ta sẽ tìm hiểu lý do tại sao cần sử dụng dependency injection; sau đó thảo luận về cách viết mã code trước đây mà không có dependency injection cùng với những vấn đề phát sinh; cuối cùng là hướng dẫn chi tiết cách sử dụng PicoContainer để thực hiện dependency injection.
1. Tại Sao Cần Sử Dụng Dependency Injection?
Dependency injection không chỉ giúp giải quyết vấn đề tạo đối tượng một cách rườm rà mà còn mang lại nhiều lợi ích khác. Khi viết các tệp đặc điểm (feature files) của Cucumber (xxx.feature
), một file có thể chứa nhiều kịch bản (scenarios), và giữa các kịch bản hoặc các bước (steps) trong cùng một kịch bản có thể cần chia sẻ trạng thái (state). Nếu sử dụng thuộc tính tĩnh (static property) của lớp để lưu trữ trạng thái này, có thể xảy ra tình trạng rò rỉ thông tin, vì các thuộc tính tĩnh luôn tồn tại toàn cục trong suốt vòng đời của JVM.
Chúng ta biết rằng, mỗi lần thực thi một kịch bản mới, lớp định nghĩa bước (step definition class) của Cucumber cũng sẽ được tạo lại. Do đó, giữa các kịch bản sẽ không gặp phải vấn đề tái sử dụng hay rò rỉ thông tin từ các đối tượng step definition. Hơn nữa, các đối tượng Java bình thường được tiêm thông qua phương thức xây dựng (constructor injection) bởi PicoContainer cũng sẽ tự động được tạo và hủy bỏ theo từng kịch bản, điều này rất phù hợp để chia sẻ trạng thái trong phạm vi của một kịch bản.
Dưới đây, chúng ta sẽ xem xét lại một số lớp quan trọng trong dự án kiểm tra từ bài viết trước, cũng như cách tạo đối tượng thủ công.
2. Tạo Đối Tượng Thủ Công
Hãy cùng xem lại nội dung của tệp đặc điểm GitHub được sử dụng trong bài viết trước:
Feature: Kiểm Tra Giao Diện GitHub Issues
Scenario: Thêm Một Issue Mới
Given Đăng Nhập Vào GitHub
When Mở Trang Issues Và Thêm Một Issue Có Tiêu Đề "Cucumber UI Test"
Then Issue Được Thêm Thành Công Với Tiêu Đề "Cucumber UI Test"
Trong đó, bước Đăng nhập vào GitHub
tương ứng với đoạn mã Java của Step Definition như sau:
// Trước khi cải tiến
package com.example.tests.stepdefs;
import com.example.tests.pages.LoginPage;
import com.example.tests.utils.WebDriverFactory;
import io.cucumber.java.en.Given;
public class LoginStep {
private final LoginPage loginPage;
public LoginStep() {
loginPage = new LoginPage(WebDriverFactory.getWebDriver());
}
@Given("Đăng nhập vào GitHub")
public void login() {
loginPage.login();
}
}
Như vậy, bước này phụ thuộc vào một đối tượng LoginPage
, và việc tạo đối tượng này yêu cầu truyền vào một đối tượng WebDriver
.
Mã nguồn của lớp LoginPage
như sau:
// Trước khi cải tiến
package com.example.tests.pages;
import com.example.tests.utils.ConfigUtil;
import com.example.tests.utils.GoogleAuthenticatorUtil;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage {
private static final String LOGIN_URL = "đường dẫn đăng nhập";
public LoginPage(WebDriver driver) {
// ...
}
public void login() {
driver.get(LOGIN_URL);
driver.findElement(By.id("username")).sendKeys(ConfigUtil.getProperty("GITHUB_USERNAME"));
driver.findElement(By.id("password")).sendKeys(ConfigUtil.getProperty("GITHUB_PASSWORD"));
driver.findElement(By.id("sign-in-button")).click();
int code = GoogleAuthenticatorUtil.getTotpCode(ConfigUtil.getProperty("GITHUB_TOTP_SECRET"));
driver.findElement(By.id("totp-code")).sendKeys(String.valueOf(code));
}
}
Lớp này chỉ có một phương thức xây dựng (constructor) nhận tham số là WebDriver
.
Chúng ta đã thiết kế một lớp nhà máy (factory class) để chịu trách nhiệm tạo mới WebDriver
, mã nguồn như sau:
// Trước khi cải tiến
package com.example.tests.utils;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class WebDriverFactory {
private static final WebDriver driver [m88vin - cổng game quốc tế](/post/muslim-funeral-reading/) = new ChromeDriver();
public static WebDriver getWebDriver() {
return driver;
}
public static void closeWebDriver() {
driver.close();
}
}
Như vậy, trong lớp LoginStep
, chúng ta có thể sử dụng new LoginPage(WebDriverFactory.getWebDriver())
để truyền WebDriver
và tạo mới đối tượng LoginPage
.
Quá trình tạo đối tượng thủ công này khá phức tạp. Vậy nếu sử dụng khung làm việc PicoContainer, chúng ta cần thay đổi gì ở các lớp này?
3. Sử Dụng PicoContainer Để Thực Hiện Dependency Injection
Để sử dụng PicoContainer trong Cucumber Java, bạn cần thêm vào dự án các phụ thuộc sau:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
Dưới đây là cách cải tiến mã nguồn để sử dụng chức năng dependency injection của PicoContainer.
Lớp LoginStep
có thể được cải tiến như sau:
// Sau khi cải tiến
package com.example.tests.stepdefs;
import com.example.tests.pages.LoginPage;
import io.cucumber.java.en.Given;
public class LoginStep {
private final LoginPage loginPage;
public LoginStep(LoginPage loginPage) {
this.loginPage = loginPage;
}
@Given("Đăng nhập vào GitHub")
public void login() {
loginPage.login();
}
}
Như vậy, đối với lớp LoginStep
, chúng ta chỉ cần thêm một phương thức xây dựng nhận tham số là đối tượng LoginPage
.
Lớp LoginPage
có thể được cải tiến như sau:
// Sau khi cải tiến
package com.example.tests.pages;
import com.example.tests.driver.LazyWebDriver;
import org.openqa.selenium.By;
public class LoginPage {
private final LazyWebDriver driver;
public LoginPage(LazyWebDriver driver) {
this.driver = driver;
}
public void login() {
driver.get(LOGIN_URL);
driver.findElement(By.id("username")).sendKeys(...);
driver.findElement(By.id("password")).sendKeys(...);
driver.findElement(By.id("sign-in-button")).click();
driver.findElement(By.id("totp-code")).sendKeys(...);
}
}
Phương thức xây dựng của lớp này vẫn giống như trước, nhưng thay vì phụ thuộc vào WebDriver
, nó giờ phụ thuộc vào LazyWebDriver
. Chúng ta sẽ xem mã nguồn của LazyWebDriver
để hiểu rõ hơn.
Mã nguồn của LazyWebDriver
như sau:
// Sau khi cải tiến
package com.example.tests.driver;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.picocontainer.Disposable;
public class LazyWebDriver implements WebDriver, Disposable {
private WebDriver delegate = null;
private WebDriver getDelegate() {
if (delegate == null) {
delegate = new ChromeDriver();
}
return delegate;
}
@Override
public void get(String url) {
getDelegate().get(url);
}
@Override
public WebElement findElement(By by) {
return getDelegate().findElement(by);
}
@Override
public void dispose() {
System.out.println("Đang đóng WebDriver");
if (delegate != null) {
delegate.quit();
}
}
}
LazyWebDriver
chịu trách nhiệm cung cấp đối tượng WebDriver
của Selenium. Nó cũng triển khai giao diện WebDriver
và Disposable
. Giao diện Disposable
của PicoContainer cho phép thực hiện một số thao tác khi chương trình kết thúc (ở đây dùng để đóng WebDriver
).
Khi sử dụng PicoContainer, chúng ta chỉ cần khai báo các phụ thuộc dưới dạng tham số của phương thức xây dựng. Quá trình thực hiện dependency injection của PicoContainer diễn ra như sau:
- Trong ví dụ này, mối quan hệ phụ thuộc là:
LoginStep
->LoginPage
->LazyWebDriver
. LazyWebDriver
có một phương thức xây dựng mặc định không có tham số, PicoContainer sẽ tự động tạo nó.- Tiếp theo, dựa vào phương thức xây dựng của
LoginPage
, PicoContainer sẽ truyền đối tượngLazyWebDriver
và tạo mới một instance củaLoginPage
. - Cuối cùng, dựa vào phương thức xây dựng của
LoginStep
, PicoContainer sẽ truyền đối tượngLoginPage
và tạo mới một instance củaLoginStep
.
4. Kết Luận
Bài viết này đã cải tiến dự án kiểm tra từ bài viết trước, chỉ ra những hạn chế của việc tạo đối tượng thủ công và giới thiệu khái niệm dependency injection. Sau đó, chúng ta đã áp dụng PicoContainer để cải tiến dự án, mô tả cách sử dụng và cơ chế hoạt động của nó.
Phiên bản hoàn chỉnh của dự án kiểm tra đã được đẩy lên GitHub cá nhân của tôi, mọi người có thể theo dõi hoặc fork.
[1] Tài liệu Cucumber: Dependency Injection - [2] Lê Lê Lạc Lạc: Làm Thế Nào Để Sử Dụng Cucumber Java Cho Kiểm Tra Giao Diện? - [3] GitHub: Tài Liệu Sử Dụng PicoContainer Của Cucumber - [4] Think Code: Chia Sẻ Trạng Thái Giữa Các Bước Trong Cucumber-JVM Sử Dụng PicoContainer -  #Kiểm tra tự động #Cucumber #Java #Selenium