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. Tests With Documentation
Spring REST Docs 支持主要的 REST 測試框架。我們將會考慮 MockMvc、WebTestClient 和 REST-assured 的示例。然而,所有這些的思路和結構都大致相同。
此外,我們將使用 JUnit 5 作為測試用例的基礎,但可以使用 JUnit 4。
所有下面的測試方法都需要添加額外的擴展:
@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. WebMvcTest 和 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
最後,我們檢查 REST-assured 的示例。 因為我們需要一個運行的服務器,所以我們不應該使用 @WebMvcTest 或 @AutoconfigureMockMvc。這裏,我們使用 @AutoconfigureWebMvc 並且也提供正確的端口: 但是,測試方法看起來大致相同: 然而,在此之前,我們還沒有生成文檔。要獲得結果,我們需要進行額外的步驟。 我們可以運行測試後在目標文件夾中找到生成的片段。但是,我們可以配置輸出目錄以定義存儲片段的不同位置。通常它們如下所示: 同時,我們還可以看到有關我們參數的信息,這些信息存儲在 .adoc 文件中。 下一步是為 AsciiDoctor 提供配置,以便創建具有更易讀文檔的 HTML。 AsciiDoc 是一種簡單但功能強大的標記語言。我們可以將其用於各種用途,例如生成 HTML 和 PDF,或編寫書籍。 因此,為了生成 HTML 文檔,我們需要制定 HTML 模板: 在我們的案例中,我們使用一種簡單格式,但可以創建更復雜、更具吸引力和信息量的自定義格式。 AsciiDoc 的靈活性有助於我們完成這項任務。 在正確設置和配置後,當我們將生成目標附加到 Maven 階段時: 我們可以運行所需的 mvn 命令並觸發生成。 之前定義的模板渲染以下 HTML: 我們可以將此流程附加到我們的流水線中,並始終擁有相關且準確的文檔。另一個好處是,該流程減少了手動工作,這既浪費又容易出錯。 文檔是軟件開發的重要組成部分。開發者承認這一點,但只有少數人始終堅持編寫或維護文檔。Spring REST Docs 允許我們以最小的努力,基於代碼而非我們對 API 功能的理解,生成良好的文檔。@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
----|===
|Parameter|Description
|`+page+`
|The page to retrieve
|===5.2. 文檔生成
= 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[]5.3. 生成 HTML
<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>6. 結論