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 trong stepdefs.
  • 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 @SpringBootTestCucumberSpringIntegrationTest.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 WhenThen 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@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ư hooksutils 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: ![](Hình ảnh chạy dự án trong IntelliJ IDEA)

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?". ![](Hiệu quả tạo Issue trên GitHub)

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: ![](Báo cáo HTML tạo Issue trên GitHub)

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