← 中文资源

简体中文版本

使用 t.Run 的表驱动测试

用表驱动、子测试和并行执行覆盖邮箱规范化,并附带可执行示例。

阅读英文原文

功能说明

这段代码展示 Go 中常见的表驱动测试写法:把输入、期望输出和是否应报错放进结构体切片,用 t.Run 为每个用例创建独立的子测试。这样失败时能直接看到用例名,而不是只有一个笼统的测试失败。

子测试中调用 t.Parallel() 可以并行执行互不依赖的用例,缩短测试时间。文件还包含一个 ExampleNormalizeEmail,go test 会捕获标准输出并与 // Output: 注释比对,相当于一份可执行的文档。

关键代码

normalizeEmail 先 TrimSpace 和 ToLower,再用 strings.IndexByte 找到 @,并检查它不在开头、不在结尾且只出现一次。校验失败返回带原始输入的错误。

测试用匿名结构体定义 name、input、want、wantErr 四个字段。循环内先执行 testCase := testCase,避免旧版本 Go 的循环变量复用问题;然后在 t.Run 内再次 t.Parallel(),根据 wantErr 分支断言。

使用注意

并行子测试不能共享可变状态,也不能依赖执行顺序;如果测试需要修改全局变量或数据库,应去掉 t.Parallel()。t.Fatalf 只能在测试 goroutine 中调用,不能在业务 goroutine 里调用。

示例函数必须与测试位于同一个包,且输出注释格式完全一致,否则 go test 会失败。表驱动的用例名应描述行为而不是输入值,方便失败时定位。这个测试只验证 @ 的基本结构,不验证完整 RFC 5322 语法。

验证方式

把代码保存为 email_test.go,运行 go test -run TestNormalizeEmail -v,四个子测试应全部通过,示例输出检查也通过。把 lowercases and trims 的期望值改成错误值,应看到该子测试失败且其他子测试继续执行。运行 go test -race 可确认并行用例没有数据竞争。

代码示例

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]
}