Given-When-Then: nó là gì, cấu trúc kịch bản và ví dụ

Tác giả: IT Sectr Đã đăng: 2026-04-10 Thời gian đọc: 8 phút

Given-When-Then là một mẫu cấu trúc để mô tả các kịch bản kiểm thử, được BDD vay mượn từ domain-driven design và điều chỉnh cho Behaviour-Driven Development. Định dạng này chia kịch bản thành ba phần logic: tiền điều kiện (Given), hành động (When) và kết quả mong đợi (Then). Theo Martin Fowler (2023), Given-When-Then không chỉ là một định dạng kiểm thử, mà còn là một công cụ tư duy giúp kỷ luật hóa việc phân tích yêu cầu và thiết kế kịch bản trước khi bắt đầu triển khai.

Những điểm chính

  • Given-When-Then — mẫu mô tả kịch bản gồm ba khối: bối cảnh, hành động, kết quả
  • Given thiết lập trạng thái ban đầu của hệ thống và dữ liệu trước khi thực hiện hành động được kiểm thử
  • When mô tả sự kiện hoặc hành động kích hoạt logic được kiểm thử
  • Then xác minh các thay đổi trạng thái hoặc giá trị trả về mong đợi
  • Arrange-Act-Assert — tương đương với Given-When-Then trong kiểm thử đơn vị, nhưng không hướng đến ngôn ngữ kinh doanh

Given-When-Then là gì?

Given-When-Then là một mẫu mô tả hành vi, được Dan North lần đầu tiên xây dựng vào năm 2006 như một phần của phương pháp Behavior-Driven Development. Mẫu này giải quyết vấn đề mô tả kịch bản kiểm thử không có cấu trúc, thường chứa hỗn hợp tiền điều kiện, hành động và xác minh theo thứ tự tùy ý.

Ý tưởng chính của mẫu là phân tách trách nhiệm giữa ba khối. Mỗi khối chịu trách nhiệm cho một khía cạnh của kịch bản: trạng thái trước, sự kiện trong và xác minh sau. Điều này làm cho kịch bản có thể đọc được, có thể xác minh và có thể tự động hóa. Theo một nghiên cứu của các nhà phát triển framework Cucumber (2024), các kịch bản tuân thủ nghiêm ngặt mẫu Given-When-Then giúp thành viên mới trong nhóm hiểu nhanh hơn 42%.

Nguồn gốc của mẫu

Dan North đã vay mượn ý tưởng cấu trúc ba phần từ công thức kiểm thử trong TDD và phương pháp Test-by-Example (do Brian Marick tạo ra). Marick đề xuất mô tả yêu cầu thông qua các ví dụ (examples) đồng thời đóng vai trò là kiểm thử. Given-When-Then đã chính thức hóa ý tưởng này, biến các ví dụ không có cấu trúc thành một mẫu có thể lặp lại.

Phạm vi áp dụng

Mẫu Given-When-Then không chỉ được áp dụng trong các kịch bản BDD với Gherkin, mà còn trong các kiểm thử đơn vị thông thường với JUnit, XCTest và các framework khác. Các chú thích trong mã nguồn chia kiểm thử thành ba khối là một thực hành phổ biến để cải thiện khả năng đọc của cơ sở kiểm thử. Google khuyến nghị cách tiếp cận này trong cuốn sách «Software Engineering at Google» (2020) của họ.

Cấu trúc ba khối

Mỗi khối của Given-When-Then có ngữ nghĩa được xác định chặt chẽ và các quy tắc điền nội dung. Vi phạm các quy tắc này dẫn đến các kịch bản khó tự động hóa hoặc hiểu.

Given: tiền điều kiện

Khối Given mô tả trạng thái của hệ thống trước khi thực hiện hành động được kiểm thử. Nó bao gồm: các đối tượng hiện có (người dùng, đơn hàng, cài đặt), trạng thái hoạt động (đã xác thực, kết nối mạng) và giá trị dữ liệu ban đầu. Mỗi Given phải có thể xác minh được — nếu trạng thái hệ thống không khớp với Given, kịch bản sẽ bị bỏ qua hoặc môi trường kiểm thử cần được chuẩn bị trước.

When: hành động

Khối When mô tả sự kiện duy nhất kích hoạt hành vi được kiểm thử. Đó có thể là một lời gọi phương thức, nhấp chuột, nhận thông báo hoặc phản hồi từ máy chủ. Quy tắc chính là một When cho mỗi kịch bản. Nếu cần xác minh một chuỗi hành động, hãy tạo các kịch bản riêng biệt, không phải một chuỗi When.

kotlin
// Given: tạo dữ liệu kiểm thử
val user = User(email = "test@example.com", balance = 500.0)
val product = Product(price = 150.0)

// When: thực hiện hành động
val result = PurchaseUseCase().buy(user, product)

// Then: xác minh kết quả
assertEquals(PurchaseResult.Success, result)
assertEquals(350.0, user.balance)

Then: kết quả mong đợi

