Spring Boot学习笔记2:使用Junit4单元测试

  • 添加单元测试依赖
  • 基本单元测试
  • 基本注解
  • Assert
  • Dao层的单元测试
  • Service层的单元测试
  • Controller层的单元测试
  • GitHub示例


添加单元测试依赖

Spring Boot中引入单元测试很简单,依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Spring Boot 2.1.3.RELEASE,引入spring-boot-starter-test后,有如下几个库:

  • JUnit: The de-facto standard for unit testing Java applications.
  • Spring Test & Spring Boot Test: Utilities and integration test
  • support for Spring Boot applications. AssertJ: A fluent assertion
  • library. Hamcrest: A library of matcher objects (also known as
  • constraints or predicates). Mockito: A Java mocking framework.
  • JSONassert: An assertion library for JSON. JsonPath: XPath for JSON.

基本单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test {
	@Test
    public void test() {
        Assert.assertEquals("O大叔Tz","O大叔Tz");
    }
}

基本注解

属性

功能

@RunWith

在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。 需要注意:如果我们使用的是JUnit 4 ,那么需要添加@RunWith(SpringRunner.class)否则所有注解将会被忽略。如果你使用的是JUnit5 ,那么在 SpringBootTest 上没有必要添加与@SpringBootTest等效的@ExtendWith(SpringExtension),因为@…Test已经添加了。

@SpringBootTest

用于SpringBoot应用测试,默认根据包名逐级往上查找直到找到@SpringBootApplication

@BeforeClass

在所有测试方法前执行一次,一般在其中写上整体初始化的代码

@AfterClass

在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码

@Before

在每个测试方法前执行,一般用来初始化方法(比如我们在测试别的方法时,类中与其他测试方法共享的值已经被改变,为了保证测试结果的有效性,我们会在@Before注解的方法中重置数据)

@After

在每个测试方法后执行,在方法执行完成后要做的事情

@Test

编写一般测试用例

@Test(timeout = 1000)

测试方法执行超过1000毫秒后算超时,测试将失败

@Test(expected = Exception.class)

测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败

@Ignore(“not ready yet”)

执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类

Assert

方法

功能

assertEquals(“message”,A,B)

判断A与B是否相等,调用equals方法,如不相等则报错

assertSame(“message”,A,B)

判断A与B是否相等,使用==操作符,如不相等则报错

assertTrue(“message”,A)

判断A条件是否为真,如不为真则报错

assertFalse(“message”,A)

判断A条件是否不为真,如不符合则报错

assertNotNull(“message”,A)

判断A是否不为空,如为空则报错

assertArrayEquals(“message”,A,B)

判断A数组与B数组是否相等,如不相等则报错

JUnit4支持assertThat()等更方便灵活的断言方法

Dao层的单元测试

Dao层的测试需要准备一个空数据库,以及一些初始化的数据,使用@Sql注解来初始化。

首先在classpath下准备一个初始化数据的脚本文件user.sql,内容如下:

INSERT INTO `user` (`user_id`, `name`) VALUES (99, '大叔');

测试代码:

// @ActiveProfiles激活application-test.properties文件和@profile注解作为profile,使用准备好的空数据库环境
@ActiveProfiles("test")
// @Transactional测试执行后回滚数据
@Transactional
// 执行sql,‘/’开头表示从classpath根目录开始搜索,没有以此开头默认在测试类所在包下。也可使用classpath:、file:、http: 开头
@Sql({"/user.sql"})
// @Runwith是JUnit标准的一个注解,Spring的单元测试都用SpringRunner.class
@RunWith(SpringRunner.class)
// @SpringBootTest用于Spring Boot应用测试,默认根据报名逐级往上寻找应用启动类
@SpringBootTest
public class UserDaoTest {

    @Autowired
    private UserDao userDao;

    @Test
    public void testGetOne() {
        User user = userDao.getOne(99);
        Assert.assertNotNull(user);
        Assert.assertEquals("大叔", user.getName());
        Assert.assertSame("不一样!", 99, user.getId());
    }
}

