概念
e2e测试,a.k.a.“自动化联调”,模拟最终用户的操作、看效果。
不需要mock/fake环境,依赖多个系统的真实环境。
优点:
- 相比于UT、集成测试:
- 更贴近真实场景,更能发现问题
- 写测试用例的成本更低(因为不需要mock/fake环境)
- 相比于人工联调:
- 省去联调时大量的沟通协作成本、人肉操作成本
- 全链路任何一个系统发生变更后(代码变更或配置变更),可以自动化回归测试
- 方便对环境做故障注入,看系统在特定故障时的表现
更详细的概念介绍:
https://circleci.com/blog/what-is-end-to-end-testing/
Design choices
怎么触发、怎么setup环境
A. CI 里触发,原地创建环境、跑测试
创建环境可以基于k8s:
- 先调k8s接口,删除原先的资源
- 然后再通过kubectl(或者调k8s接口)创建(联调需要用到的)环境。
没有k8s的话也可以基于 docker或者 docker-compose 创建环境。
B. merge后持续部署,部署完成后自动触发E2E测试
触发后需要保证互斥执行,避免并发跑测试。
pros:
- 写test case不需要mock不需要fake,省去setup环境的工作
cons:
- 条件可能比较苛刻。比如,如果e2e测试依赖多个系统,需要:
- 多个系统都设置持续部署
- 并发部署时,有个编排系统,保证互斥、避免重复执行。
C. 定时巡检
巡检脚本可以临时找机器做测试,也可以使用固定的机器做测试
pros:
- 写test case不需要mock不需要fake,省去setup环境的工作
- 和部署方案解耦,条件没B方案那么苛刻,成本低
多分支开发,但测试环境只有一套,怎么办?
A. CI里创建环境,让测试环境有多套
B. 针对指定分支(例如develop) 设置持续部署。要求所有分支上线前都得合并进这个分支,合并后触发自动部署。
案例分析
K8s 如何做e2e测试
怎么触发e2e测试
CI 里触发,原地创建 K8s集群,然后跑测试
触发e2e测试后,会发生什么
-
工具链会创建并启动一个测试用的K8s集群(或者使用现有集群)
-
跑 E2E 测试用例。
这些测试用例本质上是K8s的客户端,它们会调 K8s的接口、使用K8s、断言预期效果。
- 清理掉临时的K8S集群
详见
https://blog.gmem.cc/kubernetes-e2e-test
例子
测试Pod能读取secret
![](https://img.haomeiwen.com/i8926363/23a51404334fd0f3.png)
- 准备好yaml,调用Kubectl来创建Secret、创建会读取此Secret并打印的Pod
- 等待Pod退出
- 检查Pod日志,断言出现了关键词
// 声明一个ginkgo.Describe块,自动添加[k8s.io] 标签
var _ = framework.KubeDescribe("[Feature:Example]", func() {
//省略……
framework.KubeDescribe("Secret", func() {
// 第二个Spec,测试Pod能读取一个保密字典
ginkgo.It("should create a pod that reads a secret", func() {
test := "test/fixtures/doc-yaml/user-guide/secrets"
secretYaml := readFile(test, "secret.yaml")
podYaml := readFile(test, "secret-pod.yaml.in")
nsFlag := fmt.Sprintf("--namespace=%v", ns)
podName := "secret-test-pod"
ginkgo.By("creating secret and pod")
// 创建一个Secret,以及会读取此Secret并打印的Pod
framework.RunKubectlOrDieInput(secretYaml, "create", "-f", "-", nsFlag)
framework.RunKubectlOrDieInput(podYaml, "create", "-f", "-", nsFlag)
// 等待Pod退出
err := e2epod.WaitForPodNoLongerRunningInNamespace(c, podName, ns)
framework.ExpectNoError(err)
ginkgo.By("checking if secret was read correctly")
// 检查Pod日志
_, err = framework.LookForStringInLog(ns, "secret-test-pod", "test-container", "value-1", serverStartTimeout)
framework.ExpectNoError(err)
})
})
}
测试健康检查失败的Pod能否自动重启
- 准备好yaml,调用Kubectl来创建资源
- 轮询调接口,查该pod的重启次数
- 如果在一定时间内没满足预期,就报错
和其他系统集成(联调)的案例
- K8s + Helm
https://github.com/kubernetes-sigs/e2e-framework/tree/main/examples/third_party_integration/helm
![](https://img.haomeiwen.com/i8926363/12da2a00ec5e9843.png)
- K8s + Flux
https://github.com/kubernetes-sigs/e2e-framework/tree/main/examples/third_party_integration/flux
怎么断言别的Pod内发生了什么
写测试用例时,可以使用社区的测试框架,框架提供了实用的Utils库,例如,提供了用于断言“日志中出现关键词”的函数
怎么模拟各种异常场景
- 框架有提供一些工具函数,例如:
![](https://img.haomeiwen.com/i8926363/1b371f094ec1746a.png)
- 框架提供NodeKiller,负责周期性的模拟节点失败
if framework.TestContext.NodeKiller.Enabled {
nodeKiller := framework.NewNodeKiller(framework.TestContext.NodeKiller, c, framework.TestContext.Provider)
// NodeKiller负责周期性的模拟节点失败
go nodeKiller.Run(framework.TestContext.NodeKiller.NodeKillerStopCh)
}
- 自己操作pod,在pod里执行命令
- 在自己创建的pod里写程序、制造故障
代理软件如何做e2e测试
Dapr
![](https://img.haomeiwen.com/i8926363/8a6836958d7b1ef5.png)
怎么触发e2e测试
提PR后,可以在 CI里触发(需要 maintainer or approver 在PR里评论 /ok-to-test
),会用预先准备好的 AKS 集群跑测试。
也可以用自己的 K8s 集群,手动触发、跑测试。
触发e2e测试后,会发生什么
- 调 K8s,部署dapr代理的 redis/kafka/mongodb等系统
- 部署dapr:构建,打包成镜像,发布到镜像仓库,调K8s部署镜像
- 配置dapr。Register the default component configurations for testing
- 跑e2e测试:构建e2e app,打包成镜像,发布到镜像仓库,调K8s部署,运行e2e app
e2e测试的例子
https://github.com/dapr/dapr/blob/master/tests/docs/writing-e2e-test.md
State测试
以 state 的测试为例:
![](https://img.haomeiwen.com/i8926363/6f7e038a5f6fcba1.png)
Test drive
包含真正的test case,本质是客户端,断言发包收包结果。
比如“写-读-删-读”测试,断言每一步收到的response:
![](https://img.haomeiwen.com/i8926363/613f3bfbb6be8339.png)
每一步读写操作其实是发http请求调 test app,由 test app 调dapr
https://github.com/dapr/dapr/blob/master/tests/e2e/stateapp/stateapp_test.go#L396
Test app
收到 Test driver 的命令,给dapr 发请求
https://github.com/dapr/dapr/blob/master/tests/apps/stateapp/app.go#L464
![](https://img.haomeiwen.com/i8926363/a962274d774b8168.png)
RPC测试
![](https://img.haomeiwen.com/i8926363/e9c7556013003f09.png)
Test driver
https://github.com/dapr/dapr/blob/master/tests/e2e/service_invocation/service_invocation_test.go#L79
https://github.com/dapr/dapr/blob/master/tests/e2e/service_invocation/service_invocation_test.go#L229
[图片上传失败...(image-acb490-1693271805183)]
Test app
https://github.com/dapr/dapr/blob/master/tests/apps/service_invocation/app.go#L174
https://github.com/dapr/dapr/blob/master/tests/apps/service_invocation/app.go#L299
咋断言upstream收到了某个包?
看起来没断言upstream 收到的包,只是断言 client 收到的最终response
Layotto
https://mosn.io/layotto/#/zh/development/test-quickstart
CI 里原地创建环境。没依赖 k8s,基于 docker或者 docker-compose 创建环境。
运行测试用例,本质是客户端,断言发包收包结果。
有UI的APP/网站如何做e2e测试
支付宝首页助理
定时巡检测试:
- 下发假数据
- 调前端接口
- assert接口返回的数据内容
以上测试直接在线上/预发环境运行,用于变更后测试,及时发现变更引入的问题,包括全链路任何一个系统的配置变更、代码变更
高级功能
如何在pod内实现故障注入
方案:测试用例在目标pod 中运行,因此测试用例有权限原地制造故障
缺点:只能在本pod内制造故障,没法跨pod/跨节点故障注入
如何跨pod操作(例如跨pod故障注入、跨pod查日志)
A. 给所在节点添加 DaemonSet,DaemonSet 想办法侵入其他pod的namespace、干扰其他pod
参考 chaos mesh
B. 远程登录指定pod,然后执行shell命令
缺点:远程登录有权限问题
C. 基于K8s API 让其他pod执行shell
D. pod内安插间谍(后门)
Q: 如何植入后门?
方案A. 可以在给相关 pod 内添加守护进程/sidecar容器;
好处是不侵入业务进程
缺点:
新起一个进程有一定的开发成本;
方案B. 直接在目标pod的业务进程里实现后门功能(可以通过引入一个库来自动实现,减少侵入性);
Q: 后门如何对外暴露接口?
方案A. 每个服务开放debug接口 (RPC/IPC 接口),用于跨pod操作(例如故障注入,读取日志等);
方案B. 不开接口,test app依赖有watch机制的外部存储,一个节点写入命令,另一个节点watch、执行命令
难点是怎么保证只执行一次
Q: 编程界面?
方案A. 参考k8s,设计utils 库,用于在指定的pod执行操作:
[图片上传失败...(image-56c068-1693271805183)]
网友评论