栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

八、开发者工具与单元测试

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

八、开发者工具与单元测试

本章概要
  • devtools 简介
  • devtools 实战
  • 单元测试
8.1 devtools 简介

Spring Boot 中提供了一组开发工具 spring-boot-devtools ,可以提高开发者的工作效率,开发者可以将该模块包含在任何项目中,spring-boot-devtools 最方便的地方莫过于热部署了。

8.2 devtools 实战

想要在项目中加入 devtools 模块,只需要添加相关依赖即可

注意:这里多了一个 optional 选项,是为了防止将 devtools 依赖传递到其它模块中。当开发者将应用打包运行后,devtools 会自动被禁用。


  org.springframework.boot
  spring-boot-devtools
  true

当开发者将 spring-boot-devtools 引入项目后,只要 classpath 路径下的文件发生了变化,项目就会自动重启,这极大的提高了项目的开发速度。
如果开发者使用了 eclipse ,那么在修改完代码并保存后,项目将自动编译并处罚重启,如果使用了 IDEA ,默认情况下,需要开发者手动编译才会触发重启。手动编译时,单击 Build->Build Project 菜单或者按 Ctrl + F9 快捷键进行编译,编译成功后就会触发项目重启。当然,使用 IDEA 也可以配置项目自动编译,步骤如下:
步骤01:单击 File->Settings 菜单,打开 Setting 页面,在左边的菜单栏一次找到 Build,Execution,Deployment->Compile,勾选 Build project automatically,如图:

步骤02:按 Ctrl+Shift+Alt+/ 快捷键调出 Maintenance 页面,如图

单击 Registry ,在新打开的 Registry 页面中,勾选以下复选框

做完这两步配置后,若开发者在此在 IDEA 中修改代码,则项目会自动重启。
注意:classpath 路径下的静态资源或视图模板等发生变化时,并不会导致项目重启。

8.2.2 基本原理

Spring Boot 中使用的自动重启技术涉及两个类加载器,一个是 baseclassloader ,用来加载不会变化的类,例如项目引用的第三方的 jar ;另一个是 restartclassloader,用来加载开发者自己写的会变化的类。
当项目需要重启时,restartclassloader 将被一个新创建的类加载器代替,而 baseclassloader 则继续使用原来的,这种启动方式要比冷启动快得多,因为 baseclassloader 已经存在并且已经加载好了。

8.2.3 自定义监控资源

默认情况下,/META-INF/maven、/META-INF/resources、/resources、/static、/public 以及 /templates位置下资源的变化并不会触发重启,如果想要对这些位置进行重定义,在application.properties中添加如下配置即可:

