如何快速对CRD中自定义CEL验证逻辑执行单元测试
问题:如何高效测试CRD中的CEL验证规则(无需EnvTest的单元测试方案)
我维护着若干基于Kubebuilder与Operator Framework的自定义Operator,随着CRD验证规则进入Beta阶段,希望将代码中部分复杂验证规则直接迁移至CRD中。
我已在api目录的类型文件中使用CEL实现了部分复杂验证规则且运行良好,但因部分规则较复杂、团队刚接触CEL,需对这些规则进行单元测试。目前仅找到使用EnvTest的方案,但其初始化设置耗时约5秒,不符合单元测试的效率要求。
我想咨询是否存在无需大幅增加测试耗时的真正单元测试方案。
现有代码示例
带有CEL验证规则的Spec定义
// +kubebuilder:validation:XValidation:rule="self.type=='Private' ? size(self.dependingValues) > 0 : true", message="private specs must have at least one depending value" type ExampleSpec struct { Type string `json:"type"` DependingValues []SomeType `json:"dependingValues"` }
当前使用EnvTest的测试代码
BeforeSuite初始化
var k8sClient client.Client var testEnv *envtest.Environment var cancel context.CancelFunc var ctx context.Context var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) ctx, cancel = context.WithCancel(context.TODO()) By("bootstrapping test environment") testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{ filepath.Join("..", "..", "config", "crd", "bases"), }, ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) err = iacv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) }) var _ = AfterSuite(func() { cancel() By("tearing down the test environment") err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) })
测试表
var _ = DescribeTable("ExampleSpecValidationTests", func(example func() *v1.ExampleSpec, valid bool) { err := k8sClient.Create(ctx, example()) if k8serrors.IsInvalid(err) { Expect(err == nil).To(Equal(valid)) } else { Expect(err).ToNot(HaveOccurred()) } }, Entry("minimal spec", func() *v1.ExampleSpec { return &minimalExampleSpec }, true), Entry("private with no depending values", func() *v1.ExampleSpec { e := minimalExampleSpec e.DependingValues = []DependingValues{} return &e }, false), )
解决方案:直接使用Kubernetes CEL验证库进行单元测试
可以绕过EnvTest,直接利用Kubernetes官方提供的CEL验证相关包,单独提取并测试CRD中的CEL规则,初始化耗时可缩短至毫秒级,完全满足单元测试的效率要求。
核心思路
- 直接复用代码注释或CRD中定义的CEL规则文本
- 使用
k8s.io/apiserver/pkg/cel包编译CEL表达式 - 将待测试的Spec实例转换为CEL可处理的对象
- 直接执行CEL规则并验证结果
代码实现示例(Ginkgo风格)
依赖准备
确保go.mod中包含以下依赖(版本可匹配你的Kubernetes版本):
require ( k8s.io/apiserver v0.27.3 k8s.io/utils v0.0.0-20230209194617-a36077c30491 )
单元测试代码
package v1 import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "k8s.io/apiserver/pkg/cel" "k8s.io/apiserver/pkg/cel/environment" ) var _ = ginkgo.Describe("ExampleSpec CEL Validation", func() { ginkgo.Context("private spec dependency rule", func() { // 直接复用代码中的CEL规则文本 const celRule = "self.type=='Private' ? size(self.dependingValues) > 0 : true" var celProgram cel.Program ginkgo.BeforeEach(func() { // 初始化CEL环境 env, err := environment.NewCoreEnvSet().Extend(environment.DefaultExtensions).Build() gomega.Expect(err).NotTo(gomega.HaveOccurred()) // 绑定ExampleSpec类型到CEL检查器 typeChecker, err := env.TypeChecker.NativeTypeToType(ExampleSpec{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) // 编译CEL表达式 expr, issues := env.Compile(celRule, cel.Variable("self", typeChecker)) gomega.Expect(issues).To(gomega.BeEmpty()) // 创建可执行的CEL程序 celProgram, err = env.Program(expr, cel.EvalOptions{}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) }) ginkgo.DescribeTable("validate spec instances", func(spec ExampleSpec, shouldPass bool) { // 执行CEL验证 result, _, err := celProgram.Eval(map[string]interface{}{"self": spec}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) // 验证结果:CEL返回true表示通过验证 gomega.Expect(result.(bool)).To(gomega.Equal(shouldPass)) }, ginkgo.Entry("public spec with empty dependencies", ExampleSpec{Type: "Public"}, true), ginkgo.Entry("private spec with dependencies", ExampleSpec{Type: "Private", DependingValues: []SomeType{{}}}, true), ginkgo.Entry("private spec without dependencies", ExampleSpec{Type: "Private", DependingValues: []SomeType{}}, false), ginkgo.Entry("empty type spec", ExampleSpec{}, true), ) }) })
方案优势
- 极致高效:无需启动Kubernetes测试环境,初始化耗时从5秒降至毫秒级
- 聚焦规则:直接测试CEL规则本身,不依赖API Server的完整逻辑
- 易于维护:测试代码与CRD规则直接关联,团队可快速理解和迭代
注意事项
- 若CEL规则使用Kubernetes特有函数(如
exists、resourceNames),需额外引入对应CEL扩展包 - 确保测试用的CEL规则文本与CRD生成的规则完全一致,避免测试与实际运行逻辑脱节
- 嵌套结构的验证可借助
k8s.io/apiserver/pkg/cel/transform包处理复杂对象转换
内容的提问来源于stack exchange,提问作者Thomas Barizien
相关产品推荐
相关产品推荐

