-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #154 from mlycore/master
fix esterwang's conflicts
- Loading branch information
Showing
2 changed files
with
150 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
--- | ||
title: "技术日报(2019-04-01)" | ||
date: 2019-04-01T00:00:00+08:00 | ||
categories: [ "daily"] | ||
draft: false | ||
--- | ||
### [容器时代]技术日报(20180922) | ||
|
||
1. PostgreSQL on Kubernetes: How to run a stateful legacy app on a stateless microservice http://www.bmc.com/blogs/kubernetes-postgresql/ | ||
2. Securing a dockerized plumber API with SSL and Basic Authentication https://www.tuicool.com/articles/IrqQRbi | ||
3. MicroK8s in the Wild https://blog.ubuntu.com/2019/03/28/microk8s-in-the-wild | ||
4. Docker-06-持久化存储和数据共享 https://www.cnblogs.com/liuguangjiji/p/10630801.html | ||
5. GoLang with Rails https://shwetakale.wordpress.com/2019/03/28/golang-with-rails/ | ||
|
||
编辑:@esterwang | ||
|
||
地址:<http://k8s.today/> | ||
|
||
[容器时代志愿编辑招募] <https://mp.weixin.qq.com/s/BVBzWx_u1xfwSZGTO-6qlg> | ||
|
||
### 其他 | ||
|
||
1、欢迎将自己的博文转载到公众号进一步推广 | ||
|
||
2、想翻译文章的同学现在就可开始 |
234 changes: 125 additions & 109 deletions
234
translation/Unit Testing with the Kubernetes Client Library.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,157 +1,173 @@ | ||
# 如何进行单元测试代码来调用Kubernetes API | ||
使用Kubernetes客户端库进行单元测试 | ||
============= | ||
|
||
使用kubernetes客户机库可以帮助您模拟出一个集群来测试您的代码。 | ||
如何对被 Kubernetes API 调用的代码进行单元测试? | ||
|
||
在构建[kubernetes/minikube](https://github.com/kubernetes/minikube)时,作为[kubernetes/client-go ](https://github.com/kubernetes/client-go)库的第一个消费者之一,services、pods和deployments构建了详细的模拟,以单元测为例,现在,有一种更简单的方法可以用更少的代码行来做同样的事情。 | ||
使用 Kubernetes 客户端库可以通过模拟集群来测试代码。 | ||
|
||
我将演示如何测试一个简单的函数,该函数列出了集群中运行的所有容器镜像。你需要一个Kubernetes集群,我建议用GKE或桌面版的Docker。 | ||
作为在构建 kubernetes / minikube 时,第一批使用kubernetes / client-go 库的用户之一,我曾经详尽的设计了服务、调度单元和部署来对我的代码进行单元测试。 现在,我发现有一种更简单的方法可以用更少的代码行来做同样的事情。 | ||
|
||
## Setup | ||
克隆示例存储库[https://github.com/r2d4/k8s-unit-test-example ](https://github.com/r2d4/k8s-unit-test-example ),如果要运行命令并以交互方式继续操作。 | ||
我将演示如何测试一个**列出了集群中运行的所有容器映像**的简单的函数。我建议你用 GKE 或 Docker 作为桌面,并且你还需要一个 Kubernetes 集群。 | ||
|
||
###main.go | ||
### 安装程序 | ||
|
||
如果你想要运行命令并以交互方式继续操作,请克隆示例仓库 https://github.com/r2d4/k8s-unit-test-example 。 | ||
|
||
### main.go | ||
|
||
```go | ||
import ( | ||
"github.com/pkg/errors" | ||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes/typed/core/v1" | ||
) | ||
//ListImages返回在提供的命名空间中运行的容器映像的列表 | ||
func ListImages(client v1.CoreV1Interface, namespace string) ([]string, error) { | ||
pl, err := client.Pods(namespace).List(meta_v1.ListOptions{}) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "getting pods") | ||
} | ||
var images []string | ||
for _, p := range pl.Items { | ||
for _, c := range p.Spec.Containers { | ||
images = append(images, c.Image) | ||
} | ||
package main | ||
|
||
import ( | ||
"github.com/pkg/errors" | ||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes/typed/core/v1" | ||
) | ||
|
||
// ListImages returns a list of container images running in the provided namespace | ||
func ListImages(client v1.CoreV1Interface, namespace string) ([]string, error) { | ||
pl, err := client.Pods(namespace).List(meta_v1.ListOptions{}) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "getting pods") | ||
} | ||
|
||
var images []string | ||
for _, p := range pl.Items { | ||
for _, c := range p.Spec.Containers { | ||
images = append(images, c.Image) | ||
} | ||
return images, nil | ||
} | ||
} | ||
|
||
return images, nil | ||
} | ||
``` | ||
|
||
|
||
## Writing the Tests | ||
从测试用例的定义开始,以及一些运行测试的框架代码。 | ||
|
||
|
||
|
||
### 编写测试 | ||
|
||
我们可以从测试用例的定义开始,运行一些测试的框架代码。 | ||
|
||
```go | ||
func TestListImages(t *testing.T) { | ||
var tests = []struct { | ||
description string | ||
namespace string | ||
expected []string | ||
objs []runtime.Object | ||
var tests = []struct { | ||
description string | ||
namespace string | ||
expected []string | ||
objs []runtime.Object | ||
}{ | ||
{"no pods", "", nil, nil}, | ||
} | ||
{ | ||
{"no pods", "", nil, nil}, | ||
} | ||
// 在这写需要测试的代码 | ||
} | ||
``` | ||
|
||
## What's Happening | ||
|
||
// Actual testing code goes here... | ||
} | ||
|
||
这种编写测试的风格称为“表驱动测试”,在Go中,这是首选的样式。实际的测试代码迭代表条目并执行必要的测试。测试代码只写一次,用于每种情况。需要注意一些事情: | ||
``` | ||
|
||
* 用于保存测试用例定义的匿名结构。它们允许我们简洁地定义测试用例。 | ||
#### 发生了什么 | ||
|
||
这种编写测试的风格称为**“表驱动测试”**,是Go语言中首选的样式,实际测试代码会迭代表条目并执行必要的测试。测试代码只需要写一次,却可以适用于每种情况。我门可以注意到一些有趣的事情: | ||
|
||
* 运行时对象切片objs将保存所有希望我们的模拟API服务器保存的运行时对象。将用一些pods填充,但是您可以在这里使用任何kubernetes对象。 | ||
* 琐碎的测试用例,服务器上没有pods不应返回任何image。 | ||
- 测试用例的定义是用匿名结构保存的,因此我们可以简洁的定义测试用例。 | ||
- 对象切片运行时,`objs`将保存我们的模拟 API 服务器保存的所有正在运行的对象,此处我们选择用一些调度单元来填充它,但其实可以在这里使用任何 Kubernetes 对象。 | ||
- 如果测试用例非常琐碎,服务器上将没有调度单元,并且不返回任何图像。 | ||
|
||
### 测试回路 | ||
|
||
## Test Loop | ||
填写将为每个测试用例运行的实际测试代码。 | ||
让我们来为每个测试用例都写一段实际测试代码。 | ||
|
||
```go | ||
for _, test := range tests { | ||
t.Run(test.description, func(t *testing.T) { | ||
client := fake.NewSimpleClientset(test.objs...) | ||
actual, err := ListImages(client.CoreV1(), test.namespace) | ||
if err != nil { | ||
t.Errorf("Unexpected error: %s", err) | ||
return | ||
} | ||
if diff := cmp.Diff(actual, test.expected); diff != "" { | ||
t.Errorf("%T differ (-got, +want): %s", test.expected, diff) | ||
return | ||
} | ||
}) | ||
} | ||
for _, test := range tests { | ||
t.Run(test.description, func(t *testing.T) { | ||
client := fake.NewSimpleClientset(test.objs...) | ||
actual, err := ListImages(client.CoreV1(), test.namespace) | ||
if err != nil { | ||
t.Errorf("Unexpected error: %s", err) | ||
return | ||
} | ||
if diff := cmp.Diff(actual, test.expected); diff != "" { | ||
t.Errorf("%T differ (-got, +want): %s", test.expected, diff) | ||
return | ||
} | ||
}) | ||
} | ||
``` | ||
需要注意的一些有趣的事情, | ||
t.run运行执行子测试。为什么使用子测试? | ||
|
||
* 可以使用-run flg to go test | ||
在这里我们也可以注意到一些有趣的事情: | ||
|
||
* 可以做设置和拆卸子测试是并行运行测试用例的入口(这里不做) | ||
- 文件`t.Run` 的功能是执行子测试,那么为什么要使用子测试? | ||
|
||
* 实际结果和预期结果与cmp.diff不同。diff返回两个值之间差异的人类可读报告。如果相同的输入值和选项的equal返回true,则返回空字符串。 | ||
- - 你可以使用`-run` 命令运行特定的测试用例进行测试。 | ||
|
||
fake.newsImpleClientSet返回将使用提供的对象响应的客户端集。 它由一个非常简单的对象跟踪器提供支持,该跟踪器按原样处理增加、更新和删除操作,不应用任何验证和/或默认值。 | ||
- 你可以设置和清理测试代码 | ||
- 子测试是同时运行测试用例的入口(这篇文章将不涉及) | ||
|
||
- 实际结果和预期结果都与 `cmp.diff` 不同。Diff 返回的两个值之间差异是可读的,当且仅当输入值和选项相等且为真时,Diff返回空字符串。 | ||
|
||
## Test Cases | ||
`fake.NewSimpleClientset` 返回一个使用提供的对象进行响应的客户端集。它由一个非常简单的对象跟踪器支持,该跟踪器按原样处理创建、更新和删除操作,而不应用任何验证和默认值。 | ||
|
||
创建一个pod函数,它将帮助提供一些pod供我们测试。既然我们关心namespace和image,它基于这些参数创建新的pod. | ||
|
||
``` | ||
func pod(namespace, image string) *v1.Pod { | ||
return &v1.Pod{ObjectMeta: meta_v1.ObjectMeta{Namespace: namespace}, Spec: v1.PodSpec{Containers: []v1.Container{{Image: image}}}} | ||
### 测试用例 | ||
|
||
我们来创建一个调度单元助手函数,它将帮助我们提供一些测试的调度单元。由于我们关心的是命名空间和镜像,所以我们可以创建一个可以基于这些参数创建新的调度单元的助手。 | ||
|
||
```go | ||
func pod(namespace, image string) *v1.Pod { | ||
return &v1.Pod{ObjectMeta: meta_v1.ObjectMeta{Namespace: namespace}, Spec: v1.PodSpec{Containers: []v1.Container{{Image: image}}}} | ||
} | ||
``` | ||
|
||
写三个单元测试。如果使用特殊的namespace值列出所有namespace中的pods,第一个方法将确保获取所有image。 | ||
``` | ||
我们来写三个单元测试。第一个方法能确保我门获取所有镜像,这时我们需要使用特殊的命名空间值 `“”` 来列出命名空间中所有的调度单元。 | ||
|
||
```go | ||
{"all namespaces", "", []string{"a", "b"}, []runtime.Object{pod("correct-namespace", "a"), pod("wrong-namespace", "b")}} | ||
``` | ||
|
||
第二种情况将确保按namespace正确筛选,忽略错误namespace中的pod。 | ||
``` | ||
第二种方法能确保我们按名称空间正确筛选,忽略调度单元中的 `wrong-namespace` 。 | ||
|
||
```go | ||
{"filter namespace", "correct-namespace", []string{"a"}, []runtime.Object{pod("correct-namespace", "a"), pod("wrong-namespace", "b")}} | ||
``` | ||
|
||
第三种情况将确保如果所需的namespace中没有pods,则不会返回任何内容。 | ||
``` | ||
第三种方法能确保如果所需的命名空间中没有调度单元,则不会返回任何内容。 | ||
|
||
```go | ||
{"wrong namespace", "correct-namespace", nil, []runtime.Object{pod("wrong-namespace", "b")}} | ||
``` | ||
#### Putting it all together | ||
|
||
把上面这些代码放在一起。 | ||
|
||
```go | ||
func TestListImages(t *testing.T) { | ||
var tests = []struct { | ||
description string | ||
namespace string | ||
expected []string | ||
objs []runtime.Object | ||
func TestListImages(t *testing.T) { | ||
var tests = []struct { | ||
description string | ||
namespace string | ||
expected []string | ||
objs []runtime.Object | ||
}{ | ||
{"no pods", "", nil, nil}, | ||
{"all namespaces", "", []string{"a", "b"}, []runtime.Object{pod("correct-namespace", "a"), pod("wrong-namespace", "b")}}, | ||
{"filter namespace", "correct-namespace", []string{"a"}, []runtime.Object{pod("correct-namespace", "a"), pod("wrong-namespace", "b")}}, | ||
{"wrong namespace", "correct-namespace", nil, []runtime.Object{pod("wrong-namespace", "b")}}, | ||
} | ||
{ | ||
{"no pods", "", nil, nil}, | ||
{"all namespaces", "", []string{"a", "b"},[]runtime.Object{pod("correct-namespace", "a"), pod("wrong-namespace", "b")}}, | ||
{"filter namespace", "correct-namespace", []string{"a"},[]runtime.Object{pod("correct-namespace", "a"), pod("wrong-namespace", "b")}}, | ||
{"wrong namespace", "correct-namespace", nil,[]runtime.Object{pod("wrong-namespace", "b")}}, | ||
} | ||
for _, test := range tests { | ||
t.Run(test.description, func(t *testing.T) { | ||
client := fake.NewSimpleClientset(test.objs...) | ||
actual, err := ListImages(client.CoreV1(), test.namespace) | ||
if err != nil { | ||
|
||
for _, test := range tests { | ||
t.Run(test.description, func(t *testing.T) { | ||
client := fake.NewSimpleClientset(test.objs...) | ||
actual, err := ListImages(client.CoreV1(), test.namespace) | ||
if err != nil { | ||
t.Errorf("Unexpected error: %s", err) | ||
return | ||
} | ||
if diff := cmp.Diff(actual, test.expected); diff != "" { | ||
return | ||
} | ||
if diff := cmp.Diff(actual, test.expected); diff != "" { | ||
t.Errorf("%T differ (-got, +want): %s", test.expected, diff) | ||
return | ||
return | ||
} | ||
}) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
``` | ||
Matt Rickard | ||
|
||
[https://matt-rickard.com/kubernetes-unit-testing/](https://matt-rickard.com/kubernetes-unit-testing/) | ||
### 原文链接 | ||
[原文作者:@mattrickard] | ||
[原文链接:https://matt-rickard.com/kubernetes-unit-testing/](https://matt-rickard.com/kubernetes-unit-testing/) |