Ở bài trước mình đã nói về Page Object Model - cách tổ chức code sao cho gọn gàng. Bài này mình sẽ giới thiệu một công cụ khác cũng rất phổ biến trong automation test: Cucumber, cùng với ngôn ngữ đi kèm của nó là Gherkin.

Bạn thử tưởng tượng: Dev, Tester, BA ngồi họp để chốt yêu cầu cho chức năng "Login". Lúc này BA nói: "Khi user nhập sai mật khẩu thì phải hiện thông báo lỗi". Nhưng vì test nằm trong code nên chỉ có Automation Tester/Dev đọc hiểu được.

test('shows an error when login fails', async ({ page }) => {
 const loginPage = new LoginPage(page);
 await page.goto('https://example.com/login');
 await loginPage.login('wronguser', 'wrongpass');
 await expect(loginPage.errorMessage).toHaveText('Invalid username or password');
});

Nhưng nếu BA, Manual Tester hay người không biết code muốn cùng review testcase thì đoạn code trên khá khó tiếp cận. Sẽ dễ hơn nếu có một cách mô tả behavior của hệ thống bằng ngôn ngữ mà cả technical và non-technical đều có thể đọc hiểu. Đây là nơi Gherkin và Cucumber phát huy tác dụng.

Gherkin là gì?

Gherkin là một ngôn ngữ viết kịch bản test theo cấu trúc gần với ngôn ngữ nói, dùng các keyword cố định: Feature, Scenario, Given, When, Then, And, But. Bạn có thể nghĩ Gherkin như một "công thức nấu ăn" - ai đọc vào cũng hiểu các bước cần làm, dù biết nấu ăn hay không.

Ví dụ, kịch bản ở trên viết bằng Gherkin sẽ như thế này:

Feature: Login
 Scenario: Login with invalid credentials
  Given I am on the login page
  When I log in with username "wrongUser" and password "wrongPass"
  Then I should see the error message "Invalid username or password"

Nhờ cách mô tả này, BA, Tester và Dev có thể cùng trao đổi về behavior bằng một ngôn ngữ thống nhất và dễ hiểu hơn. Trong BDD, chúng ta hướng đến một shared language dựa trên thuật ngữ nghiệp vụ mà cả team đều hiểu.

Cucumber là gì?

Nếu Gherkin là ngôn ngữ để viết kịch bản, thì Cucumber là công cụ giúp chạy kịch bản đó như một bài test thật sự. Cucumber đọc file .feature (chứa các dòng Gherkin), rồi tìm đoạn code tương ứng để thực thi từng bước - đoạn code đó gọi là step definition.

Trong ví dụ Web Automation này, có thể hình dung Gherkin là "kịch bản", Cucumber là "đạo diễn" kết nối kịch bản đó với các hành động thật trên Browser.

Cấu trúc một file .feature

  • Feature: mô tả một tính năng lớn (ví dụ: Login, Shopping Cart...)
  • Scenario: một tình huống cụ thể cần kiểm thử (ví dụ: login sai, login đúng...)
  • Given: thiết lập bối cảnh ban đầu ("với điều kiện...")
  • When: hành động người dùng thực hiện ("khi user...")
  • Then: kết quả mong đợi ("thì...")
  • And / But: nối thêm bước, đọc tự nhiên hơn là lặp lại Given/When/Then

Nối tiếp ví dụ ở bài trước, giờ ta viết lại chức năng đăng nhập theo Cucumber:

Bước 1: Viết file .feature

# features/login.feature

Feature: Login
 Scenario: Login with invalid credentials
  Given I am on the login page
  When I log in with username "wronguser" and password "wrongpass"
  Then I should see the error message "Invalid username or password"

Bước 2: Viết step definition - "phiên dịch" từng dòng Gherkin thành code thật

/# features/step_definitions/login.steps.ts

import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';

// Giả sử page và loginPage đã được khởi tạo ở phần setup

Given('I am on the login page', async () => {
 await page.goto('https://example.com/login');
});

When('I log in with username {string} and password {string}',
 async (username: string, password: string) => {
  await loginPage.login(username, password);
 }
);

Then('I should see the error message {string}',
 async (expectedMessage: string) => {
  await expect(loginPage.errorMessage)
   .toHaveText(expectedMessage);
 }
);

Mình vẫn dùng lại LoginPage - Page Object viết ở bài trước! Như vậy, có thể thấy Cucumber và POM không cạnh tranh nhau, mà bổ trợ cho nhau. Gherkin lo phần "đọc hiểu được", Page Object lo phần "thao tác với UI", còn step definition là lớp keo dán hai bên lại.

Mẹo nhỏ khi áp dụng

  1. Viết Scenario theo góc nhìn người dùng, không theo góc nhìn kĩ thuật. Nên viết "When I click the Login button" thay vì "I click the element with id loginBtn" - vì mục tiêu là ai đọc cũng hiểu. Gherkin nên tập trung vào behavior thay vì chi tiết implementation
  2. Đừng lạm dụng Given quá nhiều bước nhỏ lẻ. Nếu Given có tới 5-6 bước rời rạc, hãy gộp lại thành 1 câu có ý nghĩa nghiệp vụ rõ ràng hơn, ví dụ gộp thành Given I am logged in as an admin.
  3. Tái sử dụng khi các step thực sự có cùng ý nghĩa, không cần cố gộp mọi step chỉ để giảm số lượng code.
  4. Kết hợp Background khi nhiều Scenario trong cùng 1 Feature đều cần chung bước thiết lập ban đầu (ví dụ luôn cần "Given I am on the login page"), tránh lặp lại ở từng Scenario.
  5. Dùng Scenario Outline + Examples khi muốn chạy cùng 1 kịch bản với nhiều bộ dữ liệu khác nhau, ví dụ thử nhiều cặp username/password sai khác nhau mà không cần viết lại nhiều Scenario:
Scenario Outline: Login fails with various invalid inputs
 Given I am on the login page
 When I log in with username "<username>" and password "<password>"
 Then I should see the error message "<message>"

Examples:
| username  | password  | message |
| wronguser | wrongpass | Invalid username or password |
|           | abcd123   | Username is required |
| admin     |           | Password is required |

Vì sao nên dùng Cucumber/Gherkin?

  • Ai cũng đọc hiểu được test case - kể cả BA, khách hàng không biết code.
  • Hỗ trợ cách làm BDD - Gherkin có thể được dùng để ghi lại những behavior/example mà BA, Dev và Tester đã cùng thống nhất. Cucumber sau đó có thể kết nối những kịch bản này với automation code.
  • Tái sử dụng step - một step như Given I am on the login page có thể dùng lại ở rất nhiều scenario khác nhau.
  • Tài liệu sống - file .feature vừa là test, vừa là tài liệu mô tả hệ thống hoạt động thế nào