news 2026/9/12 12:47:01

Dapr 端到端测试编写指南:Test App 与 Test Driver 双角色实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Dapr 端到端测试编写指南:Test App 与 Test Driver 双角色实战

Dapr 端到端测试编写指南:Test App 与 Test Driver 双角色实战

【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr

Dapr 仓库的端到端(E2E)测试采用「测试应用 + 测试驱动」的双角色模型:测试应用(Test App)作为带 Dapr Sidecar 的 HTTP 服务暴露测试入口,测试驱动(Test Driver)以e2e构建标签的 Go 测试在 Kubernetes 集群中驱动整个测试流程。本文以 hellodapr 为样板,完整讲解如何在 Dapr 仓库中新增 Test App、编写 Test Driver、利用测试框架自动管理资源生命周期,并给出从镜像构建、本地部署调试到全量回归的完整命令链。

E2E 测试架构:两个核心角色如何协作

在动手写代码之前,先理解 Dapr E2E 测试的两个基本构成,这是 writing-e2e-test.md 定义的骨架:

  • Test app(测试应用):一个监听 3000 端口的简单 HTTP 服务器,暴露测试入口端点/tests/{test},用于调用 Dapr Runtime API。测试驱动会将部署到 Kubernetes 集群并注入 Dapr Sidecar。
  • Test driver(测试驱动):带有e2e构建标签的 Go 测试文件,负责把测试应用部署到 Kubernetes 集群,再通过调用测试应用的入口端点来执行 Go 测试。

两者通过 HTTP 协作:Test Driver 只负责「编排」(部署、探活、发起请求、断言),真正的业务验证逻辑(如调用 Dapr 状态、Pub/Sub、Actor API)放在 Test App 的/tests/{test}端点中。这样设计的好处是测试应用可以被多个 Test Driver 复用,且与运行环境解耦。

前置要求:动手编写前,请先确保本地已具备 E2E 测试运行环境,即集群中已部署 Dapr 控制平面,并且能够运行现有 E2E 测试。

第一步:新增 Test App(测试应用)

Test App 位于 tests/apps/ 目录,可以被多个 Test Driver 共享。技术上可以用任意语言编写,但仓库推荐使用 Go,以保持代码库的一致与简洁。hellodapr 就是最合适的样板代码。

创建步骤

  1. 在 tests/apps/ 下新建测试应用目录;
  2. 从 tests/apps/hellodapr/ 复制样板文件(注意:当前仓库中 Dockerfile 统一放在 tests/Dockerfile,hellodapr 目录内保留app.goservice.yaml两个文件):
    • app.go:用 Go 编写的简单 HTTP 服务器;
    • service.yaml:供 kubectl 使用的测试应用部署清单,仅用于开发调试,实际 E2E 测试不会使用它部署;
  3. 修改app.go,加入你自己的测试逻辑;
  4. 本地运行 Go 应用,验证app.go暴露的 HTTP 端点。

深入样板实现:hellodapr 的 app.go

来看 tests/apps/hellodapr/app.go 的关键结构。应用默认监听3000端口,可通过环境变量PORT覆盖(init()中读取并解析):

var appPort = 3000 func init() { p := os.Getenv("PORT") if p != "" && p != "0" { appPort, _ = strconv.Atoi(p) } }

路由注册在appRouter()中,共两个端点:

router.HandleFunc("/", indexHandler).Methods("GET") router.HandleFunc("/tests/{test}", testHandler).Methods("POST")
  • GET /:健康检查入口,返回{"message":"OK"}
  • POST /tests/{test}:测试入口,{test}路径参数是测试命令名,请求体携带 JSON(如{"message":"Hello Dapr."})。

testHandler是核心分发器,它从路径变量取出测试命令,按switch分发到具体的测试函数,并在响应中记录start_time/end_time(Unix 毫秒时间戳),便于测试端做耗时断言:

func testHandler(w http.ResponseWriter, r *http.Request) { testCommand := mux.Vars(r)["test"] var commandBody testCommandRequest err := json.NewDecoder(r.Body).Decode(&commandBody) if err != nil { w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(appResponse{Message: err.Error()}) return } res := appResponse{Message: testCommand + " is not supported"} statusCode := http.StatusBadRequest startTime := epoch() switch testCommand { case "blue": statusCode, res = blueTest(commandBody) case "green": statusCode, res = greenTest(commandBody) case "envTest": statusCode, res = envTest(commandBody) } res.StartTime = startTime res.EndTime = epoch() w.WriteHeader(statusCode) json.NewEncoder(w).Encode(res) }

