[第7章]测试使用Client-go的应用程序

client-go类库

Client-go 库提供了一些可以与 Kubernetes API 一起行动的客户端。

  • kubernetes.Clientset 提供了一组客户端,每个分组/版本的API都有一个,用于执行对资源的Kubernetes操作(创建、更新、删除等)。

  • rest.RESTClient 提供了一个客户端,用于对资源执行REST操作(获取、发布、删除等)。

  • discovery.DiscoveryClient 提供了一个客户端来发现由API提供的资源。

所有这些客户端都实现了由 Client-go 库定义的接口:kubernetes.Interface、rest.Interface 和 discovery.DiscoveryInterface。

此外,Client-go库提供了这些接口的假实现,以帮助你为你的功能编写单元测试。这些假的实现被定义在 fack 包中,每个包都位于真实实现的目录内:kubernetes/fake、rest/fake 和 discovery/fake。

testing 目录包含 fack 客户端使用的常用工具–例如,对象跟踪器或跟踪调用的系统。你将在本章中了解到这些工具。

为了能够使用这些客户端测试你的函数,函数需要定义一个参数来传递客户端的实现,而且参数的类型必须是接口,而不是具体类型。比如说:

func CreatePod(
     ctx context.Context,
     clientset kubernetes.Interface,
     name string,
     namespace string,
     image string,
) (pod *corev1.Pod, error)

这样,你将在函数之外创建客户端,并简单地在函数内使用任何实现。

对于真正的代码,你将创建客户端,如第6章所定义。在测试中,你将使用 fack 包中的辅助函数来替代客户端。

fack clientset

下面的函数用于创建 fack clientset :

import "k8s.io/client-go/kubernetes/fake"
func NewSimpleClientset(
     objects ...runtime.Object,
) *Clientset

fack clientset 是由一个对象跟踪器支持的,它处理创建、更新和删除操作,没有任何验证或突变,并且它在响应获取和列表操作时返回对象。

你可以把 Kubernetes 对象的列表作为参数传递,这些对象将被添加到 clientset 的对象跟踪器中。例如,使用下面的方法来创建一个 fack clientset,并调用前面定义的 CreatePod 函数:

import "k8s.io/client-go/kubernetes/fake"
clientset := fake.NewSimpleClientset()
pod, err := CreatePod(
     context.Background(),
     clientset,
     aName,
     aNs,
     anImage,
)

在测试过程中,当你调用被测试的函数(在本例中是 CreatePod)后,你有几种方法来验证该函数是否完成了你所期望的。让我们考虑一下 CreatePod 函数的这个实现:

func CreatePod(
     ctx context.Context,
     clientset kubernetes.Interface,
     name string,
     namespace string,
     image string,
) (pod *corev1.Pod, err error) {
     podToCreate := corev1.Pod{
          Spec: corev1.PodSpec{
               Containers: []corev1.Container{
                    {
                         Name:  "runtime",
                         Image: image,
                    },
               },
          },
     }
     podToCreate.SetName(name)
     return clientset.CoreV1().
     Pods(namespace).
     Create(
          ctx,
          &podToCreate,
          metav1.CreateOptions{},
     )
}

检查函数的结果

当用 fack clientset 调用 CreatePod 函数时,实际的 Kubernetes API 将不会被调用,资源不会在 etcd 数据库中生成,也不会对资源进行验证和变异。相反,资源将被原封不动地存储在内存存储中,只进行最小的转换。

在这个例子中,传递给 Create 函数的 podToCreate 和 Create 函数返回的 pod 之间的唯一转换是命名空间,它通过调用传递到 Pods(namespace),并被添加到返回的 Pod 中。

为了测试 CreatePod 函数返回的值是否是你所期望的,你可以编写以下测试:

func TestCreatePod(t *testing.T) {
     var (
          name      = "a-name"
          namespace = "a-namespace"
          image     = "an-image"
          wantPod = &corev1.Pod{
               ObjectMeta: v1.ObjectMeta{
                    Name:      "a-name",
                    Namespace: "a-namespace",
               },
               Spec: corev1.PodSpec{
                    Containers: []corev1.Container{
                         {
                              Name:  "runtime",
                              Image: "an-image",
                         },
                    },
               },
          }
     )
     clientset := fake.NewSimpleClientset()
     gotPod, err := CreatePod(
          context.Background(),
          clientset,
          name,
          namespace,
          image,
     )
     if err != nil {
          t.Errorf("err = %v, want nil", err)
     }
     if !reflect.DeepEqual(gotPod, wantPod) {
          t.Errorf("CreatePod() = %v, want %v",
               gotPod,
               wantPod,
          )
     }
}

这个测试将断言–给定一个名字、一个命名空间和一个图像–函数的结果将是wantPod,此时Pod中没有发生任何验证或变异。

不可能用这个测试来了解真正的客户端会发生什么,因为结果会有所不同–也就是说,真正的客户端和底层API会对对象进行突变,增加默认值,等等。

对行动的反应

假的客户集在没有任何验证或变异的情况下按原样存储资源。在测试中,你可能想模拟各种控制器对资源所做的改变。为此,假客户集提供了添加反应器的方法。Reactors是在特定资源上进行特定操作时执行的函数。

除了Watch和Proxy,所有操作的Reactors函数的类型定义如下:

TBD