Quarkus Insights #1:测试

周一,我们直播了Quarkus Insights #1: 测试,Georgios 展示并讲解了所有与测试相关的内容。特别展示了新的 Mockito 集成和 TestContainer 的使用示例。

他展示的代码在https://github.com/geoand/quarkus-test-demo,下面是文字记录(感谢 Markus!)。

下一集

5月12日星期二欧洲中部时间11:15 / 印度标准时间14:45 / 东部时间05:15,我们将与Stuart Douglas一起进行Quarkus Insights 第2集:quarkus:dev & 命令模式

这次的时间对欧洲/亚太地区更为友好——任何从美国出现的人都将因起得太早而受到表扬!

我们将讨论如何使用`quarkus:dev`和新的命令模式——了解Stuart,我们还将深入探讨Quarkus的内部机制。

请提前向 YouTube 视频添加问题,或者在直播节目中出现时提问——我们会尽量涵盖!

要收看当前的直播或查看即将安排的直播,请收藏此链接:https://youtube.com/quarkusio/live

文字记录

以下是本集的文字记录。

感谢 Markus Eisele 的付出!

Emmanuel: Quarkus Insights 第一期。测试。

大家好。我们现在预算紧张。

好的,所以在这场疯狂中我并不孤单。欢迎收看新一集 Quarkus Inside。这是一期正式的节目,因为这不是一个元主题。我们今天将围绕测试展开真正的对话,为此我请来了我的同事兼犯罪伙伴 Max,还有 Georgios,他对 Quarkus 做出了巨大贡献,无处不在,所以 Georgios,你想介绍一下自己吗?

Georgios: 是的,谢谢,谢谢你 Emmanuel。

是的,我叫 Georgios,或者 George,如果用英文拼写的话。我在 Red Hat 工作了几年,但当我开始 Quarkus 的优秀同事们在内部真正宣布它时,我的生活真正改变了,我当时想,天哪,我一定要加入这个项目。这超出了我所能想象的一切。所以,是的,我开始贡献 Spring 相关的嵌套内容,然后基本上就从那里开始了。现在就像你说的,左右开弓。

Emmanuel: 所以我想代表Quarkus的优秀团队说一句:“感谢上帝,Georgios在这里”,因为我们遇到的每一个麻烦你都跳出来帮忙。这是一次非常棒的体验。

Georgios: 非常感谢。

Emmanuel: 好的,Max,在我们……在开始之前你想说几句吗……

Max: 是的,几件事。我希望我已经修复了我的声音,而且 Emmanuel 应该已经停止发送我的声音两次了。

Emmanuel: 是的。

Max: 那就不那么奇怪了。

另一件事是,实际上,在周末,我们的频道实际上从8-900涨到了现在超过一千,这很酷。

是的,所以我们需要这样做。周末我们还有一件事:Tim Fox 开始了一个关于如何发音 Quarkus 的帖子,因为那是一个很长的帖子。所以我想知道你们是怎么发音的。我的发音是 Quarkus。

你呢,Emmanuel?

Emmanuel: 对我来说,它就像“Spartakus”或者 Quarkus。但我法国人,谁知道呢。

Georgios: 我当时说,Quarkus。

Emmanuel: 是的,你们发送了1.02k条信息?这就是 Tim Fox 的讨论串。我喜欢英国人之间讨论发音,或者可以……是的,我总是很喜欢。

Max: 那是 tomato tomato, data, data。是的,整个事情。无论如何,我接受 Quarkus 的所有发音,没关系。

Emmanuel: 是的,就是这样。你知道最糟糕的是 Cylon。这个真的很难,所以我们为 Quarkus 做得更好……好的。

Max: 我最后要说的是:我们发布了一篇文章,就像一篇带有视频的博客,那是我们上次做的,我们还在里面添加了文字记录,我认为这很好用,就像上次一样,我们今天没有谈论技术,希望乔治会告诉我们所有关于测试的好东西。所以它可能会更有用。如果有人想说这很有用或者我们应该继续这样做的话!所有这些都由 Markus Eisele 完成。他自愿做的。所以非常棒。它实际上也是字幕。

所以如果你去看,并且听不懂 Emmanuel 的法式英语或者我含糊不清的丹麦语。现在有字幕了!

Emmanuel: 你是指这里有文字记录,视频里有字幕吗?

Max: 是的,如果你去看视频,有一个是谷歌识别的,我只是为了娱乐价值把它留在那儿了,我……

Emmanuel: 我去哪里,我想是视频……

Max: 是的,你点击视频,然后你会看到……角落里的 CC。所以你只需将文本粘贴进去,然后谷歌就会找出时间戳。我印象深刻……好吗?如果时间……是人们喜欢的话会很好……

Emmanuel: 无论如何,这就是这里的东西的制作。收到我们的东西是……是的,好了,我们准备好了。我们可以分享 Georgios 的屏幕了,但是今天我们要讨论 Quarkus 中的测试。所以它是一个 Java 框架,所以我只需要用 JUnit 就可以开始了吗?还是会更复杂一点?

你们觉得呢?

0:05:35 S1

Georgios: 嗯,就像生活中的所有事情一样。我认为它有点复杂但又没那么复杂,没那么复杂。如果你是一个普通的企业 Java 开发人员,完全没问题,它使用了你已经知道的所有东西。所以,是的,你可能需要使用一些新的注解或者其他什么,但你只需要选择你已经知道的常用库,我们会展示 QuarKito,我们会展示 WireMock,AssertJ。所以一切都很好,你只需要了解这些如何与 Quarkus 生态系统配合,除此之外,一切都好。

Emmanuel: 顺便说一下。这里,不是关于测试,但是 John 说,“嘿,你如何发音‘pronunciation’?”;]

