Naitive 和 Testing
Quarkus 与当前流行的Java应用的区别是提供了基于GraalVM实现的本地镜像模式,使用这种方式才能发挥Quarkus的最大优势。当前并不是说JVM不行,在我们的压测中JVM模式的QPS竟比Native模式要高,说明我们对于Native的优化还没有到位= =
Quarkus 同样提供了基于Junit的单元测试,并提供了Native模式下的实现。
本章将会讲这两部分,Native模式同样也会基于容器运行,如docker、OpenShift容器模式后面再讲。
本章目标
- 1.如何打Native模式的包
- 2.如何使用单元测试
- 3.如何使用Native单元测试
1.如何打Native模式的包
1.1 Quarkus的执行都是依靠包管理工具来实现的,我们的教程基于Maven 下面添加插件
<profiles>
<profile>
<id>native</id>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<configuration>
<enableHttpUrlHandler>true</enableHttpUrlHandler>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
可以看到Quarkus 提供了一个-P 命令 native
我们执行打包时带上这个命令就是在打native包了。 后面的第一个插件就是打包插件,第二个插件是Native模式单元测试用的。打包命令如下:mvn clean package -Pnative
执行命令前还需要确定下环境是否已经满足:
(1)配置了GraalVM的环境变量,使用java --version
验证,具体配置方式在第一讲。
(2)我们的开发机上安装了native-image ${GRAALVM_HOME}/bin/gu install native-image
不同的系统安装可能有些差异,官网也进行了说明如 MacOS Catalina
1.2 下面执行打Native包的命令
注意:本地包的编译打包过程有点漫长,这个主要看机器配置。耐心等待即可。平常我们开发均使用JVM即可。
看看target下都生成了什么吧
可以看到有两种包 一种就是JVM的Jar包 还有一种就是我们要的本地可执行文件 红框框标注的。
打包虽然有点漫长,那看看启动速度吧
这个速度在Spring中或许都不敢想象哈。
2.如何使用单元测试
2.1 引入单元测试的依赖
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
为了支持junit5 还需要添加这个插件做适配
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
2.2 为了写单元测试,我们先写几个接口
@Path("/hello")
public class GreetingResource {
@Inject
GreetingService service;
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{name}")
public String greeting(@PathParam String name) {
return service.greeting(name);
}
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
2.3 写单元测试
package org.acme.getting.started.testing;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(is("hello " + uuid));
}
}
我们的Demo中使用了RestAssured.given()方法来访问一个我们的Http服务,来验证Http服务是否正常。RestAssured其实就是单元测试封装的一个Http Client 通过它的API来访问我们的服务。需要测试的话只需要熟悉一下他的API 就行了。
需要注意的是,如果我们的配置使用了区分环境的配置如 dev、test、prod、那么我们的配置文件中也需要有test环境必要的配置,如数据库连接等。Quarkus的分环境配置会和使用配置中心Consul一块来讲。test环境默认走的端口是是8081 注意不要冲突。可以通过配置来修改。
quarkus.http.test-port=8888
2.4 测试特定接口
个人觉得没有太大用,所以大家如果有需要就看文档吧使用也很简单。https://quarkus.io/guides/getting-started-testing#testing-a-specific-endpoint
2.5 测试Service方法
有时候我们并不需要直接测试接口是否正确,只需要测试一下我们的逻辑方法是否正常。那么我们可以直接注入Service类直接调用方法来测试,当然调用服务的方式也是可以的。
package org.acme.getting.started.testing;
import javax.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class GreetingServiceTest {
@Inject
GreetingService service;
@Test
public void testGreetingService() {
Assertions.assertEquals("hello Quarkus", service.greeting("Quarkus"));
}
}
2.6 实现自己的单元测试注解 组合拦截器
在上面吧的测试用我们都是使用的官方提供的@QuarkusTest
注解,我们可以自定义组合我们需要的注解。
如 在我们的单元测试中需要添加事务,我们可以直接在方法或者类上添加@Transaction注解,也可以自定义我们的组合注解。
@QuarkusTest
@Stereotype
@Transactional
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TransactionalQuarkusTest {
}
其实也很少用到,知道就行了。
那如果我们的单元测试需要操作数据,但是不想让测试结果影响正常的数据,我们期望对于测试完成之后将对数据库的操作全部rollback,我们可以使用io.quarkus.test.TestTransaction
。
2.7 单元测试的回调方法
官方提供了接口供我们实现。
io.quarkus.test.junit.callback.QuarkusTestBeforeClassCallback
io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback
io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback
io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback
2.7 修改单元测试的配置环境
默认情况下,单元测试走的是test profile的环境,但有时需要走其他的环境 如dev或者自定义环境等等。 用的也不多,通过实现QuarkusTestProfile
实现
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.quarkus.test.junit.QuarkusTestProfile;
import io.quarkus.test.junit.QuarkusTestProfile.TestResourceEntry;
public class MockGreetingProfile implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
return Collections.singletonMap("quarkus.resteasy.path","/api");
}
@Override
public Set<Class<?>> getEnabledAlternatives() {
return Collections.singleton(MockGreetingService.class);
}
@Override
public String getConfigProfile() {
return "test";
}
@Override
public List<TestResourceEntry> testResources() {
return Collections.singletonList(new TestResourceEntry(CustomWireMockServerManager.class));
}
}
2.8 Mock 支持
引入Mockito
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
详情见 https://quarkus.io/guides/getting-started-testing#mock-support
2.9 Quarkus Test生命周期管理
Quarkus 提供了Test的生命周期管理类,@io.quarkus.test.common.QuarkusTestResource
and io.quarkus.test.common.QuarkusTestResourceLifecycleManager
. 在生命周期的回调中我们可以自定做一些操作。
3.如何使用Native单元测试
3.1 Native 单元测试的插件在上面已经加入并配置了
3.2 Native 单元测试类
import io.quarkus.test.junit.NativeImageTest;
@NativeImageTest
public class NativeGreetingResourceIT extends GreetingResourceTest {
// Run the same tests
}
写起来非常简单,只是在JVM单元测试的基础上添加了@NativeImageTest
注解,然后输入命令mvn verify -Pnative
注意:
- Native模式的单元测试默认走的Prod的配置,换成其他环境需要添加配置。方法一 在application.properties 中添加
quarkus.test.native-image-profile=test
; 方法二 在测试命令后添加额外的命令./mvnw verify -Pnative -Dquarkus.test.native-image-profile=test
参考:https://quarkus.io/guides/building-native-image#testing-the-native-executable
总结
本章简单介绍了Quarkus的单元测试写法和如何构建本地可执行文件。