name: cucumber-best-practices user-invocable: false description: Cucumber最佳实践、模式和反模式
Cucumber最佳实践
掌握有效的Cucumber测试模式和最佳实践。
场景设计原则
1. 编写声明式场景
关注什么需要发生,而不是如何发生。
❌ 命令式(实现驱动):
场景:添加产品到购物车
假设我导航到"http://shop.com/products"
当我在CSS中找到元素".product[data-id='123']"
并且我点击类为"add-to-cart"的按钮
并且我等待AJAX请求完成
那么元素".cart-count"应该包含"1"
✅ 声明式(业务驱动):
场景:添加产品到购物车
假设我正在浏览产品
当我将"无线耳机"添加到购物车时
那么我的购物车应该包含1个商品
2. 一个场景,一个行为
每个场景应只测试一个业务规则或行为。
❌ 一个场景中包含多个行为:
场景:用户注册、登录和资料更新
假设我注册一个新账户
当我登录时
并且我更新我的资料
并且我更改我的密码
那么一切都应该正常工作
✅ 分开的场景:
场景:注册新账户
当我使用有效信息注册时
那么我应该收到确认邮件
场景:使用新账户登录
假设我已经注册了一个账户
当我使用我的凭证登录时
那么我应该看到我的仪表板
场景:更新资料
假设我已登录
当我更新我的资料信息时
那么我的更改应该被保存
3. 保持场景独立
每个场景应设置自己的先决条件。
❌ 依赖的场景:
场景:创建订单
当我创建订单#12345时
场景:查看订单
当我查看订单#12345时 # 依赖上一个场景!
✅ 独立的场景:
场景:查看订单
假设一个ID为"12345"的订单存在
当我查看订单详情时
那么我应该看到订单信息
4. 明智地使用背景
使用背景用于通用设置,但不要过度使用。
✅ 背景的良好使用:
功能:购物车
背景:
假设我以顾客身份登录
场景:添加产品到购物车
当我添加一个产品到购物车时
那么我的购物车应该包含1个商品
场景:从购物车移除产品
假设我的购物车中有一个产品
当我移除该产品时
那么我的购物车应该为空
❌ 背景做太多事情:
背景:
假设我在主页上
并且我点击菜单
并且我导航到产品
并且我按类别"电子产品"筛选
并且我按价格排序
# 设置过多!不是所有场景都需要所有这些
功能组织
分组相关场景
功能:用户认证
场景:成功登录
...
场景:密码错误登录失败
...
场景:多次失败后账户锁定
...
有效使用标签
@冒烟 @关键
场景:使用有效凭证登录
...
@慢速 @集成
场景:密码重置邮件工作流
...
@进行中
场景:OAuth登录
# 进行中的工作
...
运行特定标签:
# 运行冒烟测试
cucumber --tags "@冒烟"
# 运行除进行中外所有
cucumber --tags "not @进行中"
# 运行冒烟和关键
cucumber --tags "@冒烟 and @关键"
# 运行冒烟或关键
cucumber --tags "@冒烟 or @关键"
编写良好的Gherkin
使用领域语言
用业务领域的语言编写,而不是技术术语。
❌ 技术语言:
场景:POST请求到/api/users
当我向"/api/users"发送JSON负载的POST请求时
并且响应状态为201
✅ 领域语言:
场景:注册新用户
当我注册一个新用户账户时
那么用户应该被成功创建
保持步骤在同一层次
不要混合高层次和低层次细节。
❌ 混合层次:
场景:购买产品
假设我已登录
当我添加一个产品到购物车时
并且我点击ID为"checkout-btn"的元素 # 太详细!
并且我输入信用卡"4111111111111111" # 太详细!
那么我完成购买
✅ 一致层次:
场景:购买产品
假设我已登录
并且我的购物车中有一个产品
当我使用信用卡结账时
那么我的订单应该完成
并且我应该收到确认邮件
避免连接步骤
不要用"并且"在散文体中组合多个不同的操作。
❌ 连接步骤:
当我登录并添加产品到购物车并结账时
✅ 分开步骤:
当我登录时
并且我添加一个产品到我的购物车
并且我继续结账
场景大纲
用于真正的变化
当需要用不同数据测试相同行为时使用场景大纲。
✅ 良好使用:
场景大纲:登录验证
当我使用"<用户名>"和"<密码>"登录时
那么我应该看到"<消息>"
示例:
| 用户名 | 密码 | 消息 |
| 有效 | 有效 | 欢迎 |
| 无效 | 有效 | 无效用户名 |
| 有效 | 无效 | 无效密码 |
| 空 | 空 | 用户名必填 |
❌ 过度使用场景大纲:
# 不要将场景大纲用于不相关的测试用例
场景大纲:多个功能
当我使用功能"<功能>"时
那么结果是"<结果>"
示例:
| 功能 | 结果 |
| 登录 | 成功 |
| 注册 | 成功 |
| 购物车 | 空 | # 这些是不同的行为!
保持示例有意义
场景大纲:折扣计算
假设一个状态为"<会员级别>"的顾客
当他们购买总额为$<金额>的商品时
那么他们应该收到$<折扣>的折扣
示例:标准折扣
| 会员级别 | 金额 | 折扣 |
| 银牌 | 100 | 5 |
| 金牌 | 100 | 10 |
| 白金 | 100 | 15 |
示例:最低购买阈值
| 会员级别 | 金额 | 折扣 |
| 银牌 | 49 | 0 |
| 银牌 | 50 | 2.50 |
步骤定义模式
创建可重用步骤
// 通用的,可重用的
当('我填写{string}为{string}', async function(字段, 值) {
await this.page.fill(`[name="${字段}"]`, 值);
});
// 在多个场景中使用:
当('我填写"邮箱"为"test@example.com"')
当('我填写"密码"为"secure123"')
当('我填写"搜索"为"产品"')
避免过度通用步骤
在可重用性和可读性之间取得平衡。
❌ 太通用:
当('我用{string}和{string}做{string}', ...)
✅ 具体且可读:
当('我用{string}和{string}登录', ...)
当('我在{string}中搜索{string}', ...)
数据管理
使用工厂创建测试数据
// support/factories.js
const faker = require('faker');
class UserFactory {
static create(overrides = {}) {
return {
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
email: faker.internet.email(),
password: 'Test123!',
...overrides
};
}
}
// 在步骤中使用
假设('我注册一个新用户', async function() {
const user = UserFactory.create();
this.currentUser = user;
await this.api.register(user);
});
避免硬编码ID
❌ 硬编码:
假设用户"12345"存在
当我查看订单"67890"时
✅ 命名实体:
假设一个用户"john@example.com"存在
当我查看我最近的订单时
错误处理
测试正常路径和异常路径
@正常路径
场景:成功结账
假设我的购物车中有商品
当我完成结账流程时
那么我的订单应该被确认
@错误处理
场景:使用过期卡结账
假设我的购物车中有商品
当我使用过期信用卡结账时
那么我应该看到一个错误消息
并且我的订单不应该被处理
@边缘情况
场景:库存不足结账
假设我的购物车中有一个产品
但该产品缺货
当我尝试结账时
那么我应该被告知库存不可用
性能
标记慢速测试
@慢速 @集成
场景:带邮件通知的完整订单工作流
# 运行需要30秒
...
并行执行
确保场景可以并行运行:
// cucumber.js
module.exports = {
default: '--parallel 4'
};
维护
定期审查
- 移除过时场景
- 更新场景以符合需求变化
- 重构重复步骤
- 保持功能组织
版本控制
features/
authentication/
login.feature
registration.feature
shopping/
cart.feature
checkout.feature
admin/
user-management.feature
常见反模式
❌ 测试实现细节:
那么数据库中应该有1条用户在用户表中的记录
❌ 业务场景中的UI特定断言:
那么我应该在右上角看到一个红色错误消息
❌ 使用Given用于动作:
假设我点击提交按钮 # 这是一个When,不是Given!
❌ 技术术语:
当我向/api/v1/users发送JSON体的POST请求时
测试金字塔
在测试策略中适当使用Cucumber:
- 端到端Cucumber测试:关键用户旅程(20%)
- 集成测试:API/服务交互(30%)
- 单元测试:业务逻辑(50%)
不要试图用Cucumber测试所有东西。用于高价值的验收测试。
记住:Cucumber测试应记录行为,促进协作,并从业务角度提供系统按预期工作的信心。