1. 概述
文檔對於我們打算與世界分享的任何代碼都至關重要,尤其是在代碼相對複雜的情況下。優秀的API文檔不僅能吸引開發者使用它,還能展示產品的質量。 一篇寫得不清楚的文檔可能也意味着一個寫得不清楚的API。
然而,開發者更喜歡為機器編寫代碼,而不是為人類編寫文本。
在本教程中,我們將探討如何將編寫文檔與編寫API結合使用,以及如何使用Spring REST Docs。我們將以查詢參數文檔為例。
2. API
讓我們考慮一個簡單的 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);
}
}此端點返回我們在網站上提供的書籍集合。然而,由於書籍數量龐大,我們無法返回所有書籍。客户端提供我們目錄的頁碼,我們僅向他們發送該頁的信息。
我們決定使該參數為必需參數。在這種情況下,它是一個默認設置。通過這種方式,我們提高了服務的性能,並防止客户端一次請求過多的數據。
但是,我們必須提供關於我們決策的信息,並解釋客户端應該遵循的規則。在這種情況下,如果參數不存在,客户端將收到錯誤消息。
3. 文檔説明
通常編寫文檔的做法是編寫文檔,也就是説,開發者需要將同一內容編寫兩次:一次在代碼中,另一次在文本中,解釋如何與系統交互。 然而,這種做法既浪費時間,也不能假設所有開發者都會遵循。
文檔是一種相當正式的文件,旨在追求清晰度,而不是富有啓發性的見解、巧妙的措辭或創新的情節結構。 因此,為什麼不從代碼中生成文檔呢? 這樣,我們就不必編寫相同的代碼,並且所有更改都會反映在文檔中。
Spring REST Docs 正確地做到這一點。 但是,它不是從代碼中生成文檔,因為它提供的上下文很少,而是從測試中生成文檔。 這樣,我們就可以表達相當複雜的用例和示例。 另一個好處是,如果我們的測試失敗,文檔將不會被生成。
4. 包含文檔的測試
Spring REST Docs 支持主要的 REST 測試框架。 我們將以 MockMvc、WebTestClient 和 REST-assured 為例。 然而,所有這些框架的主要思想和結構都將相似。
此外,我們將使用 JUnit 5 作為測試用例的基礎,但也可以使用 JUnit 4 設置 Spring REST Docs。
以下所有測試方法都需要額外的擴展:
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})這些是用於文檔生成的功能模塊。
4.1. WebTestClient
讓我們從 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. <em >WebMvcTest</em> 和 <em >MockMvc</em>
一般來説,此方法與前一個方法非常相似。它也需要正確的設置:
@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
最後,我們來看一個使用 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.1. 生成片段
我們可以運行測試後在目標文件夾中找到生成的片段。但是,我們可以配置輸出目錄以定義存儲片段的另一個位置。通常,它們如下所示:
[source,bash]
----
$ curl 'http://localhost:8080/books?page=2' -i -X GET
----同時,我們還可以看到關於我們參數的信息,這些信息存儲在 .adoc 文件中。
|===
|Parameter|Description
|`+page+`
|The page to retrieve
|===5.2 文檔生成
下一步是為 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
在正確設置和配置完成後,當我們能夠將生成目標附加到 Maven 階段時:
<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:
我們可以將此流程附加到我們的流水線中,並始終擁有相關且準確的文檔。 另一個好處是,此流程減少了手動工作,這既浪費又容易出錯。
6. 結論
文檔是軟件開發中不可或缺的一部分。開發人員對此表示認可,但只有少數人能夠始終如一地編寫或維護文檔。Spring REST Docs 允許我們通過基於代碼而非我們對 API 功能的理解,以最小的努力生成高質量的文檔。