
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/daprDapr 仓库的端到端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 就是最合适的样板代码。创建步骤在 tests/apps/ 下新建测试应用目录从 tests/apps/hellodapr/ 复制样板文件注意当前仓库中 Dockerfile 统一放在 tests/Dockerfilehellodapr 目录内保留app.go与service.yaml两个文件app.go用 Go 编写的简单 HTTP 服务器service.yaml供 kubectl 使用的测试应用部署清单仅用于开发调试实际 E2E 测试不会使用它部署修改app.go加入你自己的测试逻辑本地运行 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_timeUnix 毫秒时间戳便于测试端做耗时断言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_PORT与DAPR_GRPC_PORT可用于验证 Dapr Sidecar 是否正确注入、端口是否就绪——这是验证 Sidecar 注入类用例的典型手法。调试与验证 Test App将新测试应用目录名加入 tests/dapr_tests.mk 的E2E_TEST_APPS变量通过 make 构建并推送测试镜像需要预先配置DAPR_TEST_REGISTRY和DAPR_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 ...)自动展开为同名目标。修改service.yaml更新 metadata 与镜像仓库/镜像名镜像命名约定为e2e-[测试应用名]如 tests/apps/hellodapr/service.yaml 中的dapriotest/e2e-hellodapr部署到集群做联调kubectl apply -f service.yaml获取外部 IPkubectl get svc用 wget / curl / Postman 验证外部端点删除测试应用kubectl delete -f service.yamlservice.yaml值得注意它通过dapr.io/enabled: true、dapr.io/app-id、dapr.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 唯一。创建步骤在 tests/e2e/ 下新建测试目录基于下述骨架创建 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 RunnerTestMain()中定义整个测试文件会用到的应用数组然后创建 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应用监听端口默认 3000DaprEnabledbool是否注入 Dapr SidecarImageNamestring镜像名约定e2e-[应用名]SidecarImagestring指定 Sidecar 镜像用于版本偏斜测试RegistryName/ImageSecretstring私有仓库与拉取凭据Replicasint32副本数IngressEnabledbool是否暴露 LoadBalancer/NodePort 外部端点IngressPortint外部端口缺省用 AppPortMetricsEnabledbool控制dapr.io/enable-metrics注解Configstring关联的 Dapr Configuration CRDAppCPULimit/AppCPURequest/AppMemoryLimit/AppMemoryRequeststring应用容器资源限制与请求DaprCPULimit/DaprCPURequest/DaprMemoryLimit/DaprMemoryRequeststringSidecar 容器资源限制与请求AppEnvmap[string]string注入到应用的环境变量IsJobbool是否以 Job 形式部署EnableAppHealthCheck/AppHealthCheckPath等bool/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.HTTPGetNTimes按numHealthChecks次轮询探活、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() 是生命周期管理的核心其执行顺序清晰可循tr.Platform.Setup()初始化测试平台如校验 kubeconfig、准备命名空间AddSecrets()创建测试所需的 Kubernetes SecretAddComponents()安装 Dapr 组件AddApps(initApps)部署初始化应用AddApps(testApps)部署正式测试应用含 Sidecar 注入与就绪等待等待逻辑位于 tests/platforms/kubernetes/appmanager.go轮询间隔 1 秒、超时 8 分钟m.Run()执行所有TestXxx用例defer触发的TearDown()统一清理全部资源。这就是文档所说「不需要自己管理测试应用」的实现依据应用部署、Sidecar 探测、资源回收都被框架接管。PlatformInterface还提供GetAppUsage、GetSidecarUsage、GetTotalRestarts、PortForwardToApp等能力可支撑资源观测与端口转发类用例。第三步调试与验证 Test Driver如果开发环境使用 minikube请设置DAPR_TEST_MINIKUBE_IP环境变量为minikube ip的输出对应源码常量定义于 tests/platforms/kubernetes/appmanager.go调试 Go 测试有两种推荐方式dlv test例如想单独调试TestHelloDaprdlv test --build-flags-tagse2e ./tests/e2e/... -- -test.run ^TestHelloDapr$VSCode Go 插件推荐调试体验更佳但由于插件暂不支持给构建选项加 build tag调试期间需要临时移除测试文件中的// build e2e构建约束并在调试结束后恢复运行全部 E2E 测试make test-e2e-all该目标定义在 tests/dapr_tests.mk默认通过gotestsum以-p 3并行度运行全部tests/e2e/...包并输出 JUnit/JSON 报告其中hotreloading、scheduler、job等共享集群全局状态的包DAPR_E2E_SERIAL_PACKAGES会以-p 1串行方式单独跑一遍。若只想跑单个测试可设置DAPR_E2E_TEST环境变量指定应用目录例如DAPR_E2E_TESThellodapr 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 定义应用描述字段全集测试 MakefileE2E_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),仅供参考