所以它在法语中是“prononciation”,所以你不用担心……但是,更严肃地说,如果你有问题,我们正在和 Georgios 讨论测试,所以我们要假装我们在……嗯,其实不是假装,但我们会向 Georgios 提问,如果你自己有问题,就发到评论里,我们会选出一些精选问题,然后你懂的,希望 Georgios 能给出答案。

我想说的是,如果你想进行一个不依赖基础设施的纯单元测试,你可以直接使用 JUnit 或你喜欢的任何框架。我想我们得说,我们已经确定 JUnit 5 作为默认的测试框架,所以我们建议你将其用于 Quarkus,但是 Quarkus 有一个有趣的模式,它启动速度足够快,或者说真的很快,因此我们可以在你的每个测试中运行整个应用程序,然后你就可以在真实生活场景中运行,对吗?

Georgios: 是的。

Emmanuel: 你想让我分享你的屏幕吗?你来展示一下?

Georgios: 是的,你觉得怎么样就怎么样。

Emmanuel: 好的,当然,我们开始吧。除非你们对测试还有更多的介绍,或者你们想……

Max: 不,听起来不错!

Georgios: 不,我很好。

Emmanuel: 好的,就是这样,是的,我用 Skype。所以这是一个 Quarkus 应用程序,对吗?

Georgios: 是的,这是一个我用 Maven 工具生成的 Quarkus 应用程序,所以我没有做任何更改,只是创建了一个 Quarkus 应用程序并在 IntelliJ 中打开了它,我将在此次演示中全程使用它。[听不清,零散] 那么你想谈谈这两个生成的测试是什么吗?

Emmanuel: 是的,我们从第一个开始吧。

Georgios: 是的,明白了,好的,很酷。正如 Emmanuel 所说,当我们使用 JUnit 5 和 Quarkus 时,我们与 Quarkus 的集成,它所做的就是启动 Quarkus 本身。整个 Quarkus 应用程序。我们设想就像你运行实际的、访问你的应用程序使用的实际端点一样,当然你还可以做很多其他事情。但这就像我们认为它被使用的主要方式。所以在这里你看到 Quarkus 测试是我们与 JUnit 5 的集成点,这就是你所需要做的,只需在测试内部启动你的 Quarkus 应用程序。这里就像你只是编写你的常规测试一样。我们在这里使用 REST-assured,Quarkus 开箱即用地支持它,所以你正在访问 Hallo 端点,并期望它……

Emmanuel: 顺便说一下,你不必这样做,但我认为我们立即集成了 Quarkus 应用程序运行的端口和主机与 REST-assured。这对于你来说是一种完全透明的体验。

Georgios: 没错,是的,如果我们没有开箱即用地集成,你就必须在这里选择。你必须指定端口,比如 8081 或其他。是的,我们开箱即用地做到了。所以这种体验完全没有摩擦。

Emmanuel: 哦,说到……所以你把端口设为8081是有原因的。Quarkus 的默认端口是8080,我们就是这样启动的。

所以从 WildFly 甚至更早的时候开始……但是对于测试,我们决定将端口提高一个,这样你就可以在应用程序运行时同时运行测试,对吧?

Georgios: 是的,没错,所以我们运行常规应用程序的默认端口是8080,但你运行测试是8081。所以,我们设想……你可以在其中做多件事……开发模式也运行在8080上,所以你可以让你的开发模式运行,然后你的测试独立运行。但这只是我们必须这样做才能让事情运行起来的方式。

Emmanuel: 我能改吗?

Georgios: 是的,当然。你到 Quarkus 这里,嗯,我的……IntelliJ Ultimate 还没有属性补全功能。

我知道 Max 正在处理,但也有一个社区插件,但我还没有安装……不过,http 端口会是 8080,比如说 9090 给常规的。

Max: 只是澄清一下:IntelliJ,如果你安装了社区 Quarkus 插件,你应该有……

Georgios: 是的,是的,是的,没错。我还没安装,但是 IntelliJ Ultimate 开箱即用没有,我也没有安装,所以,如果我想改变端口,比如 8082,我只需要设置 `quarkus.http.test-port`。就像 Emmanuel 经常说的那样,我认为这是一个很棒的决定,Quarkus 中的一切都通过 `application.properties` 配置,所以我们使用的大量框架和库都以这种方式控制。所以你只需要学习一组属性,你就可以找出所有属性,比如你去 Quarkus……即使我没有 IDE 集成,但是如果我去我们网页上的 http 参考,我们可以看到所有属性是什么,或者不在这里,也许像 Quarkus 测试,我通常只是打开源代码。无论如何,我现在不确定它们具体在哪里。如果你记得会很棒,但通常……属性就在网站上,所以你可以准确地看到你实际的……

我没有 IDE 集成,但如果我访问我们网页上的 http 参考,我们可以看到所有的属性是什么,或者不在这里,也许像 Quarkus 测试,我通常只是打开源代码。无论如何,我现在不确定它们具体在哪里。如果你记得会很棒,但通常……属性就在网站上,所以你可以准确地看到你实际的……

0:12:24 S1

Emmanuel: 你可以告诉我,让我来……有一件事我很喜欢,而且我不得不承认我当时真的非常抗拒,但我认为最好的解决方案是每个指南都列出其属性。所以如果你去……抱歉,我的网络似乎有点问题,所以如果我到配置数据源这里,在某个时候,你会看到,这是我如何配置数据源的,你甚至可能在某个地方,我想,在最后,有一个完整的列表?

是的,你看,你得到一个完整的页面,这里我们做了两件事。所以第一件事是这里的列表实际上是从我们在这个特定扩展中拥有的属性列表中生成的。你可以在这里过滤东西。所以如果我只寻找健康检查,那它就不工作了。

不知道发生了什么……是的,他们走了。所以你得到了属性列表的过滤功能,这也很有用,如果你有一个很大的属性列表,但另一个有用的功能是如果你回到 quarkus.io/guides,这里的第二个。所以,第一个是,“嘿,我怎么继续我的应用程序?”,但第二个是,“给我所有可能的引用配置”。