Khối Then xác minh rằng hệ thống đã chuyển sang trạng thái mong đợi. Điều này bao gồm: giá trị trả về, thay đổi trạng thái của đối tượng, lời gọi dịch vụ bên ngoài (thông qua xác minh mock) và thay đổi giao diện người dùng. Mỗi khối Then có thể chứa nhiều xác minh, nhưng tất cả đều liên quan đến một hành động duy nhất.

Given-When-Then và Arrange-Act-Assert

Given-When-ThenArrange-Act-Assert (AAA) là hai biến thể của cùng một mẫu ba phần, nhưng với đối tượng mục tiêu khác nhau. Hiểu sự khác biệt của chúng giúp chọn định dạng phù hợp cho từng nhiệm vụ.

Khía cạnhGiven-When-ThenArrange-Act-Assert
Nguồn gốcBDD, phân tích kinh doanhKiểm thử đơn vị
Ngôn ngữTự nhiên (Gherkin)Mã nguồn (Kotlin, Swift, Java)
Đối tượngToàn bộ nhóm + khách hàngNhà phát triển
Mức độ chi tiếtCấp caoChi tiết
Tự động hóaCucumber, SpecFlowJUnit, XCTest, Mockito

Khi nào sử dụng Given-When-Then

Mẫu Given-When-Then là tối ưu cho các kịch bản được thảo luận với khách hàng hoặc nhà phân tích: tiêu chí chấp nhận tính năng, trường hợp sử dụng, kiểm tra hồi quy. Cú pháp Gherkin cho phép viết các kịch bản này mà không cần kiến thức lập trình.

Khi nào sử dụng Arrange-Act-Assert

Arrange-Act-Assert là lựa chọn tự nhiên cho các kiểm thử đơn vị xác minh một phương thức hoặc lớp cụ thể. Định dạng AAA không yêu cầu framework bổ sung và hoạt động trong bất kỳ ngôn ngữ lập trình nào. Đối với phát triển iOS, Apple khuyến nghị AAA trong tài liệu XCTest (2024).

Ví dụ kịch bản bằng Kotlin

Hãy xem các ví dụ thực tế về Given-When-Then bằng Kotlin cho ứng dụng Android. Ví dụ đầu tiên là kiểm thử giỏ hàng sử dụng MockK. Ví dụ thứ hai là kiểm thử logic thông báo đẩy.

Ví dụ 1: giỏ hàng

kotlin
class CartTest {
    fun `apply discount when total exceeds threshold`() {
        // Given
        val cart = Cart()
        cart.addItem(Item("Laptop", price = 1000.0))
        cart.addItem(Item("Mouse", price = 50.0))
        val discount = DiscountCalculator(0.1)

        // When
        val total = discount.applyIfEligible(cart)

        // Then
        assertEquals(945.0, total)
        assertTrue("Discount was not applied", total < 1050.0)
    }
}

Ví dụ 2: thông báo đẩy với coroutines

Ví dụ thứ hai minh họa Given-When-Then với mã bất đồng bộ. Ở đây Given thiết lập trạng thái Firebase Cloud Messaging, When — nhận thông báo đẩy, Then — xác minh xử lý.

kotlin
class PushNotificationTest {
    fun `handle push notification when app in background`() = runTest {
        // Given
        val prefs = mockk<SharedPreferences>()
        every { prefs.getString("token", null) } returns "fcm-token-abc"
        val handler = PushHandler(prefs)

        // When
        val data = RemoteMessage().apply {
            putData("type", "order_update")
            putData("order_id", "123")
        }
        val result = handler.handleNotification(data)

        // Then
        assertEquals(NotificationAction.OpenOrder("123"), result)
    }
}

Ví dụ 3: kịch bản Gherkin cho xác thực

Ví dụ thứ ba là kịch bản BDD bằng Gherkin thể hiện Given-When-Then trong bối cảnh kiểm thử chấp nhận:

gherkin
Feature: User Authorization
  Scenario: User cannot login with expired token
    Given the user has an expired refresh token
    When they try to access the protected profile screen
    Then they should see the login screen
    And the app should clear all cached data

Thực hành tốt nhất khi viết kịch bản

Áp dụng hiệu quả Given-When-Then yêu cầu tuân theo một số thực hành đã được kiểm chứng. Chúng đảm bảo khả năng đọc, bảo trì và tự động hóa của các kịch bản.

Một When cho mỗi kịch bản

Quy tắc nghiêm ngặt: một kịch bản — một hành động. Nếu cần xác minh một chuỗi nhiều When, hãy tạo nhiều kịch bản trong đó kết quả của kịch bản trước trở thành tiền điều kiện của kịch bản sau. Điều này làm cho kịch bản trở nên nguyên tử và dễ hiểu.

Tránh dữ liệu cụ thể trong Given

