正式学习单元测试
单元测试学习总结
简单记录自己在单元测试上的弯路和一些实践。
1. 为什么需要单元测试
以前写代码的时候总是没有写单元测试的习惯,总觉得项目启动后,跑一下就好了,反正总是要和前端联调,自己跑一下接口没有什么坏处。
而在一家大公司之后,各种标准化的代码方式规范了起来,当然负担也是更重了。但是写简单的单元测试可以帮你发现很多小的问题;从另一方面来说,程序员应该要对自己的代码负责,即使出现了bug,也不是测试的问题,应该是开发没有把代码考虑完全造成的。
《精通spring4.x》中对单元测试的必要性说了几点理由:1. 单元测试的好处
- 软件质量的保证
- 可以优化目标代码段设计
- 是代码重构的保障(改造之后可以快速验证行为一致性)
- 是回归测试和持续集成的基础
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服务端单元测试指南