这里你有一个庞大的属性列表,你可以非常快速地搜索一些东西。所以是的,如果我查找,我不知道,SQL,因为我想要 Hibernate 的查找方式……我想我应该用……这里有点太多了,但我应该能够以某种方式足够快地找到……那里,Hibernate ORM,然后你看到我如何获取日志?

所以日志创建者会更好。

所以这是一个很好的方法来获取所有属性并非常快速地筛选它们。我当时是反对的。但是既然我们已经自动化了,我们说,嘿,为什么不呢?它实际上是一个非常好的解决方案。所以,现在回到 Georgios……

Georgios: 是的,谢谢。是的,我正在找那些。但,是的,没错。我们设想人们会更多地通过 IDE 集成来找到它,比如 VS Code 开箱即用就有了。

那么,我们来运行第一个测试吧?对于我的需求,它应该能正常工作。我直接在这里运行它。

它会启动 Quarkus 应用程序,并实际对 Hello 端点执行 HTTP 请求。所以如果我在这里把它改为 below,那么我的测试显然会失败,或者我的测试会失败,因为我期望的是 "hello",这就是 REST-assured 的全部内容。所以,REST-assured 的集成是开箱即用的。

这是一个普通的 JVM 应用程序。当我们编写 Quarkus 测试时,现在我们有另一种测试,我们称之为“原生镜像测试”。现在,它的作用是让您能够构建 GraalVM 原生镜像并对其运行测试。所以它就像一个……这里就一个快速的,我可以运行,我可以构建测试,然后两者都可以。是的。

我还没有配置 IntelliJ 来正确地找到这里的所有东西,但是如果我运行 `mvn -verify -pNative`,它在运行读取资源测试之后,会使用 Maven fail-safe 运行这个测试,如果它是一个实际的集成测试,并且会构建 GraalVM 本机映像,就像我们在这里看到的。

一旦它构建完成,它就会启动它并执行,在这种情况下,它会执行相同的 REST 端点;对原生镜像的 REST 端点执行相同的 HTTP 请求。

Emmanuel: 是的,所以如果你不确定原生镜像,这就是你进行额外冒烟测试的方法,以确保原生镜像与实际的 Java 代码相比没有出现奇怪的行为。

Georgios: 没错……是的,就是这样……我们认为它是一种黑盒测试,你只能测试应用程序中可以从外部访问的部分,比如 http 或消息传递,或者其他类似的东西。

Emmanuel: 顺便说一句,你说你忘记在 IntelliJ 中配置你的东西。我想那是导出 GraalVM home 这样的属性……

Georgios: 是的,没错。

Emmanuel: 所以你编辑你的测试并设置一个环境变量,就是 GraalVM home,它会指向原生镜像生成器,忘了它的名字了。

Georgios: Native,是的,原生镜像,是的。所以是的,没错……我在这里配置它,然后我会搞清楚,然后一切都会正常工作,但我现在没有这样做,不过没关系。

Emmanuel: 收到一个问题来自 ____。我们试着从中提取一些东西,“伙计们,请制作一个关于测试驱动开发以及控制器、服务和仓库的 POST 请求的指南,并在这些视频中展示。谢谢你们的帮助,我的 POM 中有 coffee,但注入更多类型会返回一些错误。我不确定注入 mock。哦,我想我们稍后会讨论整个 mock 的问题。也许现在是深入讨论的好时机。”

Georgios: 当然,当然,是的,听起来不错。

Max: 我确实认为这里的关键是 POST 部分。我认为这里的关键是 POST 部分,我猜,因为 GET 部分很容易,更多的是 POST,到……

Georgios: 是的,好吧,POST 流不是 Quarkus 特有的,你只需要使用你的 REST assured,你只需配置 REST assured 测试来做任何它需要用 POST 做的事情。

此外,我们也可以这样做。是的,我们稍后可以这样做,但我们还应该提到的是,我们的快速入门已经实现了大部分这些内容。所以查看 Quarkus 快速入门是一个很好的方式,可以看到我们设想的正确做法或者有很多例子。所以在这里:quarkus.io。快速入门,我们这里有所有的快速入门。如果我搜索“POST”,我很确定会找到一些东西。

是的,所以它会在这里,就像这里的验证,你可以看到如何进行 POST 的例子。所以它在这里,它只是 REST assured,没有任何 Quarkus 特定的东西。但是,是的,我们可以试一下。

那么,我们如何转向一些……

Emmanuel: 我们收到了 Recardo 的一个问题……有没有一种方法,在测试运行 Quarkus 应用程序时,可以自动化依赖系统的引导,我想这会与测试容器以及如何集成它产生很好的联系。所以无论你何时想跳到那里,我们都可以……

Georgios: 是的,我不太确定 Recardo 在这里是什么意思,因为它在 Quarkus 空间中可能意味着各种事情。

Emmanuel: 我想它不是应用程序内部的依赖系统,我们将看到你如何进行 mock,这是一种绕过它的方法,但我假设他想启动一个外部数据库。

Georgios: 是的,是的,我们会展示的。是的,我们肯定会展示的。那是我们真正想涵盖的测试容器的一个点。

所以我们从一个我们经常听到的东西开始。

很多人都想模拟 REST 客户端,因为我们想开始使用 REST 客户端,然后开始测试它。

REST 客户端超级有用,因为每个微服务迟早都要进行 HTTP 请求,所以每个人最终都会使用 REST 客户端。

让我们开始创建一个示例。REST 客户端,我们想做。我想有一个……我将访问一个外部国家服务,所以我想做的第一件事是创建一个国家,比如说,DTO,我给它两个字段“name”和“capital”。