spring.devtools.restart.exclude=static/**

这表示从默认不触发重启的目录中除去 static 目录,即 classpath:static目录下的资源发生变化时也会导致项目重启。用户也可以反向配置需要监控的目录,配置方式如下:

spring.devtools.restart.additional-paths=src/main/resources/static

这个配置表示当src/main/resources/static 目录下的文件发生变化时,自动重启项目。
由于项目的编码过程是一个连续的过程,并不是每修改一行代码就要重启项目,这样不仅浪费电脑性能,而且没有实际意义。鉴于这种情况,开发者也可以考虑使用触发文件,触发文件是一个特殊的文件,当这个文件发生变化时项目就会重启,配置方法如下:

spring.devtools.restart.trigger-file=.trigger-file

在项目 resources 目录下创建一个名为 .trigger-file 的文件,此时开发者修改代码时,默认情况下项目不会重启,需要项目重启时,开发者只需要修改 .trigger-file 文件即可。但是注意,如果项目没有改变,只是单纯地改变了 .trigger-file 文件,那么项目不会重启。

8.2.4 使用 LiveReload

devtools 默认嵌入了 LiveReload 服务器,可以解决静态文件的热部署,LiveReload 可以在资源发生变化时自动触发浏览器更新,LiveReload 支持 Chrome、Firefox、Safari。以 Chrome 为例,在 Chrome 应用商店搜索 LiveReload ,如图

没搜到,本小结 略

8.2.5 禁用自动重启

如果开发者添加了 spring-boot-devtools 依赖但是不想使用自动重启特性,那么可以关闭自动重启

spring.devtools.restart.enabled=false
8.2.6 全局配置

如果项目模块众多,开发者可以在当前用户目录下创建.spring-boot-devtools 文件来对 devtools 进行全局配置,这个配置文件适用于当前计算机上任何使用了 devtools 模块的 Spring Boot 项目。例:E:Giteechapter04srcmainresources 目录下创建.spring-boot-devtools.properties 文件,内容如下:

spring.devtools.restart.trigger-file=.trigger-file

此时就实现了使用触发文件触发项目重启。

8.3 单元测试 8.3.1 基本用法

使用 IDEA 创建一个 Spring Boot 项目时,创建成功后,默认都会添加 spring-boot-starter-test 依赖,并且创建好了测试类,代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
class Test01ApplicationTests {

    @Test
    void contextLoads() {
    }

}

代码解释:

  • 首先使用了 @RunWith 注解,该注解将 JUnit 执行类修改为 SpringRunner,而SpringRunner 是 Spring Framework 中测试类 SpringJUnit4ClassRunner 的别名。
  • @SpringBootTest 注解除了提供 Spring TestContext 中的常规测试功能之外,还提供了其它特性:提供默认的 ContextLoader、自动搜索 @Spring BootConfiguration、自定义环境属性、为不同的 webEnvionment 模式提供支持,这里的 webEnvironment 模式主要有四种:

(1)MOCK,这种模式是当 classpath 下存在 servletAPIS 时,就会创建 WebApplicationContext 并提供一个 mockservlet 环境;当 classpath 下存在 Spring WebFlux 时,则创建 ReactiveWebApplicationContext;若都不存在,则创建一个常规的 ApplicationContext。
(2)RANDOM_PORT,这种模式将提供一个真实的 Servlet 环境,使用内嵌的容器,但是端口随机。
(3)DEFINED_PORT,这种模式也将提供一个真实的 Servlet 环境,使用内嵌的容器,但是使用定义好的端口。
(4)NONE,这种模式则加载一个普通的 ApplicationContext,不提供任何 Servlet 环境。这种一般不适用于 Web 测试。

  • 在 Spring 测试中,开发者一般使用 @ContextConfiguration(class = )或者 @ContextConfiguration(locations = ) 来指定要加载的 Spring 配置,而在 Spring Boot 中则不需要这么麻烦,Spring Boot 中的 @*Test 注解将会去包含测试类的包下查找带有 @SpringBootApplication 或者 @Spring BootConfiguration 注解的主配置类
  • @Test 注解则来自 junit,Junit 中的 @After 、 @AfterClass、@Before、@BeforeClass、@Ignore 等注解一样可以在这里使用
8.3.2 Service 测试

Service 层的测试就是常规测试,例如有个 HelloService 如下:

@Service
public class HelloService {
    public String sayHello(String name) {
        return "Hello " + name + " !";
    }
}

需要对 HelloService 进行测试,直接在测试类中注入 HelloService 即可:

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test01ApplicationTests {
    @Autowired
    HelloService helloService;
    @Test
    public void contextLoads(){
        String hello = helloService.sayHello("唐三");
        Assert.assertThat(hello, Matchers.is("Hello 唐三 !"));
    }
}

在测试类中注入 HelloService ,然后调用相关方法即可。使用 Assert 判断测试结果是否正确。

8.3.3 Controller 测试

Controller 测试则要使用到 Mock 测试,即对一些不易获取的对象采用虚拟的对象来创建进而方便测试。而 Spring 中提供的 MockMvc 则提供了对 HTTP 请求的模拟,使开发者能够在不依赖网络环境的情况下对 Controller 的快速测试。

@RestController
public class HelloController {

    @Autowired
    HelloService helloService;
    @GetMapping("/hello")
    public String hello(String name) {
        return helloService.sayHello(name);
    }

    @PostMapping("/book")
    public String addBook(@RequestBody Book book) {
        return book.toString();
    }
}
public class Book {
    private Integer id;
    private String name;
    private String author;

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", author='" + author + ''' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

如果要对这个 Controller 进行测试,就需要借助 MockMvc,如下

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test01ApplicationTests {
    @Autowired
    WebApplicationContext wac;
    MockMvc mockMvc;

    @Before
    public void before() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void test1() throws Exception {
        MvcResult mvcResult = mockMvc.perform(
                MockMvcRequestBuilders
                        .get("/hello")
                        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                        .param("name", "Michael"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
        System.out.println(mvcResult.getResponse().getContentAsString());
    }

    @Test
    public void test2() throws Exception {
        ObjectMapper om = new ObjectMapper();
        Book book = new Book();
        book.setAuthor("罗贯中");
        book.setName("三国演义");
        book.setId(1);
        String s = om.writeValueAsString(book);
        MvcResult mvcResult = mockMvc
                .perform(MockMvcRequestBuilders
                        .post("/book")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(s))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andReturn();
        System.out.println(mvcResult.getResponse().getContentAsString());
    }
}

代码解释:

  • 注入一个 WebApplicationContext 用来模拟 ServletContext 环境
  • 声明一个 MockMvc 对象,并在每个测试方法执行前进行 MockMvc 的初始化操作(before)
  • 调用 MockMvc 中的 perform 方法开启一个 RequestBuilder 请求,具体的请求则通过 MockMvcRequestBuilder 进行构建,调用 MockMvcRequestBuilder 中的 get 方法表示发起一个 GET 请求,调用 post 方法则发起一个 POST 请求,其他的 DELETE 和 PUT 请求也是一样的,最后通过调用 param 方法设置请求参数
  • .andExpect(MockMvcResultMatchers.status().isOk()) 表示添加返回值的验证规则,利用 MockMvcRequestBuilder 进行验证,这里表示验证相应码是否为 200
  • .andDo(MockMvcResultHandlers.print()) 表示将请求详细信息打印到控制台
  • .andReturn() 表示返回相应的 MvcResult ,并使用 mvcResult.getResponse().getContentAsString() 打印出来
  • test2 方法演示了 POST 请求如何传递 JSON 数据,首先使用 om.writeValueAsString(book) 将一个 book 对象转换为一段 JSON,然后设置请求类型 .contentType(MediaType.APPLICATION_JSON) ,最后设置请求内容 .content(s)

除了 MockMvc 这种测试方式之外,Spring Boot 还专门提供了 TestRestTemplate 用来实现集成测试,若开发者使用了 @Spring BootTest 注解,则 TestRestTemplate 将自动可用,直接在测试类中注入即可。注意,如果要使用 TestRestTemplate 进行测试,需要将 @Spring BootTest 注解中 webEnvironment 属性的默认值由 WebEnvironment.MOCK 修改为 WebEnvironment.DEFINED_PORT 或者 WebEnvironment.RANDOM_PORT ,因为这两种都是使用一个真实的 Servlet 环境而不是模拟的 Servlet 环境

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Test01ApplicationTests {
    @Autowired
    TestRestTemplate restTemplate;

    @Test
    public void test3() {
        ResponseEntity hello = restTemplate.getForEntity("/hello?name={0}",String.class,"唐三");
        System.out.println(hello);
    }
}
8.3.4 JSON 测试

开发者可以使用 @JsonTest 测试 JSON 序列化和反序列化是否工作正常,该注解将自动配置 Jackson ObjectMapper、@JsonComponent 以及 Jackson Modules。如果开发者使用 Gson 代替 Jackson,该注解将配置 Gson。

@RunWith(SpringRunner.class)
@JsonTest
public class JSONTest {
    @Autowired
    JacksonTester jacksonTester;
    @Test
    public void testSerialize() throws IOException {
        Book book = new Book();
        book.setId(1);
        book.setName("三国演义");
        book.setAuthor("罗贯中");
        Assertions.assertThat(jacksonTester.write(book)).isEqualToJson("book.json");
      Assertions.assertThat(jacksonTester.write(book)).hasJsonPathStringValue("@.name");
        Assertions.assertThat(jacksonTester.write(book)).extractingJsonPathStringValue("@.name").isEqualTo("三国演义");
    }
    @Test
    public void testDeserialize() throws Exception {
        String content = "{"id":1,"name":"三国演义","author":"罗贯中"}";
        Assertions.assertThat(jacksonTester.parseObject(content).getName())
                .isEqualTo("三国演义");
    }
}

代码解释:

  • 添加 Jackson 注解(@JsonTest),然后注入 JacksonTester 进行 JSON 的序列化和反序列化测试
  • Assertions.assertThat(jacksonTester.write(book)).isEqualToJson(“book.json”) 在序列化完成后判断序列化结果是否是所期待的 json,book.json 是一个定义在当前包下的 JSON 文件。
  • Assertions.assertThat(jacksonTester.write(book)).hasJsonPathStringValue(“@.name”) 判断对象序列化之后生成的 JSON 中是否有一个名为 name 的 key
  • Assertions.assertThat(jacksonTester.write(book)).extractingJsonPathStringValue(“@.name”).isEqualTo(“三国演义”) 判断序列化后 name 对应的值是否为 “三国演义”
  • testDeserialize() 方法是反序列化,反序列化完成是判断对象的 name 属性是否为“三国演义”
  • book.json 内容如下
{"id":1,"name":"三国演义","author":"罗贯中"}
转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1040108.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号