Skip to content

Latest commit

 

History

History
248 lines (196 loc) · 9.91 KB

File metadata and controls

248 lines (196 loc) · 9.91 KB

9.1 测试框架概述

Fit 提供了一个简单高效的测试框架 fit-test-framework,用于在开发时进行测试。它遵循 IoC 原则降低代码耦合度,将待测试类依赖的组件以 Bean 对象的形式注入到容器里,实现业务代码与测试代码的解耦。本节介绍测试框架的功能和实现,以及运行流程。

9.1.1 注解

fit-test-framework 提供了 @FitTestWithJunit 注解支持Junit4Junit5 使用,绕过业务代码里的 main 方法,将待测试类数组注入到容器中。在这一步中,容器会通过待测试类中添加的注解 @ScanPackages 扫描指定路径下的 Bean 对象,方便容器创建待测试类 Bean 对象时注入。

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(FitExtension.class)
public @interface FitTestWithJunit {
    /**
     * 需要注入到容器的组件类型数组。
     *
     * @return 表示需要注入到容器的组件类型数组的 {@link Class}{@code <?>[]}。
     */
    Class<?>[] classes() default {};
}

同时,它还提供 @Mocked 注解用于创建以及注入模拟实例,运行单元测试时不需要访问持久化数据,实现测试代码与业务基础设施的解耦。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Fit
public @interface Mocked {}

9.1.2 Junit 扩展类

fit-test-framework 分别对 Junit4Junit5 提供扩展类 FitRunnerFitExtension,分别与 Junit4 的注解 RunWithJunit5 的注解 ExtendWith 搭配使用,以便在测试开始的时候根据类型自动创建测试框架的管理类。

/**
 * Junit4 的自定义扩展类。
 */
public class FitRunner extends BlockJUnit4ClassRunner {
    FitTestManager manager;

    public FitRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
        this.manager = FitTestManager.create(clazz);
    }

    @Override
    protected Object createTest() throws Exception {
        Object testInstance = super.createTest();
        this.manager.prepareTestInstance(testInstance);
        return testInstance;
    }
}

/**
 * Junit5 的自定义扩展类。
 */
public class FitExtension implements BeforeAllCallback, TestInstancePostProcessor {
    private FitTestManager manager;

    @Override
    public void beforeAll(ExtensionContext context) {
        this.manager = FitTestManager.create(context.getRequiredTestClass());
    }

    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
        this.manager.prepareTestInstance(testInstance);
    }
}

9.1.3 TestContext

TestContext 为测试框架提供上下文,依赖测试框架使用的插件类 TestPlugin 和测试框架使用的运行时 TestFitRuntime。上下文中还会获取单测类的类对象 testClass 和单测试类的实例对象 testInstance

public class TestContext {
    private final Class<?> testClass;
    private final TestPlugin testPlugin;
    private Object testInstance;
}

9.1.4 测试框架运行流程

运行测试用例时,程序先通过 Junit4RunWith 注解(如果是 Junit5 则通过 ExtendWith 注解)注入 Fit 测试框架扩展类,启动测试框架。随后通过注解注入待测试的类。这里待测试类中的 ScanPackages 指定扫包路径,程序会将路径下的所有Bean对象加载到容器中,在测试用例中将对应的Bean 对象注入到依赖中实例化对象,参考下图。这里所有对象实例化完成,开始跑测试用例。

image

9.2 mybatis 测试框架

本节介绍 mybatis 数据库测试框架的实现。在业务场景中,海量数据存储到业务数据库。在测试时,我们往往需要切换业务数据库到测试数据库,以确保业务数据不受影响,独立测试开发人员编写的业务代码。我们的 mybatis 测试框架实现了数据库的切换,业务代码连接 PostgreSQL,测试代码连接 Java 开发的嵌入式(内存级别) H2 数据库,它具有启动速度快,而且可以关闭持久化功能,每一个用例执行完随即还原到初始状态等优点。

9.2.1 注解

FIT 框架提供了@MybatisTest注解来进行数据库的测试,其中,DatabaseModel可指定为MYSQLPOSTGRESQL

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@FitTestWithJunit
@EnableMybatis
@EnableDataSource
public @interface MybatisTest {
    /**
     * 需要注入到容器的组件类型数组。
     *
     * @return 表示需要注入到容器的组件类型数组的 {@link Class}{@code <?>[]}。
     */
    @Forward(annotation = FitTestWithJunit.class, property = "includeClasses") Class<?>[] classes() default {};