其中envTest演示了如何读取 Sidecar 注入的环境变量DAPR_HTTP_PORTDAPR_GRPC_PORT,可用于验证 Dapr Sidecar 是否正确注入、端口是否就绪——这是验证 Sidecar 注入类用例的典型手法。

调试与验证 Test App

  1. 将新测试应用目录名加入 tests/dapr_tests.mk 的E2E_TEST_APPS变量;
  2. 通过 make 构建并推送测试镜像(需要预先配置DAPR_TEST_REGISTRYDAPR_TEST_TAG两个环境变量,否则 check-e2e-env 会直接报错):
# DAPR_TEST_REGISTRY 与 DAPR_TEST_TAG 必须已配置 make build-e2e-app-[新应用目录名] make push-e2e-app-[新应用目录名]

对应的 make 模板定义在 tests/dapr_tests.mk 中:build-e2e-app-$(1)通过e2e build子命令生成镜像并推送到DAPR_TEST_REGISTRYpush-e2e-app-$(1)负责推送到目标仓库。所有E2E_TEST_APPS项会被$(foreach ...)自动展开为同名目标。

  1. 修改service.yaml,更新 metadata 与镜像仓库/镜像名(镜像命名约定为e2e-[测试应用名],如 tests/apps/hellodapr/service.yaml 中的dapriotest/e2e-hellodapr);
  2. 部署到集群做联调:
kubectl apply -f service.yaml
  1. 获取外部 IP:
kubectl get svc
  1. 用 wget / curl / Postman 验证外部端点;
  2. 删除测试应用:
kubectl delete -f service.yaml

service.yaml值得注意:它通过dapr.io/enabled: "true"dapr.io/app-iddapr.io/app-port注解声明 Sidecar 注入,同时以LoadBalancer类型的 Service 暴露 80→3000 端口。这套清单只服务于「编写 E2E 测试前」的人工联调,正式 E2E 运行完全交给 Test Driver 与测试框架。

第二步:新增 Test Driver(测试驱动)

Test Driver 是位于 tests/e2e/ 下、带e2e构建标签的普通 Go 测试。它可以部署测试应用、执行测试,并在测试结束后清理全部应用与资源。仓库提供了 Test Runner 框架来托管测试应用的生命周期,因此你无需手动管理部署与清理;框架后续还会扩展 Pod 日志收集等功能。

重要提示:任何你部署的测试应用都必须使用对当前 Test Driver 唯一的名称。自动化 E2E 运行器可能同时运行多个 Test Driver,应用名不唯一就会与其他测试应用冲突。同理,所有有状态资源(Pub/Sub topic、状态存储、Secret 等)的命名也要对当前 Test Driver 唯一。

创建步骤

  1. 在 tests/e2e/ 下新建测试目录;
  2. 基于下述骨架创建 Go 测试文件,关键约束:
    • // +build e2e必须放在测试代码第一行(现代 Go 版本同时支持//go:build e2e形式,实际仓库文件如 hellodapr_test.go 两种标签都会写);
    • TestMain(m *testing.M)定义测试应用与组件清单,供TestXxx(t *testing.T)使用;
    • 为测试定义独立 package;
    • 遵循 Go 测试最佳实践。
// +build e2e package hellodapr_e2e import ( "testing" kube "github.com/dapr/dapr/tests/platforms/kubernetes" "github.com/dapr/dapr/tests/runner" "github.com/stretchr/testify/require" ... ) // Test runner instance var tr *runner.TestRunner // Go test main entry - https://golang.org/pkg/testing/#hdr-Main func TestMain(m *testing.M) { testApps := []kube.AppDescription{ { AppName: "hellodapr", DaprEnabled: true, ImageName: "e2e-hellodapr", Replicas: 1, IngressEnabled: true, MetricsEnabled: true, }, } tr = runner.NewTestRunner("hellodapr", testApps, nil, nil) os.Exit(tr.Start(m)) } func TestHelloDapr(t *testing.T) { externalURL := tr.Platform.AcquireAppExternalURL("hellodapr") require.NotEmpty(t, externalURL, "external URL must not be empty") // Call endpoint for "hellodapr" test app resp, err := httpGet(externalURL) require.NoError(t, err) ... } func TestStateStore(t *testing.T) { ... }

TestMain:声明应用清单并启动 Test Runner

TestMain()中定义整个测试文件会用到的应用数组,然后创建 Test Runner 实例并启动:

// The array of test apps which will be used for the entire tests // defined in this test driver file testApps := []kube.AppDescription{ { AppName: "hellodapr", // app name DaprEnabled: true, // dapr sidecar injection ImageName: "e2e-hellodapr", // docker image name 'e2e-[test app name]' Replicas: 1, // number of replicas IngressEnabled: true, // enable ingress endpoint MetricsEnabled: true, // enable metrics endpoint }, } // Create test runner instance with 'hellodapr' runner id tr = runner.NewTestRunner("hellodapr", testApps, nil, nil) // Start the test os.Exit(tr.Start(m))

kube.AppDescription的完整字段定义在 tests/platforms/kubernetes/app_description.go,除文档示例中的基础字段外,仓库还支持以下常用配置:

字段类型含义
AppNamestring应用名(测试内唯一)
AppPortint应用监听端口,默认 3000
DaprEnabledbool是否注入 Dapr Sidecar
ImageNamestring镜像名,约定e2e-[应用名]
SidecarImagestring指定 Sidecar 镜像(用于版本偏斜测试)
RegistryName/ImageSecretstring私有仓库与拉取凭据
Replicasint32副本数
IngressEnabledbool是否暴露 LoadBalancer/NodePort 外部端点
IngressPortint外部端口,缺省用 AppPort
MetricsEnabledbool控制dapr.io/enable-metrics注解
Configstring关联的 Dapr Configuration CRD
AppCPULimit/AppCPURequest/AppMemoryLimit/AppMemoryRequeststring应用容器资源限制与请求
DaprCPULimit/DaprCPURequest/DaprMemoryLimit/DaprMemoryRequeststringSidecar 容器资源限制与请求
AppEnvmap[string]string注入到应用的环境变量
IsJobbool是否以 Job 形式部署
EnableAppHealthCheck/AppHealthCheckPathbool/int/string应用健康检查配置

从源码看,IngressEnabled与外部访问还受环境变量TEST_E2E_USE_INTERNAL_IP影响(见ShouldBeExposed()),集群内联调时可走 Service 内部 IP。

runner.NewTestRunner(id, apps, comps, initApps)的四个参数分别为:Runner ID(用于日志标识)、测试应用数组、Dapr 组件(ComponentDescription)数组、初始化应用数组(先于测试应用部署,可用于预置组件或执行准备工作)。

TestXxx:编写具体用例

TestXxx(t *testing.T)的通用范式是:先获取应用外部 URL,再向其入口端点发送 HTTP 请求:

func TestHelloDapr(t *testing.T) { // Acquire app external url externalURL := tr.Platform.AcquireAppExternalURL("hellodapr") require.NotEmpty(t, externalURL, "external URL must not be empty") // Call endpoint for "hellodapr" test app resp, err := httpGet(externalURL) require.NoError(t, err) ... }

参考仓库真实实现 tests/e2e/hellodapr/hellodapr_test.go,一个完整的用例会覆盖「探活 → 触发测试 → 断言响应」全流程。它使用 tests/e2e/utils 提供的 HTTP 工具函数(utils.HTTPGetNTimesnumHealthChecks次轮询探活、utils.HTTPPost发送 JSON 请求体):

func TestHelloDapr(t *testing.T) { helloAppTests := []struct { testName string app string testCommand string expectedResponse string condition bool }{ {"green dapr", "hellogreendapr", "green", "Hello green dapr!", true}, {"blue dapr", "hellobluedapr", "blue", "Hello blue dapr!", true}, {"envTest dapr", "helloenvtestdapr", "envTest", "3500 50001", true}, } for _, tt := range helloAppTests { t.Run(tt.testName, func(t *testing.T) { if !tt.condition { t.Skip("Skipped because condition is false") } // Get the ingress external url of test app externalURL := tr.Platform.AcquireAppExternalURL(tt.app) require.NotEmpty(t, externalURL, "external URL must not be empty") // Check if test app endpoint is available _, err := utils.HTTPGetNTimes(externalURL, numHealthChecks) require.NoError(t, err) // Trigger test body, err := json.Marshal(testCommandRequest{Message: "Hello Dapr."}) require.NoError(t, err) resp, err := utils.HTTPPost(fmt.Sprintf("%s/tests/%s", externalURL, tt.testCommand), body) require.NoError(t, err) var appResp appResponse err = json.Unmarshal(resp, &appResp) require.NoError(t, err) require.Equal(t, tt.expectedResponse, appResp.Message) }) } }

同一文件中还演示了平台操作类用例,如tr.Platform.Scale("hellobluedapr", 3)扩容副本、tr.Platform.Restart("hellobluedapr")重启 Pod,可用于验证运行时对扩缩容与重启的响应。

Test Runner 内部:应用生命周期如何被托管

TestRunner.Start() 是生命周期管理的核心,其执行顺序清晰可循:

  1. tr.Platform.Setup()初始化测试平台(如校验 kubeconfig、准备命名空间);
  2. AddSecrets()创建测试所需的 Kubernetes Secret;
  3. AddComponents()安装 Dapr 组件;
  4. AddApps(initApps)部署初始化应用;
  5. AddApps(testApps)部署正式测试应用(含 Sidecar 注入与就绪等待,等待逻辑位于 tests/platforms/kubernetes/appmanager.go,轮询间隔 1 秒、超时 8 分钟);
  6. m.Run()执行所有TestXxx用例;
  7. defer触发的TearDown()统一清理全部资源。

这就是文档所说「不需要自己管理测试应用」的实现依据:应用部署、Sidecar 探测、资源回收都被框架接管。PlatformInterface还提供GetAppUsageGetSidecarUsageGetTotalRestartsPortForwardToApp等能力,可支撑资源观测与端口转发类用例。

第三步:调试与验证 Test Driver

  1. 如果开发环境使用 minikube,请设置DAPR_TEST_MINIKUBE_IP环境变量为minikube ip的输出(对应源码常量定义于 tests/platforms/kubernetes/appmanager.go);
  2. 调试 Go 测试有两种推荐方式:
    • dlv test:例如想单独调试TestHelloDapr

      dlv test --build-flags='-tags=e2e' ./tests/e2e/... -- -test.run ^TestHelloDapr$
    • VSCode + Go 插件(推荐):调试体验更佳;但由于插件暂不支持给构建选项加 build tag,调试期间需要临时移除测试文件中的// +build e2e构建约束(并在调试结束后恢复);

  3. 运行全部 E2E 测试:
make test-e2e-all

该目标定义在 tests/dapr_tests.mk:默认通过gotestsum-p 3并行度运行全部tests/e2e/...包,并输出 JUnit/JSON 报告;其中hotreloadingschedulerjob等共享集群全局状态的包(DAPR_E2E_SERIAL_PACKAGES)会以-p 1串行方式单独跑一遍。若只想跑单个测试,可设置DAPR_E2E_TEST环境变量指定应用目录,例如DAPR_E2E_TEST=hellodapr make test-e2e-all

小结与延伸阅读

回顾整个编写流程:先在 tests/apps/ 下以 hellodapr 为样板实现带/tests/{test}入口的测试应用,通过 make 目标构建推送镜像并用service.yaml人工联调;再在 tests/e2e/ 下编写带e2e标签的 Test Driver,通过AppDescription声明应用、TestRunner托管生命周期,最后用dlv或 VSCode 调试并跑通make test-e2e-all。这套双角色模式让「应用行为验证」与「集群编排」彻底解耦,也是 Dapr 全仓库 40+ 个 E2E 测试包得以并行稳定运行的基础。

想进一步深入,可继续阅读仓库中的相关文档与代码:

  • E2E 测试环境搭建:本地环境与镜像仓库配置
  • E2E 测试基础设施:CI 中如何运行 E2E
  • 集成测试编写指南:另一层级的测试方法论
  • Test Runner 实现:生命周期编排源码
  • AppDescription 定义:应用描述字段全集
  • 测试 Makefile:E2E_TEST_APPS与全部 e2e 目标

【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 12:46:25

多巴胺消失了?从神经科学到成瘾机制,找回行动力与快乐

读《消失的多巴胺》时,我刚从一个“短视频循环”里挣扎出来。那天晚上我发现自己刷了近两个小时的短视频,完全停不下来,脑子却空得发麻。手机屏幕的光映在脸上,我对自己说“再看一条就睡”,但手指压根没停。第二天起床…

作者头像 李华
网站建设 2026/9/12 12:45:39

deepvoice3_pytorch 0.0.1源码解析与端到端语音合成复现指南

简介:这是一份面向深度学习与语音合成开发者的PyPI官方资源包,提供基于PyTorch实现的端到端语音合成框架早期版本。框架聚焦文本到自然语音的转换,整合变声、注意力机制、序列建模等关键技术,覆盖文本预处理、声谱生成到波形重建的…

作者头像 李华
网站建设 2026/9/12 12:45:07

OpenClaw开源项目解析:AI交互稳定性提升方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 12:44:59

抓包工具怎么选?Charles、Wireshark等五款工具对比与实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 12:44:08

Python TTS语音合成技术:原理、实践与部署指南

1. 项目概述:Python TTS语音合成的核心价值与应用场景 TTS(Text-To-Speech)技术正在重塑人机交互的边界。作为从业十年的全栈开发者,我亲历了从机械合成音到如今近乎真人语音的技术跃迁。Python生态下的TTS工具链,特别…

作者头像 李华