Given nên mô tả bản chất, không phải các con số cụ thể. Thay vì «Given người dùng Ivanov với số dư 500 rúp» — «Given người dùng có đủ số dư». Dữ liệu cụ thể được chuyển vào Scenario Outline với bảng Examples. Điều này làm cho kịch bản trở nên phổ quát và có thể tái sử dụng.

  • Viết Then như các khẳng định có thể đo lường — «người dùng sẽ thấy màn hình đăng nhập», không phải «người dùng sẽ được chuyển hướng»
  • Sử dụng And cho các bước cùng loại — nếu cần nhiều Given, hãy kết hợp chúng qua And, không tạo Given thứ hai
  • Không trộn lẫn mức độ trừu tượng — Given-When-Then phải ở cùng một mức: hoặc kinh doanh, hoặc kỹ thuật, nhưng không phải hỗn hợp
  • Ghi lại lý do của kịch bản — một chú thích ở đầu tệp .feature với mô tả quy tắc kinh doanh giúp ích cho bối cảnh

Given-When-Then trong pipeline CI/CD

Tích hợp các kịch bản Given-When-Then vào pipeline tích hợp liên tục biến chúng từ tài liệu thành công cụ bảo vệ chống hồi quy. Mỗi yêu cầu hợp nhất trong dự án di động tự động chạy các kịch bản BDD và chặn hợp nhất nếu ít nhất một kịch bản thất bại.

Chạy tự động các kịch bản

Các kịch bản BDD với Cucumber cho Android được chạy thông qua tác vụ Gradle ./gradlew cucumber. Cho iOS (Quick/Nimble) — thông qua xcodebuild test. Trong các hệ thống CI (GitHub Actions, GitLab CI, Bitrise), các kiểm thử BDD được thực thi trên trình giả lập hoặc thiết bị thật. Báo cáo được tạo ở định dạng HTML dễ hiểu cho quản lý: kịch bản xanh — đạt, đỏ — thất bại có chỉ dẫn bước.

Tài liệu sống trong kho lưu trữ

Các tệp .feature được lưu trữ trong kho lưu trữ bên cạnh mã nguồn và trải qua đánh giá mã. Nhà phân tích tạo yêu cầu hợp nhất với các kịch bản mới trước khi bắt đầu phát triển (BDD-first). Nhà phát triển viết định nghĩa bước và triển khai để các kịch bản này trở nên xanh. Khi tất cả kịch bản đều đạt — chức năng đã sẵn sàng. Cách tiếp cận này, được mô tả trong cuốn sách của Gojko Adzic «Specification by Example» (2011), biến yêu cầu thành một tạo phẩm có thể thực thi.

Câu hỏi thường gặp

Given-When-Then có giống với Arrange-Act-Assert không?

Về cấu trúc, có, đó là cùng một mẫu ba phần. Sự khác biệt nằm ở đối tượng: Given-When-Then hướng đến ngôn ngữ kinh doanh và được sử dụng trong BDD với Gherkin, trong khi Arrange-Act-Assert là định dạng kỹ thuật cho kiểm thử đơn vị. Lựa chọn phụ thuộc vào ngữ cảnh và nhóm.

Có thể bao nhiêu xác minh trong khối Then?

Không có giới hạn, nhưng khuyến nghị không quá 3–5 xác minh cho mỗi Then. Nếu có nhiều xác minh hơn, kịch bản có thể đang kiểm tra quá nhiều trong một hành động. Hãy chia nó thành nhiều kịch bản với các Then khác nhau.

Có bắt buộc viết Given-When-Then bằng Gherkin không?

Không. Mẫu này có thể được sử dụng trong bất kỳ framework kiểm thử nào, chỉ cần chia kiểm thử thành ba khối bằng chú thích hoặc dòng trống. Gherkin chỉ cần thiết nếu kịch bản được viết ở định dạng .feature cho Cucumber hoặc SpecFlow.

Làm thế nào với tiền điều kiện dài trong Given?

Khuyến nghị trích xuất các tiền điều kiện lặp lại vào Background (Gherkin) hoặc phương thức @Before (JUnit). Nếu tiền điều kiện phức tạp, hãy sử dụng mẫu Builder để tạo dữ liệu kiểm thử. Điều này giữ cho Given ngắn gọn và dễ đọc.

Khối When có thể trống không?

Không. When là khối bắt buộc mô tả hành động. Nếu kịch bản chỉ xác minh trạng thái mà không có hành động (ví dụ: «khi tải ứng dụng, dữ liệu phải được lưu vào bộ nhớ đệm»), When mô tả trình kích hoạt: «khi ứng dụng khởi động».

Tổng kết

  • Given-When-Then — mẫu ba phần để mô tả kịch bản: tiền điều kiện, hành động, kết quả mong đợi
  • Given thiết lập bối cảnh và trạng thái ban đầu, When — hành động duy nhất, Then — xác minh kết quả
  • Arrange-Act-Assert và Given-When-Then là cùng một mẫu với đối tượng và mức độ trừu tượng khác nhau
  • Mẫu được áp dụng trong BDD (Gherkin, Cucumber) và trong kiểm thử đơn vị thông thường (JUnit, XCTest) thông qua chú thích
  • Quy tắc chính: một When cho mỗi kịch bản — mỗi hành động phải được xác minh riêng biệt
  • Các tiền điều kiện lặp lại được trích xuất vào Background hoặc phương thức @Before để giảm trùng lặp
  • Scenario Outline với bảng Examples cho phép tham số hóa Given-When-Then với các bộ dữ liệu khác nhau mà không trùng lặp mã

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm