目录
前言
单元测试是软件开发中的一种关键测试类型,它是指对软件中的最小可测试单元进行检查和验证。对于面向对象编程,最小单元就是方法,独立的函数或过程也可以是最小单元。
在 Java 中,通常一个单元测试属于一种特定的测试工具框架,如 JUnit,它们可以很容易地插入到自动化构建过程或持续集成工具中。
单元测试的主要目标是隔离软件系统的各部分,并逐个测试。这将确保每个部分都按照预期工作。单元测试有助于提高软件质量,而且也使得代码在修改后更容易维护。
1.RESTful API 接口的单元测试
一个 RESTful API 接口项目是最适合进行单元测试的,因为逻辑单元足够小,符合单元测试的定义。
Spring Boot 提供了 Spring Test 模块进行单元测试,还需要搭配 spring-boot-test-autoconfigure 实现测试的自动化配置。
Spring Test 是使用 junit 作为默认的单元测试模块,junit 的常用注解如下。
注解 |
用处 |
@Test |
修饰某个方法为测试方法 |
@Before |
在每个测试方法执行前执行一次 |
@After |
在每个测试方法执行后执行一次 |
@AfterClass |
在所有测试方法执行之后运行 |
@RunWith |
更改测试运行器 |
@BeforeClass |
在所有测试方法执行前运行 |
@Igore |
修饰的类火灾方法被忽略 |
添加单元测试和项目所需的所有模块依赖,pom.xml 内容代码如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.freejava</groupId> <artifactId>unit-test-restfulapi</artifactId> <version>0.0.1-SNAPSHOT</version> <name>unit-test-restfulapi</name> <description>Unit test project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
设计一个操作玩具数据的 restful 接口,具体见设计表。
请求类型 |
URL |
备注说明 |
GET |
/toys |
查询玩具列表 |
POST |
/toys |
创建玩具数据 |
PUT |
/toys/id |
根据 id 更新玩具 |
DELETE |
/toys/id |
根据 id 删除玩具 |
定义一个玩具实体类
package com.freejava.unittestrestfulapi.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Toy { private Long id; private String name; private Float price; private String desc; }
定义一个玩具控制器
package com.freejava.unittestrestfulapi.controller; import com.freejava.unittestrestfulapi.entity.Toy; import org.springframework.web.bind.annotation.*; import java.util.*; @RestController public class ToyController { // 创建线程安全的Map对象 static Map<Long, Toy> toys = Collections.synchronizedMap(new HashMap<Long, Toy>()); / * 获取玩具列表 * * @return */ @GetMapping(value = "/toys") public List<Toy> getToyList() { List<Toy> toyLists = new ArrayList<Toy>(toys.values()); return toyLists; } / * 创建玩具 * * @param toy * @return */ @RequestMapping(value = "/toys", method = RequestMethod.POST) public String createToy(@ModelAttribute Toy toy) { toys.put(toy.getId(), toy); return "success"; } / * 通过id获取玩具 * * @param id * @return */ @GetMapping(value = "/toys/{id}") public Toy getToy(@PathVariable Long id) { return toys.get(id); } / * 更新玩具 * * @param toy * @return */ @PutMapping(value = "/toys") public String updateToy(@RequestBody Toy toy) { Toy t = toys.get(toy.getId()); t.setDesc(toy.getDesc()); toys.put(toy.getId(), t); return "success"; } / * 根据id删除玩具 * * @param id * @return */ @DeleteMapping(value = "/toys/{id}") public String deleteToy(@PathVariable Long id) { toys.remove(id); return "success"; } }
编写单元测试,将测试编写在 test 目录下的文件中(与源目录相对应)。
package com.freejava.unittestrestfulapi; import com.freejava.unittestrestfulapi.controller.ToyController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class TestToyController { @Autowired private ToyController restController; private MockMvc mvc; @Before public void setUp() throws Exception { mvc = MockMvcBuilders.standaloneSetup(restController).build(); } @Test public void testToyController() throws Exception { // 测试UserController RequestBuilder request = null; // get查一下toy列表,目前应该为空 request = get("/toys/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); // 使用post方式提交一个toy数据 request = post("/toys/") .param("id", "1") .param("name", "功夫熊猫") .param("price", "210.00") .param("desc", "同名电影玩偶"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // get方式获取toys列表 request = get("/toys").characterEncoding("UTF-8"); mvc.perform(request).andExpect(status().isOk()).andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"功夫熊猫\",\"price\":210.00, \"desc\":\"同名电影玩偶\"}]"))); // put方式修改id为1的toy request = put("/toys/1").param("name", "葫芦小精钢"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // delete方式删除id为1的toy request = delete("/toys/1"); mvc.perform(request).andExpect(content().string(equalTo("success"))); } }
上面的这段代码将所有 toys 接口都依次进行测试,使用 IDE 运行该测试文件即可。
2.模拟数据测试
在单元测试过程中有时候会依赖上下游的接口服务和相关数据,例如要测试结算全流程,需要先进行商品选择,然后下单、支付,最后才能进行商家结算。如果只是单纯想测试中间的某一个环境,那么就需要上一步提供数据和服务,依赖性很强。在比较大的团队中,由于各自排期不同,所需的数据接口可能不稳定或者处于正在开发中,而又需要进行单元测试,那么可以考虑 Mock(模拟)数据来测试。
Spring Boot 提供了一款模拟数据的框架:Mockito。它的功能非常强大,可以进行模拟工作,如可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等。例如,当 Class A 需要 Class B 和 Class C 提供的接口时,可以考虑分别模拟出 Class B 和 Class C ,如图所示。
测试项目依赖;
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>unit_mock</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.9.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
口袋妖怪实体类
package org.example.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class PocketMonster { // 编号 ID private long id; // 口袋要挂名称 private String name; //类型,如水系 private String classType; //技能 private String skill; public PocketMonster(Long id, String name) { this.id = id; this.name = name; } }
service文件
package org.example.service; import org.example.dao.PocketMonsterDao; import org.example.entity.PocketMonster; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class PocketMonsterServicec { @Autowired private PocketMonsterDao pocketMonsterDao; public PocketMonster getPocketMonsterServicecById(Long id){ return pocketMonsterDao.getpocketMonsterDaoById(id); } }
测试类:
package org.example; import org.example.entity.PocketMonster; import org.example.service.PocketMonsterServicec; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class PocketMonsterTest { @Autowired private PocketMonsterServicec pocketMonsterServicec; @Test public void getPocketMonsterById() { //定义当调用 PocketMonsterDao 的 PocketMonsterDao方法的时候 //,并且参数为 2的时候,则返回 id 为 300,名称为 little fire dragon 对象 Mockito.when(pocketMonsterServicec.getPocketMonsterServicecById((long) 2)) .thenReturn(new PocketMonster(300L, "little fire dragon")); //返回上面设置好的对象 PocketMonster monster = pocketMonsterServicec.getPocketMonsterServicecById(2L); //开始断言 Assert.assertNotNull(monster); Assert.assertEquals(monster.getId(), 300L); Assert.assertEquals(monster.getName(), "little fire dragon"); } }
如上述代码通过使用 Mockito 对象的 when 和 thenReturn 方法分别设置模拟的条件和模拟请求的返回值,这样就能顺利地进行模拟测试。
3.使用 Spring Boot Test 进行测试
Spring Boot Test 是在Spring Test 的基础上二次封装而来的库,增加了基于 AOP 的测试,还提供了模拟能力。
Spring Boot Test 支持单元测试,切片测试以及完整的功能测试,安装方式也很简单,如初始化时已经默认将 spring-boot-test 添加到 pom.xml 。所谓切片测试,就是介于单元测试和集成测试中间的范围的一些特定组件的测试。这些组件如 MVC 中的 Controller,JDBC 数据库访问、Redis 客户端等,需要在特定的环境下才能被正常执行。
常用注解和基本用法
@RunWith(SpringRunner.class) @SpringBootTest public class OrderServiceTests { @Autowired OrderService orderService; @Test public void contextLoads() { } }
(1)@RunWith:设置运行的环境,此处 Junit 执行的类设置为 SpringRunner,一般不会修改。
(2)@SpringBootTest: 注解表示会自动检索程序的配置文件,检索顺序是从当前包开始,逐级向上查找被 @SpringBootApplication 或 @SpringBootConfiguration 注解的类。除了基础的测试功能,还提供了 ContextLoader。
(3)@Test :可以加载需要被测试的方法上,Spring 仅加载相关的 bean,无关内容不被加载。添加了@Test 注解之后 junit 上的注解如@After、@Before、@AfterClass 都可以在这里使用。
根据用途的不同,分别总结配置类型注解
配置注解表
注解 |
说明 |
@TestComponent |
指定某个Bean专门用于测试 |
@TestConfiguration |
类似@ TestComponent,补充额外的Bean或已经覆盖的 Bean |
@TestExcludeFilters |
排除当前被注解的类或者方法,一般不常用 |
@EnableAutoConfiguration |
加在启动类,会开启自动配置,自动生成一些 Bean |
@OverrideAutoConfiguration |
用于覆盖 @EnableAutoConfiguration 和 @ImportAutoConfiguration |
自动配置注解表
注解 |
说明 |
@AutoConfiureDataJpa |
自动配置JPA |
@AutoConfiureJson |
自动配置Json |
@AutoConfiureMockMvc |
自动配置MockMvc |
@AutoConfiureWebMvc |
自动配置WebMvc |
@AutoConfiureWebClient |
自动配置WebClient |
启动测试类型注解
注解 |
说明 |
@SpringBootTest |
自动检索程序的配置文件 |
@Test |
被修饰的方法接口将被测试 |
@JsonTest |
用于测试Json数据的反序列化和序列化 |
@WebMvcTest |
测试SpringMvc中的controllers |
@DataJdbcTest |
测试基于SpringDataJDBC的数据库操作 |
使用 SpringBootTest 测试Service
测试用的服务类
package com.freejava.unittestrestfulapi.service; import org.springframework.stereotype.Service; @Service public class OrderService { public String getOrder(String name) { return "This " + name + " is my order"; } }
编写测试代码就是直接在测试类中引用它。
@RunWith(SpringRunner.class) @SpringBootTest public class OrderServiceTests { @Autowired OrderService orderService; @Test public void contextLoads() { String orderString = orderService.getOrder("FreeJava"); Assert.assertThat(orderString, Matchers.is("This FreeJava is my order")); } }
运行该测试代码,可以全部通过断言判断。
使用 SpringBootTest 测试Controller
测试 Controller 就是在需要测试的 Controller 文件上增加注解,例如针对 UserController 增加注解并编写独立的测试类代码。
测试所需的用户实体类:
package com.freejava.unittestrestfulapi.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private String password; }
编写一个简单的控制器,分别编写 hi 方法和 addUser 方法,具体代码如下:
package com.freejava.unittestrestfulapi.controller; import com.freejava.unittestrestfulapi.entity.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; / * 用于测试的User控制器 */ @RestController public class UserController { // 只返回一个say hi的字符串 @GetMapping("/hi") public String hi(String username) { return "Hey, " + username + "!Fighting!"; } // 添加用户 @PostMapping("/user") public String addUser(@RequestBody User user) { return user.toString(); } }
最后编写测试类 UserApplicationTests,具体代码如下:
package com.freejava.unittestrestfulapi; import com.fasterxml.jackson.databind.ObjectMapper; import com.freejava.unittestrestfulapi.entity.User; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @RunWith(SpringRunner.class) @SpringBootTest public class UserApplicationTests { @Autowired WebApplicationContext wa; MockMvc mockMvc; @Before public void setUp() throws Exception { mockMvc = MockMvcBuilders.webAppContextSetup(wa).build(); } @Test public void testHi() throws Exception { MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/hi") .contentType(MediaType.APPLICATION_FORM_URLENCODED) .param("username", "freephp")) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()) .andReturn(); System.out.println(mvcResult.getResponse().getContentAsString()); } @Test public void testAddUser() throws Exception { ObjectMapper objMapper = new ObjectMapper(); User user = new User(); user.setId((long) 1); user.setName("CDC极客君"); user.setPassword("chengdu_is_great"); String s = objMapper.writeValueAsString(user); MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders .post("/user") .contentType(MediaType.APPLICATION_JSON) .content(s)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); System.out.println(mvcResult.getResponse().getContentAsString()); } }
总结
到此这篇Spring Boot 单元测试_java单元测试工具的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!单元测试的主要目标是隔离软件系统的各部分,并逐个测试。这将确保每个部分都按照预期工作。单元测试有助于提高软件质量,而且也使得代码在修改后更容易维护。
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/te-unit/8195.html