好的,我来使用 JSON B,所以导入,因为我们支持 JSON B 和 Jackson。两者都用于 Marshalling 和 Unmarshalling JSON,我只是在这里使用它,没有特殊原因。那么,我们来设置……我能做什么?

Max: 你可以使用 add-extension 来完成吗?

Georgios: 是的,我可以使用 mvn add-extension。但既然我已经在 IDE 中了,我就直接复制粘贴到这里。

所以,我只是在做……

Emmanuel: 这只是意味着添加一个扩展实际上就是在 POM 中添加一些东西,所以……

Georgios: 是的,没有魔法,是的。所以我们有工具来自动化这个。

所以每个人的工作流程都不同。我的工作流程是,对我来说,复制粘贴到 pom.xml 中更容易。

其他人可以做得大不相同。

Emmanuel: 是的,我以前用 X 开发,那你呢……

Georgios: 所以,不,我一直都是 IntelliJ 的粉丝。那么,我们设置了 DTO,现在它实际上已经设置好了,REST 客户端会是什么样子。所以,它是一个国家服务,所以 REST 客户端被声明了。整个想法是,你声明一个接口,然后 Quarkus 会通过生成使用 http 请求的代码来自动为你实现该接口。所以我们在这里要做的是,我们将访问一个外部服务——我们将尝试使用一个外部服务。我将使用这个 REST 国家服务,对吗?

____ 我只使用一个端点,就是 name 端点,正如你在这里看到的,我想访问的是这个 REST v2 main name,所以这就是我将在这里尝试设置的。

国家导入…​ get by name…​ String name…​ 是的,这会是一个 GET,正如我在这里看到的。嗯,它没有说,但它无论如何都是一个 GET,并返回一个 application/JSON,路径将看到我正在使用的国家服务的整个路径,版本是 v2,正如我们在这里看到的。

还有 ____ 类的路径是 name as ____ 所以它就设置好了。我只需复制粘贴 URL。所以就像我们之前说的,我们可以通过应用程序属性配置很多东西。既然我想在这里输入整个内容,这就是你需要配置的。所以基本上当你构建一个 REST 客户端时,Quarkus 正在构建一个 REST 客户端,它需要知道基本 URL 是什么,这就是我们设置基本 URL 的方式,所以这是类的名称。

哦,你说这是我们用来配置基本 URL 的属性的后缀。所以基本 URL 是 ____。

现在,我们该如何调用,我们应该如何使用它呢?嗯,我只是将其公开为一个 REST API,所以这里不构建任何业务逻辑,我们只是填充 REST 端点,它会委托给 REST 客户端来访问 ____ 的 REST 端点。

我不知道 Emmanuel,你对这个在 Spring 世界中应该怎么称呼有什么看法。我们称之为控制器。我想,在 Quarkus 中,我们一直在使用资源(resource),但我知道和 Stephane 聊过,他不太喜欢这个名字。所以,我想知道你们的看法。

Emmanuel: 我没有什么意见。

Georgios: 你呢,Max?

Max: 对我来说,控制器是我用过的东西。

Emmanuel: 是的,我想我在玩 Play 的时候用过控制器。

Georgios: 哦,好的,Play 就是这样做的,好吧。来了。所以我们正在将国家服务接口注入到……我们称之为资源,但是当一个伙伴在 REST 客户端 CDI 限定符中创建时。所以当我们注入时,我们也必须使用那个限定符,这样做是因为你可能想创建自己的,但在这种情况下,我们只是使用限定符来消除歧义,如果存在多个服务,多个国家服务的实现。所以我只是把它复制到这里,然后我们看看,这是 GET……然后我会说,路径名 - name 和 ____。

好的,现在我所做的是设置一个资源,它会访问 REST 客户端,但现在我想测试它,对吧,所以我只是……我将创建一个新测试。

我将说常规国家资源测试将是一个 Quarkus 测试。我将把这个复制到这里。

所以我基本上要做类似的事情就是测试 ____。所以 ____。

Emmanuel: ____。

Georgios: 不,我们已经脱离国际货币基金组织了。

Emmanuel: 好吧,每个人都有过。

Georgios: 是的,我同意 ____ 远离我们。所以现在我要做的是,我将尝试展示我期望它返回什么。所以这只是标准的 rest assured 语法,所以这里的大小我期望它是一,因为当我访问 REST 服务时,它只会给我一个。如果我添加了像 ____,它会返回更多的结果,然后我会说,第一个结果的名字是,“希腊”。

Max: ____ 路径,对吗?

Georgios: 是的,是的,它是 ____。Rest assured 使用 Jetty。

Emmanuel: 让我问读者一个问题:“有没有人知道我们为什么用《星球大战》的音乐开始视频?你可以继续……有人会明白的。”

Georgios: 好的,现在我在浏览器中启动测试……啊,我们这里犯了个错误,我想我犯了个错误。

我没有在 REST 客户端上添加 JSON 吗?JSON V 找不到 MessageBodyWriter……啊……显然我犯了个错误。Then produces……是的,我想这里我也犯了个错误。我没有……你本应该发现它返回 JSON 的。

Max: 错误是什么?

Georgios: 错误是它告诉我无法返回八位字节流,因为我没有 `produces` 注解。所以默认情况下它使用八位字节流,但它无法识别。

Emmanuel: 五月四日,所以我们才有那个声音……

Georgios: 哦,我又犯了个错误,我没有在这里添加 getter。

Emmanuel: 当你在搞清楚的时候,Antony 有一个问题。“嘿,伙计们,我在 Panache TTbase 中找不到已保存的更新 ____,怎么回事?”

所以这是一个玩笑,因为 Antony 是 Hibernate 的长期贡献者和团队成员,之后他转向了更黑暗的支持领域,然后是培训和认证。但这是一个非常好的问题,它比人们想象的更复杂,有一个未解决的问题,但我想我们把这个问题留到以后在一个特定的 Panache 演示中讨论会更好。

Georgios: 是的,你可能得把 Stephane 叫过来,让他详细描述 Panache 的所有细节。

Max: 那么,关于构造函数,有一个新问题……

Emmanuel: 所以,是的,将 REST 客户端注入构造函数而不是实例变量有什么区别吗?

Georgios: 不,没有。

我只是想展示一下,因为我想展示……因为在 CDI 世界或 Java EE 世界,无论你怎么称呼它。很多人不使用构造函数注入。那是 Spring 世界的人的看法,他们认为,“哦,JavaEE 没有构造函数注入,还有那些奇怪的东西,但它早就存在了。只是在所有的教程和资料中,你没有看到它被使用那么多。这就是为什么我想在这里强调它只是开箱即用,对吗?”

Emmanuel: 是的,这是一个很好的观点,而且所有这些都在编译时完成,所以没有真正的反射,一切都在编译时搞定。我们生成正确的类来实际填充那些构造函数或 setter 和 getter。

Georgios: 是的,这是一个巨大的差异化因素。

所以回到测试,我实际上应该让它工作起来。断言失败了,它说名字不好,那是我的错,因为我没有 getter,希望现在有了,但我应该让一切都正常工作。

我很确定会的。好了,是的,一切正常。作为一个超级简单的事情,我在这里写了一个 REST 客户端。我验证了它能工作,但我们从每个人那里听到的是:“哦,是的,你知道吗,那很酷,但是……很多时候我想模拟它。我不想访问真实的服务。那是非常,非常。”

Max: 只有两件事:我看到,我想 ____ 或者你不能把 regress 客户端放在这里,对吧?而不是把它放在注入点。

Georgios: 哦,是的,我应该这样做,是的,实际上我通常……是的,我注册了客户端 ____ 你说得对。

Max: 但这等于同一回事,对吧?

Georgios: 我……是的,是一样的,是一样的。实际上,这是一个很好的点,因为为了让我要展示的模拟工作,我将不得不更新这个,也就是……我将不得不把它设为应用程序作用域。我会在我们做的时候解释具体原因。

Emmanuel: 当你这样做的时候,我可以在进入营销之前解释一下。所以在这里,到目前为止,我们的大多数测试都是黑盒测试,所以我们说,“嘿,我们连接到那个 URL 并做一些事情。”但 Quarkus 测试实际上是应用程序内部的一个 bean,所以你可以字面上注入其他 bean,这样你就可以做我称之为“灰盒”测试,你可以注入其中一个 bean 来进行更多的“手术”,决定你想测试什么,然后继续。所以,我想通过副作用,我们将在 mocking 内部展示这一点,但我想先介绍一下,因为即使你不进行 mocking,它实际上也是一个非常有用的功能集。

Georgios: 是的,没错,所以在这里,是的,我们在测试中,如果我把这个 ____ 放在这个国家的服务中,确定有对象和 REST 客户端,那么就像 Emmanuel 描述的那样,如果我这样做,我可以在测试中随意使用它,因为就像我提到的,这是一个 B,所以我们要展示一些不同的东西。所以我们想模拟这个,对吧,所以我们想在……当我做,当我点击 ____,我希望有我自己的东西,而不是访问 REST 服务。所以这里的整个想法是,我添加了 Quarkus JUnit 5 Mockito,它将 Mockito 集成到 JUnit 5 与我们的 Quarkus CDI 东西中。

所以你在这里做什么,当你调用国家服务时,你将使用 `inject mock` 而不是 `inject`,因为我们需要限定符,我们将使用 `REST Client`。就像我说的,同样的原因,我们注入了……我们在注入点使用了限定符,这是另一个注入点,所以我们将使用限定符,但现在我们想控制它,所以这将给我们一个国家服务的模拟,一个我们可以用 Mockito 控制的模拟。

那是最好的部分,哎呀,抱歉……然后你只需要像往常一样使用 Mockito。我很久没有使用 Mockito 了,我一直很喜欢它,从未想过使用任何其他模拟库。

所以我会这样做,就像我用 Mockito 当国家服务 `getByName` 时。我会说,然后返回集合 ____,比如说新的国家,而不是希腊,比如说 ____ 和 ____。所以现在我在这里所做的基本上是国家服务将是模拟对象,它将取代常规的 CDI bean,但仅限于这个任务。这是一个非常重要的部分,这个测试将继续使用常规的外部 URL,当我们在这里访问这个测试时,只有它会使用模拟对象,这正是模拟对象的用途,比如每个测试对应用程序的修改。

所以如果我到这里,我期望得到 ____,我……

Max: 这可能是一个很好的方法,当你运行它的时候。要清楚,`inject mock` 实际上是一个 Quarkus 注解,对吧?

Georgios: 是的,是的,没错。我在这里打开导入,它是 `quarkus.test.junit5.mockito.inject.mock`。现在它失败了,我希望它失败,因为我需要解释为什么这个 `inject mock` 起作用。所以 `inject mock` 作用于正常作用域的 CDI bean。正常作用域的 CDI bean 通常是应用程序作用域或请求作用域。

所以,Microprofile 规范定义,当你像这样创建一个 REST 客户端,并且你没有定义 CDI 作用域时,它是依赖作用域,这意味着在每个注入点,你都会得到这个国家服务的一个新实例,这不允许我们创建……这不允许我们创建一个 mock,因为它已经是一个 JDK 动态代理,Mockito 无法模拟动态代理。所以我们必须做……我们必须把它变成一个应用程序。所以,通过把它变成应用程序作用域,这里的 `inject mock` 就会工作,当我再次运行 `mvn clean test` 时,我应该看到我所有的测试都通过了,而且它们都通过了。

Emmanuel: 某种程度上,我们不能或者 ____ 检测到嘿,这是依赖范围。但是有些测试我们想做模拟。所以我们实际上应该有一种假的、不同的范围。

