概念
e2e測試,a.k.a.“自動化聯(lián)調(diào)”,模擬最終用戶的操作、看效果。
不需要mock/fake環(huán)境,依賴多個系統(tǒng)的真實環(huán)境。
優(yōu)點:
- 相比于UT、集成測試:
- 更貼近真實場景,更能發(fā)現(xiàn)問題
- 寫測試用例的成本更低(因為不需要mock/fake環(huán)境)
- 相比于人工聯(lián)調(diào):
- 省去聯(lián)調(diào)時大量的溝通協(xié)作成本、人肉操作成本
- 全鏈路任何一個系統(tǒng)發(fā)生變更后(代碼變更或配置變更),可以自動化回歸測試
- 方便對環(huán)境做故障注入,看系統(tǒng)在特定故障時的表現(xiàn)
更詳細(xì)的概念介紹:
https://circleci.com/blog/what-is-end-to-end-testing/
Design choices
怎么觸發(fā)、怎么setup環(huán)境
A. CI 里觸發(fā),原地創(chuàng)建環(huán)境、跑測試
創(chuàng)建環(huán)境可以基于k8s:
- 先調(diào)k8s接口,刪除原先的資源
- 然后再通過kubectl(或者調(diào)k8s接口)創(chuàng)建(聯(lián)調(diào)需要用到的)環(huán)境。
沒有k8s的話也可以基于 docker或者 docker-compose 創(chuàng)建環(huán)境。
B. merge后持續(xù)部署,部署完成后自動觸發(fā)E2E測試
觸發(fā)后需要保證互斥執(zhí)行,避免并發(fā)跑測試。
pros:
- 寫test case不需要mock不需要fake,省去setup環(huán)境的工作
cons:
- 條件可能比較苛刻。比如,如果e2e測試依賴多個系統(tǒng),需要:
- 多個系統(tǒng)都設(shè)置持續(xù)部署
- 并發(fā)部署時,有個編排系統(tǒng),保證互斥、避免重復(fù)執(zhí)行。
C. 定時巡檢
巡檢腳本可以臨時找機(jī)器做測試,也可以使用固定的機(jī)器做測試
pros:
- 寫test case不需要mock不需要fake,省去setup環(huán)境的工作
- 和部署方案解耦,條件沒B方案那么苛刻,成本低
多分支開發(fā),但測試環(huán)境只有一套,怎么辦?
A. CI里創(chuàng)建環(huán)境,讓測試環(huán)境有多套
B. 針對指定分支(例如develop) 設(shè)置持續(xù)部署。要求所有分支上線前都得合并進(jìn)這個分支,合并后觸發(fā)自動部署。
案例分析
K8s 如何做e2e測試
怎么觸發(fā)e2e測試
CI 里觸發(fā),原地創(chuàng)建 K8s集群,然后跑測試
觸發(fā)e2e測試后,會發(fā)生什么
工具鏈會創(chuàng)建并啟動一個測試用的K8s集群(或者使用現(xiàn)有集群)
跑 E2E 測試用例。
這些測試用例本質(zhì)上是K8s的客戶端,它們會調(diào) K8s的接口、使用K8s、斷言預(yù)期效果。
- 清理掉臨時的K8S集群
詳見
https://blog.gmem.cc/kubernetes-e2e-test
例子
測試Pod能讀取secret

- 準(zhǔn)備好yaml,調(diào)用Kubectl來創(chuàng)建Secret、創(chuàng)建會讀取此Secret并打印的Pod
- 等待Pod退出
- 檢查Pod日志,斷言出現(xiàn)了關(guān)鍵詞
// 聲明一個ginkgo.Describe塊,自動添加[k8s.io] 標(biāo)簽
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")
// 創(chuàng)建一個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能否自動重啟
- 準(zhǔn)備好yaml,調(diào)用Kubectl來創(chuàng)建資源
- 輪詢調(diào)接口,查該pod的重啟次數(shù)
- 如果在一定時間內(nèi)沒滿足預(yù)期,就報錯
和其他系統(tǒng)集成(聯(lián)調(diào))的案例
- K8s + Helm
https://github.com/kubernetes-sigs/e2e-framework/tree/main/examples/third_party_integration/helm

- K8s + Flux
https://github.com/kubernetes-sigs/e2e-framework/tree/main/examples/third_party_integration/flux
怎么斷言別的Pod內(nèi)發(fā)生了什么
寫測試用例時,可以使用社區(qū)的測試框架,框架提供了實用的Utils庫,例如,提供了用于斷言“日志中出現(xiàn)關(guān)鍵詞”的函數(shù)
怎么模擬各種異常場景
- 框架有提供一些工具函數(shù),例如:

- 框架提供NodeKiller,負(fù)責(zé)周期性的模擬節(jié)點失敗
if framework.TestContext.NodeKiller.Enabled {
nodeKiller := framework.NewNodeKiller(framework.TestContext.NodeKiller, c, framework.TestContext.Provider)
// NodeKiller負(fù)責(zé)周期性的模擬節(jié)點失敗
go nodeKiller.Run(framework.TestContext.NodeKiller.NodeKillerStopCh)
}
- 自己操作pod,在pod里執(zhí)行命令
- 在自己創(chuàng)建的pod里寫程序、制造故障
代理軟件如何做e2e測試
Dapr

怎么觸發(fā)e2e測試
提PR后,可以在 CI里觸發(fā)(需要 maintainer or approver 在PR里評論 /ok-to-test),會用預(yù)先準(zhǔn)備好的 AKS 集群跑測試。
也可以用自己的 K8s 集群,手動觸發(fā)、跑測試。
觸發(fā)e2e測試后,會發(fā)生什么
- 調(diào) K8s,部署dapr代理的 redis/kafka/mongodb等系統(tǒng)
- 部署dapr:構(gòu)建,打包成鏡像,發(fā)布到鏡像倉庫,調(diào)K8s部署鏡像
- 配置dapr。Register the default component configurations for testing
- 跑e2e測試:構(gòu)建e2e app,打包成鏡像,發(fā)布到鏡像倉庫,調(diào)K8s部署,運行e2e app
e2e測試的例子
https://github.com/dapr/dapr/blob/master/tests/docs/writing-e2e-test.md
State測試
以 state 的測試為例:

Test drive
包含真正的test case,本質(zhì)是客戶端,斷言發(fā)包收包結(jié)果。
比如“寫-讀-刪-讀”測試,斷言每一步收到的response:

每一步讀寫操作其實是發(fā)http請求調(diào) test app,由 test app 調(diào)dapr
https://github.com/dapr/dapr/blob/master/tests/e2e/stateapp/stateapp_test.go#L396
Test app
收到 Test driver 的命令,給dapr 發(fā)請求
https://github.com/dapr/dapr/blob/master/tests/apps/stateapp/app.go#L464

RPC測試

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 里原地創(chuàng)建環(huán)境。沒依賴 k8s,基于 docker或者 docker-compose 創(chuàng)建環(huán)境。
運行測試用例,本質(zhì)是客戶端,斷言發(fā)包收包結(jié)果。
有UI的APP/網(wǎng)站如何做e2e測試
支付寶首頁助理
定時巡檢測試:
- 下發(fā)假數(shù)據(jù)
- 調(diào)前端接口
- assert接口返回的數(shù)據(jù)內(nèi)容
以上測試直接在線上/預(yù)發(fā)環(huán)境運行,用于變更后測試,及時發(fā)現(xiàn)變更引入的問題,包括全鏈路任何一個系統(tǒng)的配置變更、代碼變更
高級功能
如何在pod內(nèi)實現(xiàn)故障注入
方案:測試用例在目標(biāo)pod 中運行,因此測試用例有權(quán)限原地制造故障
缺點:只能在本pod內(nèi)制造故障,沒法跨pod/跨節(jié)點故障注入
如何跨pod操作(例如跨pod故障注入、跨pod查日志)
A. 給所在節(jié)點添加 DaemonSet,DaemonSet 想辦法侵入其他pod的namespace、干擾其他pod
參考 chaos mesh
B. 遠(yuǎn)程登錄指定pod,然后執(zhí)行shell命令
缺點:遠(yuǎn)程登錄有權(quán)限問題
C. 基于K8s API 讓其他pod執(zhí)行shell
D. pod內(nèi)安插間諜(后門)
Q: 如何植入后門?
方案A. 可以在給相關(guān) pod 內(nèi)添加守護(hù)進(jìn)程/sidecar容器;
好處是不侵入業(yè)務(wù)進(jìn)程
缺點:
新起一個進(jìn)程有一定的開發(fā)成本;
方案B. 直接在目標(biāo)pod的業(yè)務(wù)進(jìn)程里實現(xiàn)后門功能(可以通過引入一個庫來自動實現(xiàn),減少侵入性);
Q: 后門如何對外暴露接口?
方案A. 每個服務(wù)開放debug接口 (RPC/IPC 接口),用于跨pod操作(例如故障注入,讀取日志等);
方案B. 不開接口,test app依賴有watch機(jī)制的外部存儲,一個節(jié)點寫入命令,另一個節(jié)點watch、執(zhí)行命令
難點是怎么保證只執(zhí)行一次
Q: 編程界面?
方案A. 參考k8s,設(shè)計utils 庫,用于在指定的pod執(zhí)行操作:
[圖片上傳失敗...(image-56c068-1693271805183)]