重构上一篇的测试

59 阅读3分钟

上一篇介绍了接口的集成测试怎么写,代码层面上来讲还是有很多重复,重复的代码就是坏味道。所以,我们需要重构一下测试代码。

一、重构 WebTestClient 

WebTestClient 是所有集成测试通用的类,需要它来发起API请求,我们给它提取到父类中,以 CreateUserTest 这个测试类开始重构。

public abstract class IntegrationTest {

    protected WebTestClient testClient;

    @BeforeEach
    void setUp(WebApplicationContext applicationContext) {
        this.testClient = MockMvcWebTestClient.bindToApplicationContext(applicationContext).build();
    }

}

这里我们新建了一个 IntegrationTest 抽象类,用来存放所有测试都依赖、通用的一些方法。然后在把初始化 WebTestClient 的代码移动到 IntegrationTest 类里。

接着我们重构下 CreateUserTest ,让其编译通过。

@SpringBootTest
public class CreateUserTest extends IntegrationTest {

    @Test
    void should_be_able_to_create_user() {
        testClient.post().uri("/users")
                .bodyValue(Map.of("name", "Zhangsan", "age", 20))
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                .expectStatus().isCreated()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody()
                .jsonPath("$.id").isEqualTo("1");
        // TODO 一般在创建数据后,需要用对应的ID查询数据库中数据是否正确
        // verify user data
        // TODO 最后不要忘记删除接口创建的数据,不然在某些情况下影响其他测试
        // delete created user
    }

}

到了这里,我们重构的第一步就完成了,用 WebTestClient 类继承 IntegrationTest 类,并删除初始化 WebTestClient 相关代码。

执行一下 should_be_able_to_create_user 这个测试,如果是通过的,那说明我们重构没问题,接下来就把其他几个测试也一起重构掉。

二、重构 @SpringBootTest 

现在这个注解在每个测试类里面写的,它也是通用的,所以我们也给它提取到抽象类里面。

@SpringBootTest
public abstract class IntegrationTest {

    protected WebTestClient testClient;

    @BeforeEach
    void setUp(WebApplicationContext applicationContext) {
        this.testClient = MockMvcWebTestClient.bindToApplicationContext(applicationContext).build();
    }

}

把 @SpringBootTest 注解提取到 IntegrationTest 类中,然后把子类中的 @SpringBootTest 注解删除,子类修改后如下:

public class CreateUserTest extends IntegrationTest {

    @Test
    void should_be_able_to_create_user() {
        testClient.post().uri("/users")
                .bodyValue(Map.of("name", "Zhangsan", "age", 20))
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                .expectStatus().isCreated()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody()
                .jsonPath("$.id").isEqualTo("1");
        // TODO 一般在创建数据后,需要用对应的ID查询数据库中数据是否正确
        // verify user data
        // TODO 最后不要忘记删除接口创建的数据,不然在某些情况下影响其他测试
        // delete created user
    }

}

修改一个测试类,我们就可以跑一下测试,验证下修改是否正确。

三、重构 testClient.post() 

通过前面的重构,重复代码删除了很多,代码看上去也整洁很多。不过测试方法里还有很多不必要的重复,现在我们也来给它重构掉。

我们把 post 方法提取到 IntegrationTest 里,方便其他测试使用。

代码如下:

@SpringBootTest
public abstract class IntegrationTest {

    protected WebTestClient testClient;

    @BeforeEach
    void setUp(WebApplicationContext applicationContext) {
        this.testClient = MockMvcWebTestClient.bindToApplicationContext(applicationContext).build();
    }    

    protected WebTestClient.BodyContentSpec post(String uri, Object body) {
        return testClient.post().uri(uri)
                .bodyValue(body)
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                .expectStatus().isCreated()
                .expectHeader().contentType(MediaType.APPLICATION_JSON)
                .expectBody();
    }

}

修改 CreateUserTest 类的测试,代码如下:

public class CreateUserTest extends IntegrationTest {

    @Test
    void should_be_able_to_create_user() {
        post("/users", Map.of("name", "Zhangsan", "age", 20))
                .jsonPath("$.id").isEqualTo("1");

        // TODO 一般在创建数据后,需要用对应的ID查询数据库中数据是否正确
        // verify user data
        // TODO 最后不要忘记删除接口创建的数据,不然在某些情况下影响其他测试
        // delete created user
    }

}

现在可以看到测试创建用户的测试非常简洁,就2行代码,如果写其他创建测试就会非常简单,节省开发时间。

重构完成后不要忘记跑测试,检验下重构是否正确。

到此,创建相关的测试就重构完成,接下来我们把其他几个测试全重构掉。

四、重构 testClient.get() 

在 IntegrationTest 类中添加 get 方法,代码如下:

protected WebTestClient.BodyContentSpec get(String uri, Map<String, Object> uriVariables) {
    return testClient.get().uri(uri, uriVariables)
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectHeader().contentType(MediaType.APPLICATION_JSON)
            .expectBody();
}

修改对应的测试,代码如下:

@Test
void should_be_able_to_get_user_details() {
    get("/users/{id}", Map.of("id", "zhangsan"))
            .jsonPath("$.id").isEqualTo("zhangsan")
            .jsonPath("$.name").isEqualTo("ZhangSan")
            .jsonPath("$.age").isEqualTo(20);

}

重构完成,跑一下测试,验证是否正确。

五、重构 testClient.put() 

在 IntegrationTest 类中添加 get 方法,代码如下:

protected WebTestClient.BodyContentSpec update(String uri, Map<String, Object> uriVariables, Object body) {
    return testClient.put().uri(uri, uriVariables)
            .bodyValue(body)
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectHeader().contentType(MediaType.APPLICATION_JSON)
            .expectBody();
}

修改对应的测试,代码如下:

@Test
void should_be_able_to_update_user() {
    update("/users/{id}", Map.of("id", "zhangsan"), Map.of("name", "LiShi", "age", 30))
            .jsonPath("$.id").isEqualTo("zhangsan");
    // TODO 一般在修改数据后,需要用对应的ID查询数据库中数据是否正确
    // verify user data
}

重构完成,跑一下测试,验证是否正确。

六、重构 testClient.delete() 

在 IntegrationTest 类中添加 delete 方法,代码如下:

protected WebTestClient.BodyContentSpec delete(String uri, Map<String, Object> uriVariables) {
    return testClient.delete().uri(uri, uriVariables)
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isNoContent()
            .expectBody();
}

修改对应的测试,代码如下:

@Test
void should_be_able_to_delete_user() {
    delete("/users/{id}", Map.of("id", "zhangsan"))
            .isEmpty();
}

重构完成,跑一下测试,验证是否正确。

到此,所有的测试都重构完成,现在看测试代码就很简单、整洁了。

七、总结

通过上面这些内容,对重构应该有一些了解,并且也可以用于真实项目中,也可以通过上面这样的重构步骤来重构业务代码,重构的前提是有测试来保驾护航,这样就可以放开手脚来重构了。

重构应该是随时来做的,不应该像我们这样写了很多代码在来重构,至少是看到有重复代码的时候,我们就要想到可以重构了。

如果有疑问或者需要源代码,请关注下面公众号交流或者获取。

附:

清山绿水始于尘,博学多识贵于勤。
微信公众号:「清尘闲聊」。
欢迎一起谈天说地,聊代码。