Georgios: 是的,我有一个……基本上,我们和 Steward 和 Martin 讨论过这个……是的,我们正在考虑的是,我们有一种字节码转换器,当你有这种 `inject mock` 的东西时,它会被使用,并且它会修改国家服务本身的代码,以实际考虑模拟。所以那是我们可能很快就会做的事情……

Emmanuel: 好的,太棒了。我们还有22分钟就结束了,只是一个检查。

Georgios: 好的,很好。关于 mock 还有一件事。所以假设我使用 Mockito,Mockito 默认情况下,当你没有定义任何东西时,会返回一个默认响应。

所以如果我输入像 country France 这样的东西,由于我已经模拟了它,我没有告诉它对 France 做什么。它会返回一个空列表。所以这只是 Mockito 的一个特性,我想让大家知道。

Emmanuel: 我只是……我想向 Igor 澄清一下。关于 mock 的问题,不能被模拟的依赖类型。所以目前的变通方法是在你的服务上使用 `application scope`,这将是目前的变通方法,直到我们在 Quarkus 内部有一个适当的解决方案,但这实际上是可行的,而且它本来就应该大部分是 `application scope`。

Georgios: 是的,没错,是的,它不是的唯一原因是规范规定它应该默认是依赖范围。否则,我就会把它设为应用程序范围。

Max: 那么,我们又有一个。Georgio。Emmanuel,你想回答吗?如何模拟 REST 客户端?

Emmanuel: 我如何模拟 REST 客户端测试对象以抛出 500 异常,即 HTTP 500,但不用 Mockito 及其 Quarkus 注解 @mock 和 Eclipse Microprofile register client?

Georgios: 我不认为你可以……比如说,用 mock 吧?

我不认为你可以模拟它来让它返回 500。嗯,你总是可以用 Mockito 告诉它抛出异常,但通过注解……我想你不能那样做。我没有什么,我想不起有什么,但我会争辩说,如果你要测试那样的事情,你可能想做我现在想做的事情。

哦,那就是实际使用,拥有……不是模拟 REST 客户端,而是模拟外部服务。

所以,我计划现在展示的是,我们继续使用常规的 REST 客户端,但不是使用我之前使用的外部国家 REST 服务,我想启动一个服务,比如说一个代理,它会拦截所有对该服务的请求,在那里,我可以让它返回 500,我可以让它返回 404,我可以让它返回任何我想要的错误,以便测试我实际的集成。所以在我看来,那将是测试 HTTP 错误的正确方法。

Emmanuel: 这也回答了 ____ 抱歉,我把你的名字念错了,那就是……`Inject mock` 对于功能测试来说很棒,但有没有办法验证 REST 客户端上使用了正确的注解,那正是如此。所以你会有你的服务器是一个自定义的,你重定向到那个服务器,然后你可以在一个完全受控的环境中,用 REST 客户端运行属性。

Georgios: 是的,我认为那是测试端到端集成的最佳方式。我就是这么做的。

好的,我们来创建国家服务的测试,这将是一个 Quarkus 测试,我将说 ____。

我说,国家服务,注入客户端。不是 JUnit 断言,我想要 AssertJ 断言,适用于那个国家服务。

____ 听不清的打字声!!____。所以这个测试,我还没有给它添加任何特别的东西。我只是想把它设置好,向你展示,我们在这里要做的是,我们将使用国家服务,但我们将访问一个由我们控制的外部端点。一旦我说我让它运行起来……

我通过了测试……现在,我们将引入一个 Quarkus 特有的概念,然后我们将使用 WireMock 来演示。

就像 Emmanuel 之前说的,我们启动 Quarkus 一次,然后对它运行所有测试。这意味着当你想要在启动 Quarkus 之前做一些事情时,作为开发人员,你需要一个集成点。

嗯,Quarkus 测试资源就是你正在寻找的那个集成点。

所以 WireMock 国家……所以我必须创建这样一个集成点,或者我计划现在做的是创建一个 WireMock 服务器,在那里我模拟,我基本上设置了我关心的端点,而我们的测试实际上将访问那个服务器。我们如何做到这一点?所以我们……我们在这个 Quarkus 测试注解 `test resource` 注解中创建一个实现 `Quarkus test resource lifecycle manager` 的类。

我们关心的事情,我们关心的方法。在这里,或者开始,它基本上是 Quarkus 在启动 Quarkus 应用程序之前调用回 Quarkus 测试的方法,它返回的是一组属性,这些是运行时属性,Quarkus 会受到它们的影响。这个调用是在 Quarkus 完成,所有测试完成时。所以我们在这里做的是一个 WireMock 服务器。

WireMock server = new WireMockServer ____;

server.start ____;

现在,由于时间确实不多了,我将直接复制粘贴实际的模拟部分。

我是从我已有的材料中复制的。我们会在演示中分享给你,这样你就可以跟着做。我将把它设为 Java 14,我现在在 14 上。我还需要……

Emmanuel: 你想用多行,也就是文本。

Georgios: 没错,没错,我就是想用那个,因为我讨厌在小……常规 Java 代码里面写 JSON,那简直太糟糕了。

所以为了做到这一点,我需要启用一些预览功能。

所以,Maven 编译……这些东西对于实际的测试并不重要,但很高兴能看到,展示 Quarkus 拥有所有的……所有的灵活性,可以做任何你习惯用 Maven 或其他方式做的事情……

Emmanuel: 所以它支持 Java 14?

Georgios: 哦,是的,当然,当然。

它还不支持 records,但我们已经准备好了一个 PR,所以很快就会支持。

Max: 所以你基本上展示了人们可以在四五年内用于生产的东西,所以……

Georgios: 不,不,他们现在就可以用。

Emmanuel: Quarkus 与生产环境配合得非常好,伙计们。

