Spring Test单元测试

单元测试的好处?

  • 缩短发现问题到解决问题的速度
  • 验证程序修改后是否正确
  • 如果是开源软件,通过单元测试可以了解软件是怎么使用的

Spring Boot Test常用注解

当前使用SpringBoot的版本为1.4.0 Release

  • @RunWith(SpringRunner.class) 运行Junit并支持Spring-test特性
  • @SpringBootTest 为springApplication创建上下文并支持SpringBoot特性

    1
    2
    3
    4
    5
    @SpringBootTest的webEnvironment属性有以下4种
    1、Mock —— 加载WebApplicationContext并提供Mock Servlet环境
    2、RANDOOM_PORT —— 加载EmbeddedWebApplicationContext并提供servlet环境,内嵌服务的监听端口是随机的
    3、DEFINED_PORT —— 加载EmbeddedWebApplicationContext并提供servlet环境,内容服务的监听端口是定义好的。默认端口是8080
    4、NONE —— 加载ApplicationContext,启动SpringApplication时,不支持Servlet环境
  • @MockBean 声明需要模拟的服务

  • @SpyBean 定制化需要模拟服务的某个方法,即部分方法可以mock,部分方法可以调用真实方法

  • @WebMvcTest 自动加载Spring MVC配置、MockMvc配置、并扫描注解类

    1
    在没有@WebMvcTest注解时,使用@AutoConfiureMockMvc可以自动配置MockMvc;
  • @Transaction 开启事务,回滚测试方法对数据库的改变

SpringBoot Test 实战

  • 使用Mock,对Controller层接口进行测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @RunWith(SpringRunner.class)
    @WebMvcTest(UserController.class)
    public class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Before
    public void mockUserService(){
    User user = new User(1,"liuyun","123456");
    given(userService.add(user)).willReturn(true);
    given(userService.selectById(1)).willReturn(user);
    given(userService.selectAll()).willReturn(Arrays.asList(user));
    }

    /**
    * 用户列表
    */
    @Test
    public void listTest() throws Exception {
    mockMvc.perform(get("/list")
    .accept(MediaType.APPLICATION_JSON))
    .andDo(print())
    .andExpect(status().isOk());
    }
    }
  • 使用mock,模拟对数据库的操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <!-- @Slf4j 是lombok注解-->
    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserServiceTest {

    @MockBean
    private UserDao userDao;

    @Autowired
    private UserService userService;

    @Before
    public void initUserDao(){
    given(this.userDao.selectAll()).willReturn(Arrays.asList(new User(12231,"zhangsan","234")));
    }

    @Test
    public void listTest(){
    List<User> users = userService.selectAll();
    log.info(new Gson().toJson(users));
    }
    }
  • 回滚测试方法对数据库状态的影响

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- @Slf4j 是lombok注解-->
    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class TransactionTest {
    @Autowired
    private UserService userSerivce;

    @Test
    @Transactional
    public void add() throws Exception {
    //初始化用户表
    userSerivce.deleteAll();

    // 新增2个用户
    userSerivce.add(new User(1,"liuyun","123456"));
    userSerivce.add(new User(2,"xiaofeng","123456"));
    Assert.assertEquals(2, userSerivce.selectAll().size());
    }
    }

    这个测试方法的功能是,往数据库中添加两条记录,加上@Transaction注解后,执行完测试方法,数据库状态将自动回滚到执行这个方法前的状态。

JUnit常用注解

  • @Before 、@After 测试方法前/后运行
  • @BeforeClass 、@AfterClass 测试类前/后运行,为静态方法
  • @Test @ignore(“reason”) 忽略此测试方法
  • @Test(timeout=1) 测试方法运行时间超过1毫秒,则自动失败

JUnit常用注解实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!-- @Slf4j 是lombok注解-->
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class CommonAnnatationTest {
@Before
public void before() {
log.info("before");
}

@After
public void after() {
log.info("after");
}

@BeforeClass
public static void beforeClass() {
log.info("beforeClass");
}

@AfterClass
public static void afterClass() {
log.info("afterClass");
}

@Test
public void test_a() {
log.info("test_a");
}

@Ignore("废弃方法")
@Test
public void test_ignore() {
log.info("ignore");
}

@Test(timeout = 1)
public void test_timeout() {
Integer count = 0;
do {
count++;
} while (count > 0);
}
}

JUnit常用测试类型

  • 集合测试

    SuiteA.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- @Slf4j 是lombok注解-->
    @Slf4j
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest
    public class SuiteA {
    @Test
    public void test_a(){
    log.info("A");
    }

    @Test
    public void test_b(){
    log.info("B");
    }
    }

    SuiteB.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- @Slf4j 是lombok注解-->
    @Slf4j
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest
    public class SuiteB {
    @Test
    public void test_c(){
    log.info("C");
    }

    @Test
    public void test_d(){
    log.info("D");
    }
    }

    SuiteTest.class

    1
    2
    3
    4
    5
    @RunWith(Suite.class)
    @Suite.SuiteClasses({SuiteA.class,SuiteB.class})
    @SpringBootTest
    public class SuiteTest {
    }

    运行SuiteTest,将测试SuiteA、SuiteB的所有测试方法

  • 单元目录测试

    目录1:Fruit.class

    1
    2
    public interface Drinking {
    }

    目录2:Drinking.class

    1
    2
    public interface Drinking {
    }

    CategoryTest.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- @Slf4j 是lombok注解-->
    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class CategoryTest {
    @Test
    @Category(Fruit.class)
    public void getFruitName() {
    log.info("I am Banana!");
    }

    @Test
    @Category(Drinking.class)
    public void getDrinkingName() {
    log.info("I am water!");
    }
    }

    SuiteCategoriesTest.class

    1
    2
    3
    4
    5
    6
    @RunWith(Categories.class)
    @Suite.SuiteClasses({CategoryTest.class})
    @Categories.IncludeCategory(Fruit.class)
    @SpringBootTest
    public class SuiteCategoriesTest {
    }

    运行SuiteCategoriesTest,将会运行CategoryTest测试类下,getFruitName()方法。