上一篇介绍了接口的集成测试怎么写,代码层面上来讲还是有很多重复,重复的代码就是坏味道。所以,我们需要重构一下测试代码。
一、重构 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();
}
重构完成,跑一下测试,验证是否正确。
到此,所有的测试都重构完成,现在看测试代码就很简单、整洁了。
七、总结
通过上面这些内容,对重构应该有一些了解,并且也可以用于真实项目中,也可以通过上面这样的重构步骤来重构业务代码,重构的前提是有测试来保驾护航,这样就可以放开手脚来重构了。
重构应该是随时来做的,不应该像我们这样写了很多代码在来重构,至少是看到有重复代码的时候,我们就要想到可以重构了。
如果有疑问或者需要源代码,请关注下面公众号交流或者获取。
附:
清山绿水始于尘,博学多识贵于勤。
微信公众号:「清尘闲聊」。
欢迎一起谈天说地,聊代码。