Georgios: 是的,为什么不呢?所以我必须同时启用编译和 surefire 的预览功能,否则它就无法工作,而且我必须告诉 IntelliJ 使用它,因为它不够智能,无法理解我是如何配置它的。

所以基本上我在这里做的是,我正在替换……这个方法正在寻找的端点,我说当你看到“gr”时,不要返回常规的绿色东西,而是返回相同的名字,愤怒。所以我实际上只是写了希腊语。我做的另一件事是,在这里,我告诉它将所有不匹配的东西转发,我告诉它转发到实际的,实际的服务,我还需要做一件事才能让它工作,我需要告诉 Quarkus 使用这个实际的,这个服务器。

所以,这个服务器一旦启动,它就会在某个 URL、某个端口启动……我不知道是哪个,但我真的不在乎,因为我所要做的就是让国家服务工作,我需要告诉它 URL 在哪里,而 WireMock 的基本 URL 给了我这个信息。所以它的作用是,它会覆盖我在这里的属性,这是默认值。假设这会在我启动时被覆盖,因为就像我说的,这些由 start 返回的属性是运行时属性,它们会覆盖 `application.properties` 中的内容。

Emmanuel: 其中一个问题是,有没有一个文档可以详细介绍每个人自己的进度,这些信息太多了。

Georgios: 是的,我知道,不,因为这是我准备这次演示时才想到的。

Emmanuel: 瞧,YouTube 就像驱动功能一样。

Max: 功能还在,只是例子是新的。

Georgios: 是的,我需要写下这个例子。是的,因为这里有很多信息,而且它不是……

Max: 你完成之后,把这段代码提交到仓库并推送,然后我们就可以开始了。

Georgios: 我已经做了,完成后我会分享 URL。

Max: 那个什么……那个开始?是什么让它被选中了?生命周期管理器?

Georgios: 是的,Quarkus 测试资源,它会获取这些生命周期管理器,我可以有多个生命周期管理器,比如一个用于数据库或其他,它们都在 Quarkus 启动之前运行。这是重要的部分。它们在 Quarkus 启动之前运行,并且可以覆盖运行时属性。

Max: 太酷了。所以我一直在想这个。

Emmanuel: 我们还剩下10分钟,我们还没有展示测试容器,这是我们收到的一个问题。所以我们来做吧。

Georgios: 是的,是的,我们快点做吧,好的。所以,我停留 ____,然后进入测试容器,我想从这里复制粘贴依赖项,我需要拿到,看看,好的。

Emmanuel: ____ 这变得有点尴尬。

测试容器,对于不了解的人来说,它是一种通过你的测试生命周期来控制,在同一台机器上的 Docker 容器中启动一组资源的方式。也许在其他机器上,我不太清楚,但其思想是,你可以控制 Docker 启动和停止数据库、JMS 队列或任何你想运行集成测试的东西。这对于此非常有用。

Georgios: 绝对,绝对。是的,我们经常收到这个问题。所以我们真的需要展示这个。所以我只是复制粘贴了 Hibernate Panache ORM 依赖,postgresql 驱动。

哦,嗯,Panache 的 mock 留到以后吧。你可以和 Stephane 在节目上讨论。因为这是一个 1.5 版本的新功能,还没发布。

好的,我们来创建一个实体“route”。所以那个实体 ____。

Emmanuel: 这里有个好问题。Antonio 说:“关于调试测试,有什么建议吗?我觉得在 IntelliJ 中附加调试器很麻烦,我想我看到了……是的,那是针对测试的,好的,打开它。它不是……____ 任何运行在 IntelliJ 中的应用程序,任何运行在 IntelliJ 中的测试,都可以从 IntelliJ 运行普通的 Java 应用程序并附加调试器……现在我们有了命令模式,这容易多了。我不知道测试方面,你会怎么做……”

Georgios: 我这样做的话会成功,让我看看,好的,我在 Country 服务,如果我到 Country 资源,是的。

Max: 如果你在测试中,只需点击任何地方的调试按钮。

Georgios: ____ 你做得不好,现在我有很多依赖项,但是,是的,我得到了这个东西。一个常规的测试,反正会删除,所以假设我这样做了,现在我所有 Hibernate 的东西都在里面,让我把 Hibernate 拿掉,所以然后……所以。

Max: 你是想,只是在上面添加测试容器吗?或者你有什么……

Georgios: 是的,我当时,我当时正在启动测试容器。是的,没错,但现在我想展示实际的调试……

看,调试就像任何其他 Java 应用程序 JUnit 测试一样,它会起作用,因为这个想法是,它之所以起作用,是因为它在同一个 JVM 中,我的意思是测试和应用程序在同一个 JVM 中运行。

所以调试功能就是可以使用的。

目前有点麻烦的是 IDE 内部的开发模式。所以使用开发模式最简单的方法是运行 `mvn quarkus:dev`,然后稍后附加一个远程调试器。

大多数人都习惯这种工作流程,因为他们习惯于一直从 IDE 或其他地方运行他们的 Java jar。但是使用 Quarkus 开发模式,它有点不同。

你只需要在命令行上启动它,然后就忘了它,然后附加一个远程调试器。

我就是这样……

Max: 所以我只是……我要补充的是,VS Code 实际上在你这样做的时候,它会设置一个默认的启动配置,你按下 F5,它就会启动 quarkus dev 并为你连接调试器,所以你不……我实际上直到 Fred 告诉我它这么轻量级才意识到这一点,而且我想我们已经……我想看到我们在 IntelliJ 和 Eclipse 中也有同样的东西……因为这很好,因为我……

Georgios: 是的,这很棒,因为人们期望它能工作,他们不明白这并不明显,比如当你启动 Quarkus 开发模式时,会启动一个不同的进程,那是你需要连接调试器的进程。是的,这不太明显。所以,是的,在 IntelliJ 中也能有这个功能会很棒。

Emmanuel: 好了,我们回到测试容器。

