BeforeAll vs f.BeforeEach() 设计与使用场景
设计场景对比
1. Ginkgo 的 BeforeAll vs BeforeEach
// Ginkgo 生命周期钩子
ginkgo.BeforeAll(func() {
// 在整个 Describe/Context 块的所有测试开始前执行一次
// 用于:创建共享的昂贵资源(docker 网络、数据库等)
})
ginkgo.BeforeEach(func() {
// 在每个 It 测试用例前执行
// 用于:为每个测试准备隔离的环境(创建 namespace、pod 等)
})
2. Framework 的 f.BeforeEach() 方法
这是 kube-ovn e2e framework 的初始化方法,负责:
- 惰性创建各种 Kubernetes 客户端(
f.KubeOVNClientSet、f.AttachNetClient等) - 使用
if client == nil检查,确保客户端只初始化一次 - 虽然命名为
BeforeEach(),但可以在任何地方手动调用
执行顺序
典型场景(如 subnet 测试):
var _ = framework.Describe("[group:subnet]", func() {
f := framework.NewDefaultFramework("subnet")
// ❌ 此时 f.ClientSet 等客户端都是 nil
ginkgo.BeforeEach(func() {
// ✅ Ginkgo 自动调用每个测试前
// 通常在这里访问 f.ClientSet(会触发 framework 自动初始化)
cs = f.ClientSet // framework.Framework 内部懒加载
podClient = f.PodClient()
})
ginkgo.It("should do something", func() {
// 测试代码
})
})
你们当前的场景(使用 BeforeAll + 显式调用):
var _ = framework.OrderedDescribe("[group:iptables-vpc-nat-gw]", func() {
f := framework.NewDefaultFramework("iptables-vpc-nat-gw")
ginkgo.BeforeAll(func() {
// ⚠<fe0f> 问题:BeforeAll 在所有测试前执行
// 但 framework 客户端此时还未初始化
// ✅ 解决方案:手动调用 f.BeforeEach() 初始化客户端
f.BeforeEach()
// ✅ 现在可以安全访问客户端
cs = f.ClientSet
attachNetClient = f.NetworkAttachmentDefinitionClientNS(...)
})
})
执行时序图
测试启动
↓
[OrderedDescribe 块开始]
↓
ginkgo.BeforeAll() ← 只执行 1 次
├─ f.BeforeEach() ← 手动调用,初始化所有客户端
├─ 创建 docker network(共享资源)
└─ 连接节点到网络
↓
[第 1 个 It 测试]
↓
[第 2 个 It 测试]
↓
...
↓
ginkgo.AfterAll() ← 只执行 1 次
└─ 清理共享资源
为什么需要在 BeforeAll 中调用 f.BeforeEach()?
框架设计的默认假设:
- Framework 期望在
ginkgo.BeforeEach钩子中首次访问客户端 - Kubernetes e2e framework 会在首次访问
f.ClientSet时自动初始化
你们的特殊需求:
- 使用
ginkgo.Ordered+BeforeAll共享 docker 网络等昂贵资源 - 需要在
BeforeAll中访问客户端(比原设计更早) - 因此必须手动调用
f.BeforeEach()触发初始化
最佳实践建议
ginkgo.BeforeAll(func() {
// 1<fe0f><20e3> 首先初始化 framework
f.BeforeEach()
// 2<fe0f><20e3> 然后初始化测试客户端
cs = f.ClientSet
attachNetClient = f.NetworkAttachmentDefinitionClientNS(...)
// 3<fe0f><20e3> 最后创建共享资源
network, err := docker.NetworkCreate(...)
ginkgo.DeferCleanup(func() {
docker.NetworkDelete(network.ID)
})
})
关键要点
| 钩子 | 执行时机 | 用途 | 是否需要手动调用 |
|---|---|---|---|
ginkgo.BeforeAll | 所有测试前 1 次 | 创建共享资源 | ❌ 由 Ginkgo 管理 |
ginkgo.BeforeEach | 每个测试前 | 隔离环境准备 | ❌ 由 Ginkgo 管理 |
f.BeforeEach() | 手动/自动 | 初始化 framework 客户端 | ⚠ 在 BeforeAll 中需要手动调用 |
ginkgo.AfterAll | 所有测试后 1 次 | 清理共享资源 | ❌ 由 Ginkgo 管理 |
这样设计确保了客户端在使用前已正确初始化,避免了空指针错误 ✓ EOF
BeforeAll vs f.BeforeEach() 设计与使用场景
设计场景对比
1. Ginkgo 的 BeforeAll vs BeforeEach
// Ginkgo 生命周期钩子
ginkgo.BeforeAll(func() {
// 在整个 Describe/Context 块的所有测试开始前执行一次
// 用于:创建共享的昂贵资源(docker 网络、数据库等)
})
ginkgo.BeforeEach(func() {
// 在每个 It 测试用例前执行
// 用于:为每个测试准备隔离的环境(创建 namespace、pod 等)
})
2. Framework 的 f.BeforeEach() 方法
这是 kube-ovn e2e framework 的初始化方法,负责:
- 惰性创建各种 Kubernetes 客户端(
f.KubeOVNClientSet、f.AttachNetClient等) - 使用
if client == nil检查,确保客户端只初始化一次 - 虽然命名为
BeforeEach(),但可以在任何地方手动调用
执行顺序
典型场景(如 subnet 测试):
var _ = framework.Describe("[group:subnet]", func() {
f := framework.NewDefaultFramework("subnet")
// ❌ 此时 f.ClientSet 等客户端都是 nil
ginkgo.BeforeEach(func() {
// ✅ Ginkgo 自动调用每个测试前
// 通常在这里访问 f.ClientSet(会触发 framework 自动初始化)
cs = f.ClientSet // framework.Framework 内部懒加载
podClient = f.PodClient()
})
ginkgo.It("should do something", func() {
// 测试代码
})
})
你们当前的场景(使用 BeforeAll + 显式调用):
var _ = framework.OrderedDescribe("[group:iptables-vpc-nat-gw]", func() {
f := framework.NewDefaultFramework("iptables-vpc-nat-gw")
ginkgo.BeforeAll(func() {
// ⚠️ 问题:BeforeAll 在所有测试前执行
// 但 framework 客户端此时还未初始化
// ✅ 解决方案:手动调用 f.BeforeEach() 初始化客户端
f.BeforeEach()
// ✅ 现在可以安全访问客户端
cs = f.ClientSet
attachNetClient = f.NetworkAttachmentDefinitionClientNS(...)
})
})
执行时序图
测试启动
↓
[OrderedDescribe 块开始]
↓
ginkgo.BeforeAll() ← 只执行 1 次
├─ f.BeforeEach() ← 手动调用,初始化所有客户端
├─ 创建 docker network(共享资源)
└─ 连接节点到网络
↓
[第 1 个 It 测试]
↓
[第 2 个 It 测试]
↓
...
↓
ginkgo.AfterAll() ← 只执行 1 次
└─ 清理共享资源
为什么需要在 BeforeAll 中调用 f.BeforeEach()?
框架设计的默认假设:
- Framework 期望在
ginkgo.BeforeEach钩子中首次访问客户端 - Kubernetes e2e framework 会在首次访问
f.ClientSet时自动初始化
你们的特殊需求:
- 使用
ginkgo.Ordered+BeforeAll共享 docker 网络等昂贵资源 - 需要在
BeforeAll中访问客户端(比原设计更早) - 因此必须手动调用
f.BeforeEach()触发初始化
最佳实践建议
ginkgo.BeforeAll(func() {
// 1️⃣ 首先初始化 framework
f.BeforeEach()
// 2️⃣ 然后初始化测试客户端
cs = f.ClientSet
attachNetClient = f.NetworkAttachmentDefinitionClientNS(...)
// 3️⃣ 最后创建共享资源
network, err := docker.NetworkCreate(...)
ginkgo.DeferCleanup(func() {
docker.NetworkDelete(network.ID)
})
})
关键要点
| 钩子 | 执行时机 | 用途 | 是否需要手动调用 |
|---|---|---|---|
ginkgo.BeforeAll | 所有测试前 1 次 | 创建共享资源 | ❌ 由 Ginkgo 管理 |
ginkgo.BeforeEach | 每个测试前 | 隔离环境准备 | ❌ 由 Ginkgo 管理 |
f.BeforeEach() | 手动/自动 | 初始化 framework 客户端 | ⚠️ 在 BeforeAll 中需要手动调用 |
ginkgo.AfterAll | 所有测试后 1 次 | 清理共享资源 | ❌ 由 Ginkgo 管理 |
这样设计确保了客户端在使用前已正确初始化,避免了空指针错误 ✓