正式学习单元测试

单元测试学习总结

简单记录自己在单元测试上的弯路和一些实践。

1. 为什么需要单元测试

以前写代码的时候总是没有写单元测试的习惯,总觉得项目启动后,跑一下就好了,反正总是要和前端联调,自己跑一下接口没有什么坏处。

而在一家大公司之后,各种标准化的代码方式规范了起来,当然负担也是更重了。但是写简单的单元测试可以帮你发现很多小的问题;从另一方面来说,程序员应该要对自己的代码负责,即使出现了bug,也不是测试的问题,应该是开发没有把代码考虑完全造成的。

《精通spring4.x》中对单元测试的必要性说了几点理由:1. 单元测试的好处

  1. 软件质量的保证
  2. 可以优化目标代码段设计
  3. 是代码重构的保障(改造之后可以快速验证行为一致性)
  4. 是回归测试和持续集成的基础

2. 最初级的单元测试

虽然单元测试有以上这些好处,但确实是码代码过程中很枯燥的部分,也许是自己还没有领悟到TDD的好处吧。其中关于优化代码的好处,目前还没有领悟到。

所以刚开始做单元测试的时候,仅仅是把对应的待测试对象引入,然后调用方法,最后打印结果,通过肉眼的方式来判断是否出错了。

当然,运行时异常之类的异常还是可以排查出来的,所以,也算是把流程走了一遍吧。

目前看来太low了。自己本来就是为了简化工作流程的工作,把线下的工作搬到线上,但是在实现的过程却还是使用纯人力的方式,完全对不起程序员这个称呼,果然是码农哦。

这个阶段,我对单元测试的要求是针对所有和前端交互的接口对进行测试,目标是把主流程过一遍,所以使用的是 mockMVC 的方式,基类如下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationServer.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@ActiveProfiles(profiles = "test")
@Transactional
@Rollback
public class AbstractControllerTest<T> {

  protected MockMvc mockMvc;

  protected void setUp(T controller) {
    mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
  }

  protected void printResult(MvcResult mvcResult, String name) {
    String result = null;
    try {
      result = mvcResult.getResponse().getContentAsString();
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
    System.out.println(String.format("=====> %s: %s", name, result)); // 肉眼可见的鄙视
  }

}

3. 次级的单元测试

后来看了别人写的单元测试,后知后觉之后,开始使用断言来对程序的结果进行判断,直接通过红绿来判断代码是否通过测试即可。

随即增加的还有针对service进行测试,因为实现的细节还是更重要的,而不仅仅只是主流程通过即可。另一方面,当测试覆盖率的要求上来之后,仅仅controller的测试就不够了。

然后就是针对service的测试,因为大家的习惯都是把逻辑都写在service 中,而controller作为接入层,仅仅只调用service即可。

而service的测试经常伴随着数据库的访问,这时候还需要自己准备一些数据之后,才能够进行测试,测试基类如下:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
        properties = {"logging.path=logs",
                "spring.profiles.active=test"
        })
@Transactional
@Rollback
public class BaseTest {
	@Autowired
	UserDao userDao;
	
	@Test
	public void testLogin(){
		User user=userDao.findByUser(1);
		assertNotNull(user);
		assertEquals(user.password,"xxxx");  // 这些数据一看就是自己提前写好的。。。
	}
}

4. 可重复的单元测试

由2-3的进步,差不多可以应付过单元测试的编写了,因为毕竟也可以覆盖大部分的测试场景。

但是你会发现,3中的单元测试其实是不具有可重复执行的要求的。如果你写的单元测试不具有可重复执行的特性,那么你还不如不写单元测试,直接启动项目之后,把所有功能都回归一遍就好了,根本不用浪费时间来写无聊的代码。

那么针对数据库的测试如何做到可重复执行呢,毕竟测试数据库又不是自己的,没有办法控制别人什么时候会把你的测试数据删掉,导致下次执行的时候就报错了。

这里有两种方案:

  • mock 的方式,即把通过数据库返回的逻辑,手动模拟成正确的,这样你就不用依赖数据库,但同时你就缺少了一环。

测试代码如下:

	@Test
    public void test(){
        doReturn("success").when(userDao).findByUser(1);

        String str = userDao.findByUser(1);
		 assertEquals(str,"success");
    }	

这种方法很简单,自己去构造数据,可以测试代码逻辑是否符合你的要求。

  • 使用dbunit模拟数据

先上代码:

@DatabaseSetup("/table.xls")
  @Test
  public void listUser() {
    List<Integer> levels = Lists.newArrayList();
    List<Integer> states = Lists.newArrayList();
    Integer page = 1;
    Integer pageSize = 10;
    Pagination<ProjectListVO> pagination = userService.listUser(operator, levels, states, page, pageSize);
    assertNotNull(pagination);
    assertEquals(pagination.getTotalCount(), 3);
  }

可以看到和之前没有什么不一样,只是方法上增加了一个注解,com.github.springtestdbunit.annotation.DatabaseSetup,这样就可以在 xls 文件里去构建你的数据库数据,然后只需要保证这个文件不被修改就可以了,可以每个开发一个自己的测试文件,美滋滋。

以上,基本上解决了 接入层、dao层、代码逻辑的各种测试,也很简单去完成,但是简单的测试不仅仅是为了应付要求,还要真正理解自己的代码,然后逼迫自己去写出小而美的代码。

目前仅仅知道了表面的方法,方法背后的意义还需要慢慢体会学习。

ps:以上引用的注解使用了这个开源组件,有兴趣的同学可以访问学习,很棒。

5. 文章集合:

有赞的特别推荐!!!

单元测试实践经验
有赞单元测试时间
TDD及单元测试最佳实践
spring Boot测试的最佳实践和测试架构的启发(JUnit4和mockito,包括MockMvc
Java服务端单元测试指南

原文地址:https://www.cnblogs.com/paxing/p/12835479.html