Service层的单元测试

Service层是处理业务逻辑的地方,通常比较复杂,编写单元测试代码前需要处理好以下三个问题:

  • 保证可重复测试。一个service方法可以多次测试,因此测试完毕后数据要能自动回滚。
  • 模拟未完成的Service。当前测试的Service依赖的其它Service未开发完毕时,要能模拟其它Service。
  • 准备测试数据。单元测试前要模拟好测试场景的数据。

如何解决呢?

  • 对于第一个问题,可以使用Spring提供的@Transactional注解进行事务回滚。
  • 对于第二个问题,使用Spring Boot集成的Mockito来模拟未完成的Service或者不能随便调用的Service。
  • 对于第三个问题,@Sql注解。

下面来看一下使用 Mockito 解决第二个问题的示例。

假设现有UserService依赖于CreditSystemServicegetUserCredit(int id) 方法,但是CreditSystemService并没有实现。

@Transactional
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceImplTest {

    // UserService中依赖了CreditSystemService,但是CreditSystemService没有实现类
    @Autowired
    private UserService userService;

    // 注解@MockBean 可以自动注入Spring管理的Service,这里creditSystemService由Mockito工具产生。
    @MockBean
    private CreditSystemService creditSystemService;

    @Test
    public void getCredit() throws Exception {
       // given(需要模拟的方法).willReturn(模拟返回的结果)
       BDDMockito.given(creditSystemService.getUserCredit(BDDMockito.anyInt())).willReturn(100);
       Assert.assertEquals(100, userService.getCredit(userId));
    }
}

Controller层的单元测试

Spring MVC Test 通过@WebMvcTest 来完成MVC单元测试。示例代码如下:

@RunWith(SpringRunner.class)
// @WebMvcTest表示这是一个MVC测试,只限于单个Controller。
@WebMvcTest(UserController.class)
public class UserControllerTest {

    // MockMvc是Spring专门提供用于测试SpringMVC类
    @Autowired
    private MockMvc mockMvc;
    
    // @MockBean 用来模拟实现,因为在Spring MVC测试中并不会初始化@Service注解的类,需要自己模拟service实现。
    @MockBean
    private UserService userService;
    
    //MockMvc模拟的session
	//@Autowired
    //private MockHttpSession session;
    
	//@Before
	//public void setupMockMvc(){
	   	//User user =new User(99,"大叔");
   		//session.setAttribute("user",user); //给session里放个attribute
	//}

    @Test
    public void getUser() throws Exception {
        int userId = 99;
        BDDMockito.given(userService.getCredit(userId)).willReturn(100);
        // perform,完成一次MVC调用,Spring MVC Test是servlet容器内部的模拟测试,不会发起真正的HTTP请求。
        // get,模拟一次GET请求。
        // andExpect,表示请求期望的返回结果
        mockMvc.perform(MockMvcRequestBuilders.get("/user/{id}", userId))
                .andExpect(status().isOk()).andExpect(MockMvcResultMatchers.content().string(String.valueOf(100)));
    }
}

MockMvc方法

功能

perform

执行一个MockHttpServletRequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处。MockHttpServletRequestBuilder.get(String urlTemplate, Object... urlVariables):根据uri模板和uri变量值得到一个GET请求方式的。另外提供了其他的请求的方法,如:postputdelete等。

param

添加request的参数,如发送请求的时候带上.param("id",99),可以重复添加。假如使用需要发送json数据格式的时将不能使用这种方式,可见后面被@ResponseBody注解参数的解决方法。

session

模拟注入一个MockHttpSession。

cookie

模拟注入一个Cookie。

andExpect

添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确(对返回的数据进行的判断),StatusResultMatchers对请求结果进行验证,ContentResultMatchers对请求返回的内容进行验证。

andDo

添加ResultHandler结果处理器,比如调试时打印结果到控制台(对返回的数据进行的判断)。

andReturn

最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理(对返回的数据进行的判断)。