Welcome back, folks! Today, we will cover the end-to-end tests in an intriguing blog post. If you've never written these kinds of tests or if you strive to improve them, keep reading as I'll walk you through this exciting journey. By the end of the article, you'll know how to empower the usage of the testcontainers-go package to let your test suite shine.
Before moving ahead, let's set the boundaries for this blog post since we will cover several concepts, tools, and techniques.
Since we'll touch on several topics throughout the rest of the blog post, I feel it's a good idea to put them together here.
The tools I present throughout this blog post are a mix of tools I know well and some I used for the first time. Try not to use these tools without thinking, but evaluate them based on your scenario.
We're going to rely on:
To avoid bloating the reading, I won't cover every aspect and facet of the topics presented here. I will put the relevant documentation URLs where needed.
Let's assume we need to write end-to-end tests on a project we don't own. In my case, I want to write end-to-end tests on a project written with the Java programming language. Since I didn't know how to code in Java, my testing option was only end-to-end tests. The service I had to test was a set of REST APIs. The solution has been obvious: exercise the endpoints by issuing HTTP requests.
It allows testing the exposed features like a black box. We only have to deal with the public surface: what we send to the server and what we get back from it. Nothing more, nothing less.
We care about the api/accounts endpoint that lists the bank accounts in our database (a MySQL instance). We're going to issue these two requests:
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。你会感谢我吗?
感谢各位朋友的关注!如果您有任何问题、疑虑、反馈或意见,我可以一起倾听和发言。如果您希望我介绍一些具体概念,请与我联系。下次再见,保重,再见?
The above is the detailed content of Leverage Your Test Suite With testcontainers-go & docker-compose. For more information, please follow other related articles on the PHP Chinese website!