BDD, Gherkin và Cucumber trong Automation Testing

BDD (Behavior-Driven Development)
BDD là một phương pháp phát triển phần mềm mở rộng từ TDD (Test-Driven Development), tập trung vào hành vi của hệ thống từ góc nhìn của người dùng cuối. Thay vì viết test cases thuần kỹ thuật, BDD khuyến khích việc mô tả các tính năng bằng ngôn ngữ tự nhiên mà tất cả các bên liên quan đều có thể hiểu được.
Đặc điểm chính của BDD
BDD xoay quanh ba nguyên tắc cốt lõi:
- Tập trung vào business value mà tính năng mang lại
- Sử dụng ngôn ngữ chung giữa các bên liên quan (business, dev, QA)
- Viết test dưới dạng kịch bản hành vi người dùng
Ví dụ với Playwright TypeScript
<em>// Cách viết test truyền thống</em>
test('should login successfully', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#username', 'user@example.com');
await page.fill('#password', 'password123');
await page.click('#loginButton');
await expect(page).toHaveURL('https://example.com/dashboard');
});
<em>// Cách viết theo tư duy BDD</em>
test('User with valid credentials should access dashboard', async ({ page }) => {
<em>// Given: User is on login page</em>
await page.goto('https://example.com/login');
<em>// When: User enters valid credentials and submits</em>
await page.fill('#username', 'user@example.com');
await page.fill('#password', 'password123');
await page.click('#loginButton');
<em>// Then: User should be redirected to dashboard</em>
await expect(page).toHaveURL('https://example.com/dashboard');
});
Gherkin
Gherkin là ngôn ngữ đặc tả được thiết kế để mô tả hành vi phần mềm mà không cần đi sâu vào chi tiết implementation. Gherkin sử dụng cấu trúc Given-When-Then để tạo ra các kịch bản test dễ đọc và dễ hiểu.
Cú pháp cơ bản của Gherkin
Feature: Shopping Cart
As a customer
I want to add items to my cart
So that I can purchase multiple products at once
Scenario: Add single item to empty cart
Given I am on the product page
When I click "Add to Cart" button
Then the cart should contain 1 item
And the cart total should be updated
Scenario: Add multiple quantities of same item
Given I am on the product page
When I select quantity as "3"
And I click "Add to Cart" button
Then the cart should contain 3 items
And the cart should show "3 x Product Name"
Các từ khóa chính trong Gherkin
- Feature: Mô tả tính năng cần test
- Scenario: Một trường hợp cụ thể của tính năng
- Given: Điều kiện ban đầu
- When: Hành động được thực hiện
- Then: Kết quả mong đợi
- And/But: Kết nối các bước
Scenario Outline cho test với nhiều data
Feature: Login functionality
Scenario Outline: Login with different user roles
Given I am on the login page
When I login with username "<username>" and password "<password>"
Then I should see the "<dashboard>" dashboard
Examples:
| username | password | dashboard |
| admin@co.com | admin123 | Admin |
| user@co.com | user123 | User |
| mod@co.com | mod123 | Moderator |
Cucumber
Cucumber là một framework cho phép chạy các test được viết bằng Gherkin. Nó đóng vai trò như cầu nối giữa các file đặc tả Gherkin và code automation thực tế.
Tích hợp Cucumber với Playwright TypeScript
Đầu tiên, cài đặt các dependencies cần thiết:
npm install --save-dev @cucumber/cucumber playwright @playwright/test
Cấu trúc project với Cucumber và Playwright
project/
├── features/
│ ├── login.feature
│ └── shopping-cart.feature
├── step-definitions/
│ ├── login.steps.ts
│ └── shopping-cart.steps.ts
├── support/
│ ├── hooks.ts
│ └── world.ts
└── cucumber.json
Ví dụ implementation đầy đủ
File: features/login.feature
Feature: User Authentication
Scenario: Successful login with valid credentials
Given I navigate to the login page
When I enter "user@example.com" in the email field
And I enter "password123" in the password field
And I click the login button
Then I should be redirected to the dashboard
And I should see "Welcome back" message
File: step-definitions/login.steps.ts
import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import { ICustomWorld } from '../support/world';
Given('I navigate to the login page', async function(this: ICustomWorld) {
const page = this.page!;
await page.goto('https://example.com/login');
});
When('I enter {string} in the email field', async function(this: ICustomWorld, email: string) {
const page = this.page!;
await page.fill('[data-testid="email-input"]', email);
});
When('I enter {string} in the password field', async function(this: ICustomWorld, password: string) {
const page = this.page!;
await page.fill('[data-testid="password-input"]', password);
});
When('I click the login button', async function(this: ICustomWorld) {
const page = this.page!;
await page.click('[data-testid="login-button"]');
});
Then('I should be redirected to the dashboard', async function(this: ICustomWorld) {
const page = this.page!;
await expect(page).toHaveURL(/.*dashboard/);
});
Then('I should see {string} message', async function(this: ICustomWorld, message: string) {
const page = this.page!;
await expect(page.locator('text=' + message)).toBeVisible();
});
File: support/world.ts
import { setWorldConstructor, World } from '@cucumber/cucumber';
import { Browser, BrowserContext, Page, chromium } from 'playwright';
export interface ICustomWorld extends World {
browser?: Browser;
context?: BrowserContext;
page?: Page;
}
export class CustomWorld extends World implements ICustomWorld {
browser?: Browser;
context?: BrowserContext;
page?: Page;
}
setWorldConstructor(CustomWorld);
File: support/hooks.ts
import { Before, After, BeforeAll, AfterAll } from '@cucumber/cucumber';
import { chromium, Browser } from 'playwright';
import { ICustomWorld } from './world';
let browser: Browser;
BeforeAll(async function() {
browser = await chromium.launch({ headless: false });
});
Before(async function(this: ICustomWorld) {
this.context = await browser.newContext();
this.page = await this.context.newPage();
});
After(async function(this: ICustomWorld) {
await this.page?.close();
await this.context?.close();
});
AfterAll(async function() {
await browser.close();
});
Configuration file
File: cucumber.json
{
"default": {
"require": [
"step-definitions/**/*.ts",
"support/**/*.ts"
],
"requireModule": [
"ts-node/register"
],
"format": [
"progress",
"html:reports/cucumber-report.html"
],
"parallel": 2
}
}
Chạy Cucumber tests
<em># Chạy tất cả features</em>
npx cucumber-js
<em># Chạy specific feature</em>
npx cucumber-js features/login.feature
<em># Chạy với tag</em>
npx cucumber-js --tags "@smoke"
Tìm hiểu thêm
Tài liệu chính thức
Các công cụ và thư viện hữu ích
- playwright-bdd – Integration giữa Playwright và Cucumber
- Cucumber VSCode Extension – Hỗ trợ syntax highlighting và autocomplete cho Gherkin
- Allure Report – Tool tạo report đẹp cho BDD tests
