04-Native模式和单元测试

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
04-Native模式和单元测试
1.2 下面执行打Native包的命令
注意:本地包的编译打包过程有点漫长,这个主要看机器配置。耐心等待即可。平常我们开发均使用JVM即可。
04-Native模式和单元测试
看看target下都生成了什么吧
04-Native模式和单元测试
可以看到有两种包 一种就是JVM的Jar包 还有一种就是我们要的本地可执行文件 红框框标注的。

打包虽然有点漫长,那看看启动速度吧
04-Native模式和单元测试
这个速度在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
04-Native模式和单元测试
注意:

  1. 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的单元测试写法和如何构建本地可执行文件。

上一篇:从GraalVM到Quarkus系列-B002篇-Quarkus中的字节码框架gizmo


下一篇:leetcode29.两数相除(中等)