Georgios: 是的,是的,是的,我……我们把它搞定吧。所以,fruit resource 我可以,我创建了一个 fruit entity。有了 Panache,我显然几乎不用写任何代码,所以一切都会开箱即用。所以,我得到 public List fruit。

Emmanuel: 顺便说一句,我们应该在不久的将来拥有更少的代码,这只是一个预告……从资源到 Panache 实体。

Georgios: 是的,你知道会发生什么。

Emmanuel: 我不知道什么时候,这就是问题所在……我活在未来。

Georgios: PR 我想,已经基本提出了。Stephane 正在审阅,但我们应该快了。啊哈,fruit listAll……好的,这只是列出所有……现在我需要创建 fruit resource test……这将是一个 Quarkus 测试,现在当我添加一个不同的测试资源,一个 Quarkus 测试资源。所以 Quarkus 启动之前我需要启动数据库,对吧?

所以如果我到这里,并测试容器,database.class,创建一个类,那么它又会是一个 Quarkus 资源管理器,而且由于我们时间不多,我将复制粘贴它。

所以它在这里基本上做了什么,我将快速写下它,然后解释它做了什么……所以想法是,带有测试资源生命周期管理器的资源将启动一个测试容器 PostgreSQL 数据库。

所以这里我告诉它版本 11.7,带有数据库名、用户名、密码,这些只是一些简单的默认值,这里重要的部分是启动,我启动数据库,然后我定义 Quarkus 运行时属性,那将是数据源运行时属性。那将是用户名、密码和 URL,就像 WireMock 做的那样,测试容器以同样的方式给我一个 JDBC URL,我可以直接使用。

这样我就不用在 `pom.xml` 中配置 Docker 或让 Docker 容器在其他地方运行了,我只需将其与 Quarkus 测试容器集成,它就能正常工作。我还需要修复一些东西才能让它开箱即用,这是我告诉它的测试特定功能,它是一个数据库,Postgres 数据库,为测试创建和删除数据库。我将为数据库填充一些数据,然后你只需……所以,fruit,我需要复制类似这样的东西,因为……

Max: 那只是在使用测试容器吗?我想我唯一需要做的就是连接到 Quarkus 测试资源。

Georgios: 是的,没错,是的,在 Quarkus 启动之前启动测试容器。

所以这应该总是很清晰。我用三个水果填充了数据库,这就是为什么在我的测试中我说我期待三个……我肯定……好的……

Max: Java 14……

Georgios: 所以 sdk man,我完全赞同它作为使用 Java 14 的方式。现在再次运行,我们看看。所以,测试容器在这里启动,我们看到它启动了,测试通过了,而且并不容易,对吧?所以你看到了……我所需要的只是一个测试资源来启动数据库,重要的是设置属性,我需要的运行时属性,它就能工作。

Emmanuel: 那么,快速了解一下 ____ sdkman.io,它是一种选择你想要的 Java 和其他一些东西的方式。

你这里有很多选项,但是如果你从一个 JDK 跳到另一个 JDK,无论是 GraalVM 还是非 GraalVM。一个非常有用的工具。

你还展示了测试容器。我想指出它不仅限于数据库,你还有很多其他东西,比如 Kafka、Elasticsearch、____ 等等。所以这很不错。而且它与 JUnit 5 集成,这是我们现在正在使用的,所以……有一个小小的缺点,它们没有做到,这真的很令人沮丧,那就是它们确实需要 Docker demon,这意味着我们不能将其与 podman 一起使用,这有点可惜,因为那会非常有用……但除此之外,它运行得很好,是的。

Georgios: 是的,希望那个限制会被解除……我们可能会很快解除,谁知道呢,好吧,我们有……

Max: 我们到时间了。你还有什么想做的吗?

Georgios: 嗯,是的,基本上我展示的所有代码的 URL 都能找到。我知道如何分享。我直接发在聊天里吗?

Max: 是的,你直接发在聊天里,然后我们会在博客发布时把它放进去,是的。

Georgios: 哦,好的,太棒了,你继续。

Emmanuel: 好的,告诉我们你的感受。是太技术化了,不够技术化,太快了,太小了不能完美,太完美了,还是……然后在直播聊天中,当它仍然活跃时,否则就去视频,在里面发表评论,不要忘记订阅 Quarkus 频道或 YouTube.com/quarkusio。我意识到你这里不需要“c”,所以让我把它删除,点击这里,好了……YouTube.com/quarkusio - 点击订阅,我不确定这是否会是每周活动,还是双周活动,双周活动对于像我这样对英语感到困惑的人来说意味着每两周一次。

Max: 没问题吗?

Emmanuel: 是的,有一个问题,让我回去看看……“你为什么更喜欢 Maven 而不是 Gradle?”

Georgios: 原因很简单,我更熟悉它,对吧?

Quarkus 两者都支持,比如说一些极端情况,非常极端的情况,使用 Maven 效果更好,Gradle 的支持正在研究中,随着时间的推移会变得更好。但我使用它的原因是,我更熟悉 Maven。

Max: Maven/Gradle 讨论的本质是,目前我们在 Gradle 中还不能完全处理一些问题,但我们希望解决它们,但我还想说,据我们目前所知,大约 70% 的访问 code.quarkus.io 的人选择了 Maven,其余人选择了 Gradle。

所以,是的,会有一点……它们会改进的,是的。

Emmanuel: Georgios 太快了,所以我们需要慢一点的编码员。

确实,YouTube Live 有一点延迟,希望在最终的视频完全上传并正确运行后不会太明显,但即便如此……这是很好的反馈。

Max: 好了,我们结束了。

Emmanuel: 是的,我们很好,谢谢大家,再见,嗯,未来的某个时候会看到____ 我们将尝试做些什么,但谁知道呢?祝大家五月四日快乐。

Georgios: 谢谢大家!