You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何快速对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规则直接关联,团队可快速理解和迭代

注意事项

  1. 若CEL规则使用Kubernetes特有函数(如exists、resourceNames),需额外引入对应CEL扩展包
  2. 确保测试用的CEL规则文本与CRD生成的规则完全一致,避免测试与实际运行逻辑脱节
  3. 嵌套结构的验证可借助k8s.io/apiserver/pkg/cel/transform包处理复杂对象转换

内容的提问来源于stack exchange,提问作者Thomas Barizien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 23:15:56