概念定义
规约会与实现漂移,因为没有什么强制两者一致。可执行规约把它们焊死:场景文本绑定自动化,规约无法静默变旧——它只会失败。
工作机制
工作坊挖掘具体示例——真实输入与期望输出——编码成场景;步骤定义把每个业务短语映射到生产同款 API 操作。套件进 CI 并发布成人人可读的活文档。
使用场景
误解代价高昂的业务面行为:计费规则、工作流迁移、监管计算。纯技术单元的读者就是作者本人,不值得套这层仪式。规约粒度对准业务事件而不是界面细节,才能活得比一次改版更久。
常见误区
事后补 Gherkin 是测试剧场:协作发生过就是发生过,工件伪造不了。语法很轻,好的规约来自与领域专家一起挖出的具体例子。参数化过度的步骤库产出没人读得懂的场景;绑定 UI 机制的规约每次改版即碎——要绑到稳定接口上的行为。
代码示例
package main
import (
"fmt"
"strings"
"testing"
)
func normalizeEmail(raw string) (string, error) {
email := strings.TrimSpace(strings.ToLower(raw))
at := strings.IndexByte(email, '@')
if at <= 0 || at == len(email)-1 || strings.Count(email, "@") != 1 {
return "", fmt.Errorf("invalid email %q", raw)
}
return email, nil
}
func TestNormalizeEmail(t *testing.T) {
t.Parallel()
cases := []struct {
name string
input string
want string
wantErr bool
}{
{"lowercases and trims", " [email protected] ", "[email protected]", false},
{"rejects missing at", "ada.example.com", "", true},
{"rejects empty local part", "@example.com", "", true},
{"rejects double at", "ada@@example.com", "", true},
}
for _, testCase := range cases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
got, err := normalizeEmail(testCase.input)
if testCase.wantErr {
if err == nil {
t.Fatalf("expected error, got %q", got)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != testCase.want {
t.Fatalf("got %q, want %q", got, testCase.want)
}
})
}
}
func ExampleNormalizeEmail() {
email, _ := normalizeEmail(" [email protected] ")
fmt.Println(email)
// Output: [email protected]
}