    /**
     * 获取测试数据源兼容模式。
     *
     * @return 表示数据源兼容模式的 {@link DatabaseModel}。
     * @see EnableDataSource#model()
     */
    @Forward(annotation = EnableDataSource.class, property = "model") DatabaseModel model() default DatabaseModel.NONE;
}

9.2.2 测试示例

以下是一个mybatis测试用例,其中,数据源兼容模式选择了POSTGRESQL

@MybatisTest(classes = {PeopleRepo.class}, model = DatabaseModel.POSTGRESQL)
@Sql(scripts = "sql/test_create_table.sql")
@DisplayName("测试 PeopleMapper")
public class PeopleRepoTest {
    @Fit
    private PeopleRepo mapper;

    @Test
    @DisplayName("插入数据成功")
    void shouldOkWhenInsert() {
        this.mapper.insert("Student", 28, "Xiao Ming");
        List<String> students = mapper.select("Student", 28);
        assertThat(students.size()).isEqualTo(1);
        assertThat(students.get(0)).isEqualTo("Xiao Ming");
    }
}

9.3 mvc 测试框架

FIT 在提供方便易用的MVC框架的同时,也提供了相应的测试工具。

9.3.1 注解

FIT 提供了@MvcTest注解将待测试类数组注入到测试代码中。

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@FitTestWithJunit
@EnableMockMvc
public @interface MvcTest {
    /**
     * 需要注入到容器的组件类型数组。
     *
     * @return 表示需要注入到容器的组件类型数组的 {@link Class}{@code <?>[]}。
     */
    @Forward(annotation = FitTestWithJunit.class, property = "includeClasses") Class<?>[] classes() default {};
}

示例如下:

@MvcTest(classes = {TestController.class, Config.class})
public class MvcDemoTest {
    ...
}

9.3.2 RequestBuilder

本测试框架为客户端提供了HTTP请求的构建器MockMvcRequestBuilderMockRequestBuilder,它们提供了一系列方法,允许开发者灵活地定义请求的细节,如请求方法、URL、请求头、请求参数和请求体等。

MockMvcRequestBuilder为模拟 MVC 客户端提供请求的构建集合,它提供了一系列静态方法构建 HTTP 请求方法,并返回一个新创建的MockRequestBuilder

方法 解释
get(String url) 构建一个 GET 请求
post(String url) 构建一个 POST 请求
put(String url) 构建一个 PUT 请求
patch(String url) 构建一个 PATCH 请求
delete(String url) 构建一个 DELETE 请求

MockRequestBuilder提供了一系列方法用于设置请求的各项参数及内容:

方法 解释
param(String name, String value) 为请求插件结构体添加键值对参数
param(String name, List values) 为请求插件结构体添加键值对参数
responseType(Type responseType) 设置客户端请求结果的类型
port(int port) 设置客户端请求的端口号
header(String name, String header) 设置请求结构体的消息头
header(String name, List headers) 设置请求结构体的消息头
entity(Entity entity) 设置请求结构体的消息体内容
formEntity(MultiValueMap<String, String> formEntity) 设置 Http 请求的 Form 格式的消息体内容
jsonEntity(Object jsonObject) 设置 Http 请求的 Json 格式的消息体内容
client(HttpClassicClient httpClassicClient) 设置 Http 客户端
build() 构建客户端的请求参数

使用示例如下:

//构建POST请求
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/user").jsonEntity(userDto).responseType(Void.class);
//构建GET请求并添加参数键值对
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/user")
        .param("name", "zhangsan")
        .param("email", "zhangsan@test.com")
        .responseType(TypeUtils.parameterized(UserVo.class, new Type[] {UserEntity.class}));

9.3.3 MockMvc

FIT 框架提供了控制器的测试类 MockMvc,它允许开发者在不启动服务器的情况下模拟 HTTP 请求和响应,验证控制器的行为,它提供了perform()函数用以执行MockRequestBuilder模拟的HTTP请求,同时提供了streamPerform()函数以获取流式数据。使用示例如下:

public class UserControllerTest {
    @Fit
    private MockMvc mockMvc;

    private HttpClassicClientResponse<?> response;

    @Test
    void shouldReturnOkWhenGetUser() {
        ...
        MockRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/user")
                .param("name", "zhangsan")
                .param("email", "zhangsan@test.com")
                .responseType(TypeUtils.parameterized(UserVo.class, new Type[] {UserEntity.class}));
        this.response = this.mockMvc.perform(requestBuilder);
        assertThat(this.response.statusCode()).isEqualTo(200);
    }
}

其中,MockMvc实例可直接通过依赖注入的方式获取。