Cách tích hợp Cucumber Java với Spring Boot - bắn cá đổi thẻ cào
Ngày 02 tháng 6 năm 2024 - Máy tính
Trước đây, trong bài viết "Làm thế nào để sử dụng Cucumber Java cho kiểm thử giao diện người dùng?", chúng tôi đã trình bày cách tích hợp Cucumber với Selenium thông qua ví dụ đăng nhập GitHub và tạo Issue trên trang. Tuy nhiên, trong ví dụ đó, chúng ta chưa sử dụng công cụ tiêm phụ thuộc (dependency injection), mà tất cả các đối tượng đều được khởi tạo bằng từ khóa new
nguyên bản. Sau đó, trong bài viết "Làm thế nào để sử dụng PicoContainer trong Cucumber Java để thực hiện tiêm phụ thuộc?", chúng tôi đã giới thiệu phương pháp sử dụng PicoContainer để tiêm phụ thuộc trong Cucumber. Mặc dù PicoContainer khá nhẹ nhàng và là công cụ tiêm phụ thuộc được khuyến nghị bởi Cucumber chính thức, nhưng trong hệ sinh thái Java, Spring hoặc Spring Boot mới là khung làm việc phổ biến nhất. Ngoài chức năng tiêm phụ thuộc, Spring còn cung cấp nhiều tính năng hữu ích khác (như cấu hình linh hoạt, kết nối cơ sở dữ liệu thuận tiện, cách tiếp cận dễ dàng để tích hợp thành phần, v.v.). Vì vậy, việc tìm hiểu cách tích hợp Cucumber với Spring Boot là rất cần thiết. Trong bài viết này, chúng tôi sẽ tiếp tục hai bài trước, cũng với kịch bản kiểm thử đăng nhập GitHub và tạo Issue trên trang, để minh họa cách tích hợp Cucumber với Spring Boot. Ngôn ngữ lập trình của ví dụ này là Java, công cụ kiểm thử trình duyệt là Selenium, và dự án được quản lý bằng Maven.
Trước khi bắt đầu, hãy liệt kê các phiên bản JDK, Maven, Spring Boot và Cucumber được sử dụng trong ví dụ này:
1JDK: BellSoft Liberica 17.0.7
2Maven: 3.9.2
3Spring Boot: 3.3.0
4Cucumber Java: 7.18.0
1. Cấu trúc dự án và m88vin - cổng game quốc tế phụ thuộc Maven
Cấu trúc của dự án kiểm thử này như sau:
1cucumber-spring-boot-integration-demo
2├─ src/test
3│ ├─ java
4│ │ └─ com.example.tests
5│ │ ├─ conf
6│ │ │ ├─ ApplicationConf.java
7│ │ │ ├─ WebDriverBean.java
8│ │ │ └─ CucumberSpringIntegrationTest.java
9│ │ ├─ stepdefs
10│ │ │ ├─ LoginStep.java
11│ │ │ └─ CreateIssueStep.java
12│ │ ├─ pages
13│ │ │ ├─ LoginPage.java
14│ │ │ └─ CreateIssuePage.java
15│ │ ├─ utils
16│ │ │ └─ GoogleAuthenticatorUtil.java
17│ │ ├─ hooks
18│ │ │ └─ ScreenshotHook.java
19│ │ ├─ DummyApplication.java
20│ │ └─ TestRunner.java
21│ └─ resources
22│ ├─ features
23│ │ └─ github-issues.feature
24│ └─ application.yaml
25└─ pom.xml
Dưới đây là tóm tắt về vai trò của từng gói, lớp và thư mục:
- Gói
conf
chứa các lớp cấu hình khác nhau. - Gói
stepdefs
chứa các lớp thực thi định nghĩa bước (Step Definition) cho các tập tin đặc tả đặc điểm (feature file) của Cucumber. - Gói
pages
chứa các lớp đối tượng trang (page object), chịu trách nhiệm hỗ trợ các bước trongstepdefs
. - Gói
utils
chứa các lớp công cụ Java, bao gồm lớp công cụ lấy mã xác thực hai yếu tố cho đăng nhập GitHub. - Gói
hooks
chứa các móc (hook) Cucumber, có thể thêm logic bổ sung trước hoặc sau khi chạy kịch bản (scenario) hoặc bước (step). - Lớp
DummyApplication.java
là một lớp khởi động rỗng cho ứng dụng Spring Boot. - Lớp
TestRunner.java
là điểm vào để thực thi các trường hợp kiểm thử. - Thư mục
resources/features
chứa các tập tin đặc tả đặc điểm của Cucumber. - Tập tin
resources/application.yaml
là tập tin cấu hình của dự án.
Dự án kiểm thử này là một dự án Spring Boot, yêu cầu phải tham chiếu Parent sau:
1<parent>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-parent</artifactId>
4 <version>3.3.0</version>
5 <relativePath/>
6</parent>
Các phụ thuộc được sử dụng bao gồm:
1<dependencies>
2 <!-- spring boot -->
3 <dependency>
4 <groupId>org.springframework.boot</groupId>
5 <artifactId>spring-boot-starter-web</artifactId>
6 <scope>test</scope>
7 </dependency>
8 <dependency>
9 <groupId>org.springframework.boot</groupId>
10 <artifactId>spring-boot-starter-test</artifactId>
11 <scope>test</scope>
12 </dependency>
13 <!-- cucumber -->
14 <dependency>
15 <groupId>io.cucumber</groupId>
16 <artifactId>cucumber-java</artifactId>
17 <version>${cucumber.version}</version>
18 <scope>test</scope>
19 </dependency>
20 <dependency>
21 <groupId>io.cucumber</groupId>
22 <artifactId>cucumber-junit</artifactId>
23 <version>${cucumber.version}</version>
24 <scope>test</scope>
25 </dependency>
26 <dependency>
27 <groupId>io.cucumber</groupId>
28 <artifactId>cucumber-spring</artifactId>
29 <version>${cucumber.version}</version>
30 <scope>test</scope>
31 </dependency>
32 <!-- selenium -->
33 <dependency>
34 <groupId>org.seleniumhq.selenium</groupId>
35 <artifactId>selenium-java</artifactId>
36 <version>${selenium.version}</version>
37 <scope>test</scope>
38 </dependency>
39 <!-- google authenticator -->
40 <dependency>
41 <groupId>com.warrenstrange</groupId>
42 <artifactId>googleauth</artifactId>
43 <version>1.5.0</version>
44 <scope>test</scope>
45 </dependency>
46 <!-- lombok -->
47 <dependency>
48 <groupId>org.projectlombok</groupId>
49 <artifactId>lombok</artifactId>
50 <version>1.18.32</version>
51 <scope>provided</scope>
52 </dependency>
53 <!-- junit vintage -->
54 <dependency>
55 <groupId>org.junit.vintage</groupId>
56 <artifactId>junit-vintage-engine</artifactId>
57 <version>${junit-vintage.version}</version>
58 <scope>test</scope>
59 </dependency>
60</dependencies>
Có thể thấy rằng dự án này chủ yếu phụ thuộc vào Spring Boot, Cucumber và Selenium.
Ngoài ra, nó còn phụ thuộc vào plugin tạo báo cáo HTML maven-cucumber-reporting
:
1<plugin>
2 <groupId>net.masterthought</groupId>
3 <artifactId>maven-cucumber-reporting</artifactId>
4 <version>5.8.1</version>
5</plugin>
Sau khi giới thiệu cấu trúc tổng thể của dự án và phụ thuộc Maven, bây giờ chúng ta sẽ lần lượt xem xét các tập tin đặc điểm Cucumber, các gói và lớp của dự án.
2. Tập tin đặc điểm Cucumber
Nội dung của tập tin đặc điểm Cucumber github-issues.feature
như sau:
1Tính năng: Kiểm thử giao diện người dùng Issues GitHub
2 Kịch bản: Thêm một Issue mới
3 Giả sử đăng nhập vào GitHub
4 Khi mở trang Issues và thêm một Issue có tiêu đề "Cucumber UI Test"
5 Thì Issue được thêm thành công và có tiêu đề "Cucumber UI Test"
Có thể thấy rằng nó mô tả cách thêm một Issue trên GitHub bằng ngôn ngữ Gherkin.
3. Gói conf
Chúng tôi đặt tất cả các lớp liên quan đến cấu hình vào gói này.
Để tích hợp Cucumber với Spring Boot, mỗi lớp định nghĩa bước phải được coi là một lớp kiểm thử Spring Boot có thể chạy độc lập, cần được đánh dấu bằng chú thích @SpringBootTest
. Trong bài viết này, chúng tôi thiết lập một lớp cha @SpringBootTest
là CucumberSpringIntegrationTest.java
, tất cả các lớp định nghĩa bước đều kế thừa từ lớp này.
Nội dung của CucumberSpringIntegrationTest.java
như sau:
1// src/test/java/com/example/tests/conf/CucumberSpringIntegrationTest.java
2package com.example.tests.conf;
3
4import com.example.tests.DummyApplication;
5import io.cucumber.spring.CucumberContextConfiguration;
6import org.springframework.boot.test.context.SpringBootTest;
7
8@CucumberContextConfiguration
9@SpringBootTest(classes = DummyApplication.class)
10public class CucumberSpringIntegrationTest {}
Sau khi tích hợp Spring, tất cả các Bean đều được quản lý bởi container Spring. Do đó, việc khởi tạo Selenium WebDriver trở nên rất đơn giản, chỉ cần thiết lập một lớp cấu hình và đánh dấu phương thức lấy instance tương ứng bằng chú thích @Bean
.
WebDriverBean.java
chịu trách nhiệm lấy instance của WebDriver, nội dung như sau:
1// src/test/java/com/example/tests/conf/WebDriverBean.java
2package com.example.tests.conf;
3
4import org.openqa.selenium.WebDriver;
5import org.openqa.selenium.chrome.ChromeDriver;
6import org.springframework.context.annotation.Bean;
7import org.springframework.context.annotation.Configuration;
8
9@Configuration
10public class WebDriverBean {
11 @Bean
12 public WebDriver webDriver() {
13 return new ChromeDriver();
14 }
15}
Sử dụng Spring Boot giúp việc đọc file trở nên cực kỳ đơn giản, không cần tự thực hiện đọc và phân tích file nữa, chỉ cần sử dụng các chú thích phù hợp.
ApplicationConf.java
dùng để đọc các biến từ tập tin cấu hình application.yaml
, nội dung như sau:
1// src/test/java/com/example/tests/conf/ApplicationConf.java
2package com.example.tests.conf;
3
4import lombok.Getter;
5import org.springframework.beans.factory.annotation.Value;
6import org.springframework.context.annotation.Configuration;
7
8@Getter
9@Configuration
10public class ApplicationConf {
11 @Value("${github.repo}")
12 private String githubRepo;
13
14 @Value("${github.username}")
15 private String githubUsername;
16
17 @Value("${github.password}")
18 private String githubPassword;
19
20 @Value("${github.totp-secret}")
21 private String githubTotpSecret;
22}
4. Gói stepdefs
Tất cả các lớp thực thi định nghĩa bước của tập tin đặc điểm Selenium đều nằm trong gói này.
Lớp thực thi bước Given
tương ứng với tập tin đặc điểm, dùng để đăng nhập vào GitHub, là LoginStep.java
, nội dung như sau:
1// src/test/java/com/example/tests/stepdefs/LoginStep.java
2package com.example.tests.stepdefs;
3
4import com.example.tests.conf.CucumberSpringIntegrationTest;
5import com.example.tests.pages.LoginPage;
6import io.cucumber.java.en.Given;
7import org.springframework.beans.factory.annotation.Autowired;
8
9public class LoginStep extends CucumberSpringIntegrationTest {
10 @Autowired
11 private LoginPage loginPage;
12
13 @Given("đăng nhập vào GitHub")
14 public void login() {
15 loginPage.login();
16 }
17}
Có thể thấy rằng nó kế thừa lớp cha CucumberSpringIntegrationTest
đã định nghĩa ở trên và sử dụng chú thích @Autowired
để tiêm phụ thuộc LoginPage
, sau đó gọi phương thức login()
của LoginPage
.
Lớp thực thi bước When
và Then
tương ứng với tập tin đặc điểm, dùng để mở trang, thêm Issue và kiểm tra tiêu đề Issue, là CreateIssueStep
, nội dung như sau:
1// src/test/java/com/example/tests/stepdefs/CreateIssueStep.java
2package com.example.tests.stepdefs;
3
4import com.example.tests.conf.CucumberSpringIntegrationTest;
5import com.example.tests.pages.CreateIssuePage;
6import io.cucumber.java.en.Then;
7import io.cucumber.java.en.When;
8import org.springframework.beans.factory.annotation.Autowired;
9import static org.hamcrest.CoreMatchers.startsWith;
10import static org.hamcrest.MatcherAssert.assertThat;
11
12public class CreateIssueStep extends CucumberSpringIntegrationTest {
13 @Autowired
14 private CreateIssuePage issuesPage;
15
16 @When("mở trang Issues và thêm một Issue có tiêu đề {string}")
17 public void createIssue(String title) {
18 // tạo issue
19 issuesPage.createIssue(title);
20 }
21
22 @Then("Issue được thêm thành công và có tiêu đề {string}")
23 public void checkTitle(String title) {
24 assertThat(issuesPage.getTitle(), startsWith(title));
25 }
26}
Nó cũng kế thừa CucumberSpringIntegrationTest
và sử dụng chú thích @Autowired
để tiêm phụ thuộc CreateIssuePage
.
5. Gói pages
Gói này dùng để lưu trữ các lớp đối tượng trang, các lớp này chứa các phần tử và hành vi của trang. Các phần tử trang được định nghĩa dưới dạng thuộc tính, các hành vi trang được định nghĩa dưới dạng phương thức.
Lớp LoginPage
tương ứng với trang đăng nhập GitHub, nội dung như sau:
1// src/test/java/com/example/tests/pages/LoginPage.java
2package com.example.tests.pages;
3
4import com.example.tests.conf.ApplicationConf;
5import com.example.tests.utils.GoogleAuthenticatorUtil;
6import org.openqa.selenium.By;
7import org.openqa.selenium.WebDriver;
8import org.springframework.beans.factory.annotation.Autowired;
9import org.springframework.stereotype.Component;
10
11@Component
12public class LoginPage {
13 private static final String LOGIN_URL = "URL_ĐĂNG_NHẬP";
14
15 @Autowired
16 private ApplicationConf applicationConf;
17
18 @Autowired
19 private WebDriver driver;
20
21 public void login() {
22 // mở URL đăng nhập
23 driver.get(LOGIN_URL);
24
25 // nhập tên người dùng và mật khẩu
26 driver.findElement(By.id("username")).sendKeys(applicationConf.getGithubUsername());
27 driver.findElement(By.id("password")).sendKeys(applicationConf.getGithubPassword());
28
29 // nhấn nút "Sign in"
30 driver.findElement(By.id("sign-in-button")).click();
31
32 // nhập mã xác thực
33 int code = GoogleAuthenticatorUtil.getTotpCode(applicationConf.getGithubTotpSecret());
34 driver.findElement(By.id("totp-code")).sendKeys(Integer.toString(code));
35 }
36}
Có thể thấy rằng nó sử dụng chú thích @Component
để giao việc khởi tạo cho container Spring và sử dụng @Autowired
để tiêm các phụ thuộc cần thiết.
Lớp CreateIssuePage
tương ứng với trang tạo Issue, nội dung như sau:
1// src/test/java/com/example/tests/pages/CreateIssuePage.java
2package com.example.tests.pages;
3
4import com.example.tests.conf.ApplicationConf;
5import org.openqa.selenium.By;
6import org.openqa.selenium.WebDriver;
7import org.openqa.selenium.support.ui.ExpectedConditions;
8import org.openqa.selenium.support.ui.WebDriverWait;
9import org.springframework.beans.factory.annotation.Autowired;
10import org.springframework.stereotype.Component;
11import java.time.Duration;
12
13@Component
14public class CreateIssuePage {
15 private static final String CREATE_ISSUE_URL = "/issues/new";
16 private static final By INPUT_TITLE_ELEM = By.xpath("//input[@id='issue_title']");
17 private static final By SUBMIT_BUTTON = By.xpath("//button[contains(text(), 'Submit new issue')]");
18
19 @Autowired
20 private ApplicationConf applicationConf;
21
22 @Autowired
23 private WebDriver driver;
24
25 public void createIssue(String title) {
26 // mở URL tạo Issue
27 driver.get(applicationConf.getGithubRepo() + CREATE_ISSUE_URL);
28
29 // đợi phần tử hiển thị
30 new WebDriverWait(driver, Duration.ofMinutes(1)).until(ExpectedConditions.visibilityOfElementLocated(INPUT_TITLE_ELEM));
31
32 // nhập tiêu đề
33 driver.findElement(INPUT_TITLE_ELEM).sendKeys(title);
34
35 // nộp
36 driver.findElement(SUBMIT_BUTTON).click();
37 }
38
39 public String getTitle() {
40 return driver.getTitle();
41 }
42}
Nó cũng sử dụng chú thích @Component
và @Autowired
.
Với những gì đã trình bày, chúng ta đã giới thiệu chi tiết các gói và tập tin chính của dự án kiểm thử này. Các gói khác như hooks
và utils
lần lượt quản lý các móc Cucumber và các lớp công cụ Java. Chúng tôi đã đặt một móc chụp ảnh màn hình sau mỗi bước và một công cụ tạo mã xác thực Google Authentication trong các gói này. Bạn có thể tham khảo mã nguồn của các lớp này qua liên kết cuối bài nếu muốn.
6. Lớp DummyApplication
Do đây là một dự án Spring Boot, nó cần một lớp khởi động làm điểm vào.
Chúng tôi đã thêm một lớp khởi động rỗng DummyApplication.java
, nội dung như sau:
1// src/test/java/com/example/tests/DummyApplication.java
2package com.example.tests;
3
4import org.springframework.boot.SpringApplication;
5import org.springframework.boot.autoconfigure.SpringBootApplication;
6
7@SpringBootApplication
8public class DummyApplication {
9 public static void main(String[] args) {
10 SpringApplication.run(DummyApplication.class, args);
11 }
12}
7. Lớp TestRunner
Điểm vào để thực thi các trường hợp kiểm thử Cucumber là lớp TestRunner.java
, nội dung như sau:
1// src/test/java/com/example/tests/TestRunner.java
2package com.example.tests;
3
4import [ty le bd](/post/xyz202221.html) io.cucumber.junit.Cucumber;
5import io.cucumber.junit.CucumberOptions;
6import org.junit.runner.RunWith;
7
8@RunWith(Cucumber.class)
9@CucumberOptions(features = "src/test/resources/features", plugin = {"json:target/cucumber.json"})
10public class TestRunner {}
Nó chỉ định cách chạy, vị trí tập tin đặc điểm và vị trí báo cáo JSON.
8. Chạy dự án và hiển thị báo cáo
Nếu bạn sử dụng IntelliJ IDEA để mở dự án này, bạn có thể chạy chế độ DEBUG trực tiếp trên tệp TestRunner.java
. Hiệu quả chạy như sau:

Hiệu quả tự động kiểm thử trình duyệt giống với ví dụ trong bài viết trước "Làm thế nào để sử dụng Cucumber Java cho kiểm thử giao diện người dùng?". 
Nếu sử dụng dòng lệnh, bạn có thể chạy bằng lệnh Maven sau:
1mvn clean verify
Sau khi chạy xong, báo cáo HTML sẽ được tạo trong thư mục target/cucumer-report-html
, hiệu quả như sau:

9. Kết luận
Bài viết này đã trình bày cách tích hợp Cucumber với Spring Boot thông qua ví dụ đăng nhập GitHub và tạo Issue trên trang. Dự án kiểm thử hoàn chỉnh đã được tải lên GitHub của tôi, mời bạn theo dõi hoặc Fork.
[1] GitHub: Cucumber Spring Sample Project - [2] Medium: Integrating Cucumber into a Spring Boot Project - [3] Baeldung: Cucumber Spring Integration -
#Tự động hóa kiểm thử #Cucumber #Spring #Java #Selenium