Given-When-Then 是一种描述测试场景的结构化模板,由 BDD 从 domain-driven design 中借鉴并适配到 Behaviour-Driven Development。该格式将场景分为三个逻辑部分:前置条件(Given)、操作(When)和预期结果(Then)。根据 Martin Fowler(2023)的说法,Given-When-Then 不仅仅是测试格式,更是一种思维工具,它规范了需求分析和场景设计,使其在实现开始之前就得到规范。
要点
Given-When-Then 是一种行为描述模板,由 Dan North 于 2006 年首次提出,作为 Behavior-Driven Development 方法论的一部分。该模板解决了非结构化测试场景描述的问题,这些描述通常包含前置条件、操作和检查的任意顺序混合。
该模板的核心思想是三个块之间的职责分离。每个块精确负责场景的一个方面:之前的状态、期间的事件和之后的检查。这使得场景可读、可验证和可自动化。根据 Cucumber 框架开发人员的研究(2024),严格遵循 Given-When-Then 模板的场景可减少新团队成员 42% 的理解时间。
Dan North 将三部分结构的理念借鉴自 TDD 中的测试公式化和示例驱动测试方法论(由 Brian Marick 创建)。Marick 提出通过示例(examples)描述需求,这些示例同时充当测试。Given-When-Then 将这一理念形式化,将非结构化的示例转化为可重复的模板。
Given-When-Then 模板不仅应用于 Gherkin 的 BDD 场景中,也应用于 JUnit、XCTest 和其他框架的常规单元测试中。代码中将测试分为三个块的注释是提高测试库可读性的常见做法。Google 在其著作《Software Engineering at Google》(2020)中推荐了这种方法。
每个 Given-When-Then 块都有严格定义的语义和填充规则。违反这些规则会导致场景难以自动化或理解。
Given 块描述在执行被测操作之前系统的状态。它包括:现有对象(用户、订单、设置)、活动状态(已授权、已连接到网络)以及数据的初始值。每个 Given 必须是可验证的——如果系统状态与 Given 不符,应跳过该场景或预先准备测试环境。
When 块描述触发被测行为的单一事件。这可以是方法调用、按钮点击、收到通知或来自服务器的响应。关键规则——每个场景一个 When。如果需要检查操作序列,应创建单独的场景,而不是 When 链。
// Given:创建测试数据
val user = User(email = "test@example.com", balance = 500.0)
val product = Product(price = 150.0)
// When:执行操作
val result = PurchaseUseCase().buy(user, product)
// Then:检查结果
assertEquals(PurchaseResult.Success, result)
assertEquals(350.0, user.balance)
Then 块检查系统是否已进入预期状态。这包括:返回值、对象状态变化、外部服务的调用(通过 mock 验证)以及 UI 变化。每个 Then 块可以包含多个检查,但所有检查都针对同一操作。
Given-When-Then 和 Arrange-Act-Assert(AAA)是同一三部分模板的两种变体,但目标受众不同。理解它们的区别有助于为特定任务选择正确的格式。
| 方面 | Given-When-Then | Arrange-Act-Assert |
|---|---|---|
| 起源 | BDD,业务分析 | 单元测试 |
| 语言 | 自然语言(Gherkin) | 代码(Kotlin, Swift, Java) |
| 受众 | 整个团队 + 客户 | 开发人员 |
| 详细程度 | 高层级 | 详细 |
| 自动化 | Cucumber, SpecFlow | JUnit, XCTest, Mockito |
Given-When-Then 模板最适合与客户或分析师讨论的场景:功能验收标准、使用场景、回归检查。Gherkin 语法允许在没有编程知识的情况下编写此类场景。
Arrange-Act-Assert 是检查特定方法或类的单元测试的自然选择。AAA 格式不需要额外的框架,并且可以在任何编程语言中使用。对于 iOS 开发,Apple 在其 XCTest 文档(2024)中推荐 AAA。
让我们看看 Kotlin 中针对 Android 应用程序的 Given-When-Then 实际示例。第一个示例——使用 MockK 测试购物车。第二个——测试推送通知逻辑。
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)
}
}
第二个示例演示了 Given-When-Then 与异步代码的结合。这里 Given 设置了 Firebase Cloud Messaging 的状态,When 是接收推送通知,Then 是检查处理结果。
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)
}
}
第三个示例是 Gherkin 中的 BDD 场景,展示了 Given-When-Then 在验收测试上下文中的应用:
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
有效使用 Given-When-Then 需要遵循几个经过验证的实践。它们确保场景的可读性、可维护性和可自动化性。
严格规则:一个场景——一个操作。如果需要检查多个 When 的序列,应创建多个场景,其中前一个的结果成为后一个的前置条件。这使场景具有原子性和可理解性。
Given 应描述本质,而非具体数字。避免使用 “Given 用户 Ivanov 有 500 卢布余额”,而应使用 “Given 用户有足够的余额”。具体数据移至带有 Examples 表格的 Scenario Outline 中。这使得场景通用且可复用。
将 Given-When-Then 场景集成到持续集成流水线中将它们从文档转变为防止回归的保护。移动项目中的每个合并请求都会自动运行 BDD 场景,并在至少一个场景失败时阻止合并。
Android 上 Cucumber 中的 BDD 场景通过 Gradle 任务 ./gradlew cucumber 运行。对于 iOS(Quick/Nimble)——通过 xcodebuild test。在 CI 系统(GitHub Actions, GitLab CI, Bitrise)中,BDD 测试在模拟器或真实设备上执行。报告以 HTML 格式生成,管理者可以理解:绿色场景——通过,红色——失败并指示步骤。
.feature 文件与代码一起存储在仓库中,并经过代码审查。分析人员在开发开始前创建带有新场景的合并请求(BDD-first)。开发人员编写步骤定义和实现以使这些场景变为绿色。当所有场景通过时——功能就完成了。Gojko Adzic 在《Specification by Example》(2011)中描述的这种方法将需求转化为可执行的工件。
常见问题
从结构上讲——是的,这是同一个三部分模板。区别在于受众:Given-When-Then 面向业务语言,用于带有 Gherkin 的 BDD,而 Arrange-Act-Assert 是用于单元测试的技术格式。选择取决于上下文和团队。
没有限制,但建议每个 Then 不超过 3-5 个检查。如果检查太多,场景可能在一个操作中测试了太多内容。应将其拆分为具有不同 Then 的多个场景。
不是。该模板可以在任何测试框架中使用,只需用注释或空行将测试分为三个块。仅当场景以 .feature 文件格式为 Cucumber 或 SpecFlow 编写时才需要 Gherkin。
建议将重复的前置条件移到 Background(Gherkin)或 @Before 方法(JUnit)中。如果前置条件复杂,使用 Builder 模式创建测试数据。这保持 Given 简短且可读。
不可以。When 是描述操作的必需块。如果场景仅检查无操作的状态(例如,“加载应用程序时数据应被缓存”),When 描述触发器:“当应用程序启动时”。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。