使用spring rest docs 进行单元测试

来源:互联网 发布:轻淘客cms是什么 编辑:程序博客网 时间:2024/05/16 01:13

Spring REST Docs 帮助你管理 RESTful 服务文档,使用 Asciidoctor 来编写文档,使用 Spring MVC Test 自动生成片段。

首先什么样的风格叫做rest风格呢?

编程界的大家应该都有听说过restful风格能够提高项目的性能,而且现在到处都在说我们的项目是rest风格的,那么什么是rest风格呢?简单来举个例子 大家就能懂啦:
比如说现在有这样一个url:
https://www.oschina.net/news/66853/spring-rest-docs-1-0-0
这里的66853可能是一个专用的id或者其他相关的映射,我们从这里是不能知道它到底指的是什么。
而如果是非restful风格的url则会是这样:
https://www.oschina.net/news/uid?userUid=66853/spring-rest-docs-1-0-0
XXX?XXX= XXX这样大家都能猜出来的url,后来加密之后发送到后台,然后就出现了所谓的rest比较流行。


如何使用spring rest doc 生成api文档?

例子:SpringBoot工程,将http接口通过API文档暴露出来,只需要通过JUnit单元测试和spring的MockMVC就可以生成文档。
SpringRestDoc框架通过测试来生成REST接口的说明文档:可以对参数和返回值进行简单的说明,还能产生url和返回用例,通过单元测试,和目前的moxkMVC框架相结合,可以产生Asciidoctor文档片段。
缺点:目前只能生成片段,继承一整个API文档的话还需要手动去写。当然测试的越多,文档的内容就越详细。
解决方案:这里的只能产生片段文章,不能自动整合成一整个文档的缺点,可以使用我上一篇介绍的swagger去解决。swagger具有较强的能力和自信能做好这件事情 ,你就放心交给它好了。

基础配置

软件:idea
jdk:1.8
maven:3.0+


简单粗暴,咱直接从操作开始哈:

1.在pom.xml文件中引入对应的maven依赖

<dependencies>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-test</artifactId>      <scope>test</scope>    </dependency>    <dependency>      <groupId>org.springframework.restdocs</groupId>      <artifactId>spring-restdocs-mockmvc</artifactId>      <scope>test</scope>    </dependency>  </dependencies>

2.再测试类中添加设置:

restdoc是通过单元测试生成snippets文件,然后snippets根据插件生成html文档
在测试类中的设置:
(1)

public JUnitRestDocumentation restDocumentation =new JUnitRestDocumentation("target/generated-snippets");

(2) Then 在@before方法中配置MockMVC或者REST assured:

Setting up your JUnit tests

private MockMvc mockMvc;@Autowiredprivate WebApplicationContext context;@Beforepublic void setUp() {this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).apply(documentationConfiguration(this.restDocumentation)).build();}

MockMvc实例是使用一个配置MockMvcRestDocumentationConfigurer来配置的。这个类的一个实例可以是从org.springframework.restdocs.mockmvc.MockMvcRestDocumentation中的静态documentationConfiguration()方法获得。

Setting up your tests without JUnit
与上面不同的是不需要@Rule,并且换成了JUnitRestDocumentation

private ManualRestDocumentation restDocumentation =new ManualRestDocumentation("target/generated-snippets");

同样的,setup()方法中MockMVC的设置将变成这样:

private MockMvc mockMvc;@Autowiredprivate WebApplicationContext context;@BeforeMethodpublic void setUp(Method method) {this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).apply(documentationConfiguration(this.restDocumentation)).build();this.restDocumentation.beforeTest(getClass(), method.getName());}

但是在每个测试之后ManualRestDocumentation.afterTest()方法都将被调用一次:

@AfterMethodpublic void tearDown() {this.restDocumentation.afterTest();}

3.Invoking the RESTful service

