1、概览 {#1概览}
API 文档在团队开发中极其重要,特别是在 API 接口及其复杂的情况下,良好的 API 文档不仅能提升开发效率,还能显示产品的质量。如果一家公司的 API 文档写得马马虎虎,那么它的 API 也可能写得马马虎虎。
程序员都讨厌写自己文档和别人不写文档。
本文将带你了解如何使用 Spring REST Docs 自动地生成 API 文档。
2、API 接口 {#2api-接口}
一个简单 API 如下:
@RestController
@RequestMapping("/books")
public class BookController {
private final BookService service;
public BookController(BookService service) {
this.service = service;
}
@GetMapping
public List<Book> getBooks(@RequestParam(name = "page") Integer page) {
return service.getBooks(page);
}
}
该 API 返回系统中的 Book
数据。不过,由于可用图书数量庞大,不可能一次性返回所有。所以给客户端提供了一个查询参数 page
,用于控制数据分页。
而且,需要把该参数设置为必须参数(默认就是必须参数),避免客户端一次性请求过多数据。如果客户端未提交该查询参数,就会收到状态为 400 的错误提示。
3、文档 {#3文档}
编写文档的通常方法是 "手写文档",这意味着开发人员必须把同一件事写两遍。首先写代码,然后再写对应的文档。太麻烦!
文档是一种相当正式的文件,其目标是清晰明了,并不需要太多花里胡哨的东西。因此,我们可以根据代码生成文档,这样就不用重复写同样的东西,而且所有的改动都会反映在文档中。
我们可以通过 Spring REST Docs 来生成 API 文档。不过,它不是从代码中生成文档,因为代码没有提供太多上下文,而是从测试中生成文档。这可以表达相当复杂的案例和示例。另一个好处是,如果测试失败,文档也不会生成。
4、测试 {#4测试}
Spring REST Docs 支持常用的 REST 测试框架。如 MockMvc 、WebTestClient 和 REST-assured,然而,无论使用哪种测试框架,主要的思想和结构都是相似的。
本文使用 JUnit 5 作为测试用例的基础,但也可以在 JUnit 4 中设置 Spring REST Docs。
以下所有测试方法都需要额外扩展(Extensio):
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
这都是用于生成文档的特殊类。
4.1、WebTestClient {#41webtestclient}
首先从 WebTestClient 开始,这是一种更现代的 REST 测试方法。
如前所述,需要用额外的扩展来扩展测试类。此外,还需要对其进行配置:
@BeforeEach
public void setUp(ApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
this.webTestClient = WebTestClient.bindToApplicationContext(webApplicationContext)
.configureClient()
.filter(documentationConfiguration(restDocumentation))
.build();
}
之后,就可以编写一个测试,它不仅会测试我们的 API,还会提供有关请求的信息:
@Test
@WithMockUser
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() {
webTestClient.get().uri("/books?page=2")
.exchange().expectStatus().isOk().expectBody()
.consumeWith(document("books",
requestParameters(parameterWithName("page").description("The page to retrieve"))));
}
4.2、WebMvcTest 和 MockMvc {#42webmvctest-和-mockmvc}
这种方法与上一种方法非常相似。它同样需要正确的设置:
@BeforeEach
public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
this.mockMvc = webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentation))
.alwaysDo(document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint())))
.build();
}
除了使用了 MockMvc 及其 API 之外,测试方法看起来是一样的:
@Test
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() throws Exception {
mockMvc.perform(get("/books?page=2"))
.andExpect(status().isOk())
.andDo(document("books",
requestParameters(parameterWithName("page").description("The page to retrieve"))));
}
4.3、REST-assured {#43rest-assured}
最后,来看看使用 REST-assured 的示例。由于我们需要一个正在运行的服务器,因此不能使用 @WebMvcTest
或 @AutoconfigureMockMvc
。
这里,我们使用 @AutoconfigureWebMvc
,并提供正确的端口:
@BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation, @LocalServerPort int port) {
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(restDocumentation))
.setPort(port)
.build();
}
这些测试看起来大致相同:
@Test
@WithMockUser
void givenEndpoint_whenSendGetRequest_thenSuccessfulResponse() {
RestAssured.given(this.spec).filter(document("users", requestParameters(
parameterWithName("page").description("The page to retrieve"))))
.when().get("/books?page=2")
.then().assertThat().statusCode(is(200));
}
5、生成文档 {#5生成文档}
此时,文档还没生成。还有几个步骤需要完成。
5.1、生成文档片段 {#51生成文档片段}
运行测试后,可以在 target
文件夹中找到生成的片段(也可以配置输出的目录),看起来如下:
[source,bash]
----
$ curl 'http://localhost:8080/books?page=2' -i -X GET
----
同时,还可以查看保存在 .adoc
文件中的参数信息。
|===
|Parameter|Description
\|`+page+`
\|The page to retrieve
`|===
`
5.2、文档生成 {#52文档生成}
下一步是为 AsciiDoctor 提供一个配置,以创建可读性更强的 HTML 文档。AsciiDoc 是一种简单而强大的标记语言。可用于各种用途,例如生成 HTML 和 PDF 文件或撰写书籍。
由于我们想生成 HTML 文档,因此需要为 HTML 提供模板大纲:
= Books With Spring REST Docs
How you should interact with our bookstore:
.request
include::{snippets}/books/http-request.adoc\[\]
.request-parameters
include::{snippets}/books/request-parameters.adoc\[\]
`.response
include::{snippets}/books/http-response.adoc[]
`
在本例中,使用的是一种简单的格式,但也可以创建一种更复杂的自定义格式,这样既吸引人又能提供更多信息。AsciiDoc 非常灵活。
5.3、生成 HTML {#53生成-html}
正确设置和配置后,就可以将 generation goal 添加到 Maven phase:
<executions>
<execution>
<id>generate-docs</id>
<phase>package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
<attributes>
<snippets>${snippetsDirectory}</snippets>
</attributes>
<sourceDirectory>src/docs/asciidocs</sourceDirectory>
<outputDirectory>target/generated-docs</outputDirectory>
</configuration>
</execution>
</executions>
运行 mvn 命令并触发生成,我们在上一节中定义的模板会生成以下 HTML:
可以将这一流程添加到 CI/DI Pipeline 上,每一次构建都会生成最新的文档。另一个好处是,这一流程减少了人工操作,而人工操作既费时又容易出错。
6、总结 {#6总结}
文档是软件的重要组成部分,大家都知道,但是大家都不喜欢写文档。通过 Spring REST Docs,我们可以根据代码,以最小的工作量生成优秀的 API 文档。
Ref:https://www.baeldung.com/spring-rest-document-query-parameters