欢迎回来,朋友们!今天,我们将在一篇有趣的博客文章中介绍端到端测试。如果您从未编写过此类测试,或者您正在努力改进它们,请继续阅读,我将引导您完成这一激动人心的旅程。在本文结束时,您将了解如何授权使用 testcontainers-go 包,让您的测试套件大放异彩。
在继续之前,让我们为这篇博文设定界限,因为我们将介绍几个概念、工具和技术。
由于我们将在博客文章的其余部分讨论几个主题,因此我觉得将它们放在一起是个好主意。
我在这篇博文中展示的工具是我熟悉的工具和一些我第一次使用的工具的组合。尽量不要不假思索地使用这些工具,而是根据您的场景评估它们。
我们将依赖:
为了避免阅读内容臃肿,我不会涵盖此处介绍的主题的各个方面和方面。我会把相关的文档网址放在需要的地方。
假设我们需要在一个不属于我们的项目上编写端到端测试。就我而言,我想在使用 Java 编程语言编写的项目上编写端到端测试。由于我不知道如何用 Java 编码,所以我的测试选项只是端到端测试。我必须测试的服务是一组 REST API。解决方案很明显:通过发出 HTTP 请求来运用端点。
它允许像黑匣子一样测试暴露的功能。我们只需要处理公共表面:我们发送到服务器的内容以及我们从服务器返回的内容。不多不少。
我们关心 api/accounts 端点,它列出了我们数据库(MySQL 实例)中的银行帐户。我们将发出这两个请求:
HTTP Method | Address | Expected Status Code |
---|---|---|
GET | api/accounts?iban=IT10474608000005006107XXXXX | 200 StatusOK |
GET | api/accounts?iban=abc | 400 StatusBadRequest |
现在,您应该对我们的目标有了更清晰的了解。那么,让我们进入测试代码。
在本节中,我将介绍我们需要为可怕的端到端测试编写的所有相关代码。
由于我们不关心源代码,因此起点是 docker-compose.yml 文件。相关代码为:
services: mysqldb: image: "mysql:8.0" container_name: mysqldb restart: always ports: - 3307:3306 networks: - springapimysql-net environment: MYSQL_DATABASE: transfers_db MYSQL_USER: bulk_user MYSQL_PASSWORD: root MYSQL_ROOT_PASSWORD: root healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 start_period: 30s api_service: build: . container_name: api_service restart: always ports: - 8080:8080 networks: - springapimysql-net environment: - spring.datasource.url=jdbc:mysql://mysqldb:3306/transfers_db - spring.datasource.username=bulk_user - spring.datasource.password=root depends_on: mysqldb: condition: service_healthy volumes: - .m2:/root/.m2 networks: springapimysql-net:
文件内容非常简单。我们可以总结以下列表中定义的内容:
有关更多 Docker Compose 参考,您可以查看此处。现在,让我们看看端到端的测试代码。
ginkgo 测试框架帮助我们构建测试套件。它完全是用 Go 编写的。此外,它还提供了一个 CLI 实用程序来设置和运行测试。因为稍后我们会用到它,所以我们从这里下载它。您可以通过两种方式下载:
要检查您的计算机上是否有可用的实用程序,您可以运行命令 ginkgo version (在撰写本文时,我的版本是 2.20.2)。
请注意,ginkgo 命令不是运行测试所必需的。您仍然可以通过坚持 go test 命令来在没有此实用程序的情况下运行测试。
但是,我强烈建议下载它,因为我们将使用它来生成一些样板代码。
位于根目录中,让我们创建一个名为 end2end 的文件夹来托管我们的测试。在该文件夹中,通过发出命令 go mod init path/to/your/module 来初始化 Go 模块。
现在,是时候运行 ginkgo bootstrap 命令了。它应该生成一个名为 end2end_suite_test.go 的新文件。该文件会触发我们稍后定义的测试套件。
此方法类似于 testify/suite 包的方法。由于定义和运行阶段是分开的,它增强了代码的模块化和鲁棒性。
现在,让我们将测试添加到我们的套件中。要生成我们的测试所在的文件,请运行另一个 ginkgo 命令:ginkgogeneratecounts。这次,文件accounts_test.go 弹出。现在,让我们保持原样并切换到终端。我们通过运行 Go 命令 go mod tidy 将缺少的依赖项下载到我们的计算机本地来修复丢失的软件包。
让我们从测试套件的入口点开始。文件内容看起来很整洁:
//go:build integration package end2end import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) func TestEnd2End(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "End2End Suite") }
唯一不寻常的事情可能是导入部分中的点导入。您可以在此处的文档中阅读有关它的更多信息。
在某些时候,我们需要一些魔法才能进入下一个测试级别。恰好是 testcontainers-go。为了这个演示,我们使用 compose 模块(更多参考,请参阅此处)。
该工具可以运行我们之前看到的 compose 文件,并对正在运行的容器执行端到端测试。
这是 testcontainers-go 功能的摘录。如果您想了解更多信息,请参阅文档或联系我们。我很乐意带您了解其令人惊叹的功能。
这个包允许使用单个命令运行端到端套件。这是运行这些测试的更加一致和原子的方式。它让我能够:
以这种方式编写代码可以帮助您避免处理 docker cli 命令和 makefile 的麻烦。
现在,让我们看看我们的测试所在的代码。
//go:build integration package end2end import ( "context" "net/http" "os" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" tc "github.com/testcontainers/testcontainers-go/modules/compose" "github.com/testcontainers/testcontainers-go/wait" ) var _ = Describe("accounts", Ordered, func() { BeforeAll(func() { os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") composeReq, err := tc.NewDockerComposeWith(tc.WithStackFiles("../docker-compose.yml")) Expect(err).Should(BeNil()) DeferCleanup(func() { Expect(composeReq.Down(context.Background(), tc.RemoveOrphans(true), tc.RemoveImagesLocal)).Should(BeNil()) }) ctx, cancel := context.WithCancel(context.Background()) DeferCleanup(cancel) composeErr := composeReq. WaitForService("api_service", wait.ForListeningPort("8080/tcp")). Up(ctx, tc.Wait(true)) Expect(composeErr).Should(BeNil()) }) Describe("retrieving accounts", func() { Context("HTTP request is valid", func() { It("return accounts", func() { client := http.Client{} r, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/accounts?iban=IT10474608000005006107XXXXX", nil) res, err := client.Do(r) Expect(err).Should(BeNil()) Expect(res).To(HaveHTTPStatus(http.StatusOK)) }) }) Context("HTTP request is NOT valid", func() { It("err with invalid IBAN", func() { client := http.Client{} r, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/accounts?iban=abcd", nil) Expect(err).Should(BeNil()) res, err := client.Do(r) Expect(err).Should(BeNil()) Expect(res).To(HaveHTTPStatus(http.StatusBadRequest)) }) }) }) })
乍一看,似乎很难消化。为了让事情变得更简单,让我们把它分成更小的部分。
Describe 容器节点只不过是一个包装器,用于保存我们套件的相关代码。一切都必须生活在其中。它是脚手架代码的一部分:var _ =Describe("accounts", Ordered, func() {}。在 {} 中,您应该放置所有相关代码。要强制使用设置节点(像BeforeAll一样),我们必须将Describe容器定义为Ordered。
如果您忘记添加它,请不要担心,因为 Go 编译器会抱怨。
我们继续吧。
这个节点允许我们提取通用的设置逻辑。此代码部分在套件内的测试之前执行一次和。让我们回顾一下正在做的事情:
我简化了测试代码,因为我不想让这篇博文变得更长?.
测试函数位于描述容器节点中。您可以参考这里了解ginkgo是如何处理测试规范的。描述节点允许您根据测试范围对测试进行分组和组织。您可以将此节点嵌套在其他描述节点中。
Describe节点嵌套越多,测试范围就越窄。
然后,我们就有了限定父描述的上下文容器节点。它限定了测试有效的情况。最后,我们有 It 部分,即 Spec 主题。这是我们正在执行的实际测试,并且是层次结构树的叶级别。测试代码是不言自明的,因此我将跳转到运行测试的部分。
恭喜?我们设法到达这里。现在,我们只错过了试运行操作。眨眼间,我们的测试执行报告就会打印到终端上。
让我们切换到终端并运行命令 ginkgo --tags=integration -v。一段时间后,您将看到终端上打印的输出。
我知道这篇博文中浓缩了很多内容。我的目标是提供有关如何编写良好的测试套件的见解和方法。您可能希望使所提供的工具、包和技术适应其他类型的测试或用例。
在离开之前,我想强调一下 testcontainers-go 包的 compose 模块的另一个优点。
如果您坚持使用我提供的配置,您一定会使用最新的 Docker 镜像,并且可以避免由于使用过时的镜像而花费数小时进行故障排除。它类似于命令 docker compose build --no-cache && docker compose up。你会感谢我吗?
感谢各位朋友的关注!如果您有任何问题、疑虑、反馈或意见,我可以一起倾听和发言。如果您希望我介绍一些具体概念,请与我联系。下次再见,保重,再见?
以上是通过 testcontainers-go 和 docker-compose 来利用您的测试套件的详细内容。更多信息请关注PHP中文网其他相关文章!