the testing framework被配置好之后,它就可以尝试去集成RESTful服务了,针对其中的请求和回复进行相应详细的说明。
举个栗子:(kotlin

    @Resource private lateinit var context: WebApplicationContext    private lateinit var mockMvc: MockMvc    //设置测试片段生成文件夹的位置    @Rule @JvmField var restDocumentation = JUnitRestDocumentation("target/asciidoc/snippets")    @Before    fun setUp() {        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)                .apply<DefaultMockMvcBuilder>(documentationConfiguration(this.restDocumentation))                .build()    }@Test    fun create() {    val parentId = "sssssssssss"    val request = post("/groups/$parent")    this.mockMvc.perform(request)                .andException(status.isCreated)                .andDo(print())                .andDo(document("{methodName}", preprocessRequest(prettyPrint())))    }

举个Spring REST Docs的官方文档中的例子:(java)
我们可以在andDo()中添加一些对参数的具体描述:

this.mockMvc.perform(get("/locations/{latitude}/{longitude}", 51.5072,0.1275))            .andExpect(status().isOk())            .andDo(document("locations",            pathParameters(parameterWithName("latitude").description("The location'slatitude"),parameterWithName("longitude").description("The location's longitude"))));

关于这些Spring REST Docs的官方文档中对参数的具体描述的一些用法我就不详细赘述了,因为这个很显然,代码上看起来不整齐,此时也已经有更好的东西代替它了,所以我们直接学习更好的解决“为属性和回复消息进行详细说明”的问题的解决方案就好啦~

以上是在SpringRESTDocs的官方文档中学习到的知识,做以简单的总结。


那么有什么好的解决方案呢?
就是我上一篇文章提到的springfox-swagger,它是一个能力的强者,为什么呢?话不多说,举个简单的栗子:
使用springfox-swagger这个框架的时候,我们是把api的详细注释都添加在Controller层的各个类中:
(kotlin)当然啦,想使用下面这段代码的模式,就需要配置集成springfox-swagger在你的项目中。(SpringBoot+SpringDataJPA+SpringFramework+Springfox-swagger)

@RestController@Api(value = "User", description = "the chapter of user")@RequestMapping("/users", produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))class ItemController {    @ApiOperation(value = "createUser", notes = "创建一个user") @ApiImplicitParams(    ApiImplicitParam(name = "Name", value = "用户名", paramType = "path", required = true, dataType = "String"),    ApiImplicitParam(name = "age", value = "用户的年龄", paramType = "path", required = true, dataType = "Int")    )    @ApiResponses(            ApiResponse(code = 200, message = "创建成功", response = User::class),            ApiResponse(code = 404, message = "没有找到", response = Void::class)    )    @PostMapping("{name}/age/{age}", consumes = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))    fun create(@PathVariable("name") userName: String, @PathVariable("age") userAge: Int): ResponseEntity<Any> {        //此处实现的代码省略        //......        return user    }}

关于Api的一些简单的注解,都在上一篇中有描述。


总结
(1)springRESTDocs主要是用来在测试的时候生成测试代码片段,并且在测试片段中添加一些简单的说明;
(2)swagger主要是用来提供测试界面,并整合生成API接口文档;
(3)MockMVC是一个属于spring MVC的测试框架,基于Restful风格的SpringMVC的测试,可以测试完成的流程。关于MockMVC的相关信息可以查看网址:http://www.cnblogs.com/lyy-2016/p/6122144.html他写的很棒,也可以看我之前写的博客。

代码举例:

下面 举一个完整的例子,结合springBoot+SpringDataJPA+SpringMVC+MockMVC+SpringRESTDocs+Springfox-swagger,完成生成一个具体的详细的API接口文档。

第一步:在Maven中配置Swagger2+SpringRESTDocs的jar包的依赖

mavne会自动下载你所需要的jar包

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->        <dependency>            <groupId>io.springfox</groupId>            <artifactId>springfox-swagger2</artifactId>            <version>2.6.1</version>        </dependency>        <dependency>            <groupId>io.springfox</groupId>            <artifactId>springfox-swagger-ui</artifactId>            <version>2.6.1</version>        </dependency>        <dependency>            <groupId>org.springframework.restdocs</groupId>            <artifactId>spring-restdocs-mockmvc</artifactId>            <version>1.1.2.RELEASE</version>            <scope>test</scope>        </dependency>        <dependency>            <groupId>io.springfox</groupId>            <artifactId>springfox-staticdocs</artifactId>            <version>2.6.1</version>        </dependency>

第二步:配置Swagger2

    import org.springframework.context.annotation.Bean    import org.springframework.context.annotation.Configuration    import org.springframework.http.ResponseEntity    import springfox.documentation.builders.ApiInfoBuilder    import springfox.documentation.builders.PathSelectors    import springfox.documentation.builders.RequestHandlerSelectors    import springfox.documentation.service.ApiInfo    import springfox.documentation.service.Contact    import springfox.documentation.spi.DocumentationType    import springfox.documentation.spring.web.plugins.Docket    import springfox.documentation.swagger.web.UiConfiguration    import springfox.documentation.swagger2.annotations.EnableSwagger2    @Configuration    @EnableSwagger2    class Swagger2Config {    @Bean    fun restfulApi(): Docket {        return Docket(DocumentationType.SWAGGER_2)                .genericModelSubstitutes(ResponseEntity::class.java)                .useDefaultResponseMessages(true)                .forCodeGeneration(true)                .select()                .apis(RequestHandlerSelectors                    .basePackage("{你扫描API注解的包}"))                .paths(PathSelectors.any())                .build()                .pathMapping("/")                .apiInfo(apiInfo())    }    private fun apiInfo(): ApiInfo {        return ApiInfoBuilder()                .title("API文档")                .description(this.description)                .termsOfServiceUrl("http://terms.XXX.com")                .contact(Contact("联系方式"))                .version("1.0.0")                .build()    }

这里的@Configuration注解,让spring加载配置,@EnableSwagger2启用Swagger2
再通过createRestApi函数创建Docket的Bean之后,apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现,本例采用指定扫描的包路径来定义,Swagger会扫描该包下所有Controller定义的API,并产生文档内容(除了被@ApiIgnore指定的请求)。

第三步:设置swagger整合片段生成api的一些规范

    import io.github.robwin.markup.builder.MarkupLanguage    import io.github.robwin.swagger2markup.GroupBy    import io.github.robwin.swagger2markup.Swagger2MarkupConverter    import org.junit.Before    import org.junit.Test    import org.junit.runner.RunWith    import org.springframework.boot.test.context.SpringBootTest    import org.springframework.http.MediaType    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner    import org.springframework.test.web.servlet.MockMvc    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get    import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status    import org.springframework.test.web.servlet.setup.MockMvcBuilders    import org.springframework.web.context.WebApplicationContext    import springfox.documentation.staticdocs.Swagger2MarkupResultHandler.outputDirectory    import springfox.documentation.staticdocs.SwaggerResultHandler    import javax.annotation.Resource    @Test    @Throws(Exception::class)    fun convertToAsciiDoc() {        // 得到swagger.json,写入outputDir目录中        this.mockMvc.perform(get("/v2/api-docs").accept(MediaType.APPLICATION_JSON))                .andDo(SwaggerResultHandler.outputDirectory(outputDir).build())                .andExpect(status().isOk)                .andReturn()        // 读取上一步生成的swagger.json转成asciiDoc,写入到outputDir        // 这个outputDir必须和插件里面<generated></generated>标签配置一致        Swagger2MarkupConverter.from(outputDir + "/swagger.json")                .withPathsGroupedBy(GroupBy.XXX)// 按XXX排序                .withMarkupLanguage(MarkupLanguage.ASCIIDOC)// 格式                .withExamples(snippetDir)                .build()                .intoFolder(outputDir) // 输出    }

在单元测试的时候,SpringRestDocs会生成一些代码片段,这里就是将片段整合先生成一个json文件,再转换格式的过程。

第四步:在测试类中添加SpringRestDocs和Mockmvc的一些代码,进行单元测试

    @Resource private lateinit var context: WebApplicationContext    private lateinit var mockMvc: MockMvc    @Rule @JvmField var restDocumentation =                 JUnitRestDocumentation("{SpringRestDocs生成测试片段的文件的绝对路径}")    @Before    fun setUp() {        this.mockMvc = MockMvcBuilders.webAppContextSetup(context)                .apply<DefaultMockMvcBuilder>(documentationConfiguration(this.restDocumentation))                .build()    }

其中.apply(documentationConfiguration(this.restDocumentation))就是将MockMVC和SpringRestDocs结合在一起。
@Rule 表示当前是在Junit测试环境下的测试

    @Test    fun create() {        val json = """                {                    "userName" : "userName",                    "age" : "24"                }             """        val request = post("/users")                .contentType("{生产类型}")                .content(json)                .accept("{消费类型}")        this.mockMvc.perform(request)                .andExpect(status().isCreated)                .andDo(print())                .andDo(document("createUser", preprocessResponse(prettyPrint())))    }

这里的andDo()之类的用法 ,我在之前的文章中都有讲过。这些都是用来模拟一个Mockmvc的请求。
这里的.andDo(document(“createUser”, preprocessResponse(prettyPrint())))是属于SpringRestDocs在生成的测试代码片段中加的一些简单的注释

第五步:在你设置的swagger2扫描的包下,填写api的注解,完善生成api文档中对属性和回复消息的内容。这里的扫描路径一般情况下都是SpringMVC中的Controller层

 @ApiOperation(value = "createUser", notes = "创建一个user") @ApiImplicitParams(    ApiImplicitParam(name = "Name", value = "用户名", paramType = "path", required = true, dataType = "String"),    ApiImplicitParam(name = "age", value = "用户的年龄", paramType = "path", required = true, dataType = "Int")    )    @ApiResponses(            ApiResponse(code = 200, message = "创建成功", response = User::class),            ApiResponse(code = 404, message = "没有找到", response = Void::class)    )    @PostMapping("{name}/age/{age}", consumes = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))    fun create(@PathVariable("name") userName: String, @PathVariable("age") userAge: Int): ResponseEntity<Any> {       //照例 ,中间实现的一些代码省略        return User    }

这里所有的api注解,在单元测试完成之后,运行Swagger2的test,都能被整合到api接口文档中。其他的就不再赘述了。


over啦~
待完善revision

原创粉丝点击