后端单元测试的疑问

大家是如何写测试用例(junit test)的,我没在公司写过代码,不知道具体的测试用例需要写什么,现在只知道用断言啥的,难道是一个一个测试所有的service层的接口吗?controller层呢?想提前学习一下,有大佬能教教我吗,最好有code test让我这种小白加强了解下,谢谢大佬了。

阅读 3.1k
3 个回答

后台单元测试的思想基本上的思路是:Mock掉依赖,集中测试核心功能,利用断言来保证代码的正确性。单元测试是非常有必要的,特别是在团队开发中,你的单元测试能够保证自己写的代码不被其它成员(或自己)在后面的迭代更新过程被 BUG 掉。

而且在单后台分离的开发趋势下,利用写单元测试的方法来保障自己代码的正确性比起全环境要好太多了。

以下给出个层的单元测试示例代码,供参考:

C层重点测试:输入参数绑定、请求方法、返回字段;一般会 MOCK 掉服务层。

  @MockBean
  VillageService villageService;

  @Test
  void update() throws Exception {
    // 构造village实体(待保存数据)
    Village newVillage = getOneVillage();
    Village oldVillage = getOneVillage();
    Long id = oldVillage.getId();
    String url = baseUrl + "/" + id.toString();

    String jsonString = JSON.toJSONString(newVillage, SerializerFeature.DisableCircularReferenceDetect);

    Mockito.doReturn(newVillage).when(this.villageService).update(Mockito.anyLong(), Mockito.any(Village.class));

    MockHttpServletRequestBuilder postBuilder = MockMvcRequestBuilders.put(url)
        .contentType(MediaType.APPLICATION_JSON)
        .content(jsonString);
    mockMvc.perform(postBuilder)
        .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(newVillage.getId()))
        .andExpect(MockMvcResultMatchers.jsonPath("$.name").value(newVillage.getName()))
        .andExpect(MockMvcResultMatchers.jsonPath("$.pinyin").value(newVillage.getPinyin()))
        .andExpect(MockMvcResultMatchers.jsonPath("$.establishTime").exists())
        .andExpect(MockMvcResultMatchers.jsonPath("$.latitude").exists())
        .andExpect(MockMvcResultMatchers.jsonPath("$.longitude").value(newVillage.getLongitude()))
        .andExpect(MockMvcResultMatchers.jsonPath("$.model.id").exists())
        .andExpect(MockMvcResultMatchers.jsonPath("$.community.id").exists())
        .andExpect(MockMvcResultMatchers.status().is(200));

    ArgumentCaptor<Village> villageArgumentCaptor = ArgumentCaptor.forClass(Village.class);
    ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class);
    Mockito.verify(this.villageService).update(longArgumentCaptor.capture(), villageArgumentCaptor.capture());
    Assertions.assertEquals(newVillage.getId(), villageArgumentCaptor.getValue().getId());
    Assertions.assertEquals(newVillage.getName(), villageArgumentCaptor.getValue().getName());
    Assertions.assertEquals(newVillage.getPinyin(), villageArgumentCaptor.getValue().getPinyin());
    Assertions.assertEquals(newVillage.getLongitude(), villageArgumentCaptor.getValue().getLongitude());
    Assertions.assertEquals(newVillage.getLatitude(), villageArgumentCaptor.getValue().getLatitude());
    Assertions.assertEquals(newVillage.getCommunity().getId(), villageArgumentCaptor.getValue().getCommunity().getId());
    Assertions.assertEquals(newVillage.getModel().getId(), villageArgumentCaptor.getValue().getModel().getId());
  }

M层的测试,一般会对每个实现类进行单元测试的测试,MOCK 掉其依赖的其它服务层或仓库层。


  public GriderServiceImplTest() {
    this.griderRepository = Mockito.mock(GriderRepository.class);
    this.houseRepository = Mockito.mock(HouseRepository.class);
    this.webUserService = Mockito.mock(WebUserService.class);
    this.userRepository = Mockito.mock(UserRepository.class);
    this.griderService = Mockito.spy(new GriderServiceImpl(this.griderRepository,
        null,
        webUserService,
        houseRepository,
        null,
        null, null, this.userRepository));
  }

  @Test
  void deleteById() {
    this.logger.debug("准备模拟数据");
    List<House> houses = new ArrayList<House>(){};
    Grider grider = new Grider();
    grider.setWebUser(new WebUser());
    grider.setHouses(houses);
    Long id = new Random().nextLong();
    Mockito.doReturn(Optional.of(grider)).when(this.griderRepository).findById(id);
    Mockito.doNothing().when(this.griderService).removeGriderByHouses(houses);

    this.griderService.deleteById(id);
    Mockito.verify(this.griderService, Mockito.times(1)).removeGriderByHouses(houses);
    Mockito.verify(this.webUserService, Mockito.times(1)).removeRole(grider.getWebUser(), RoleType.GRIDER);
    Mockito.verify(this.griderRepository, Mockito.times(1)).deleteById(id);
  }

仓库层初始化一般只测试基本语法是否正确,如果自定义了一些测试方法,那么也需要进行测试。

class VolunteerRepositoryTest extends RepositoryTest {
  @Autowired
  VolunteerRepository volunteerRepository;
  @Test
  void existsByWechatUserAndDeletedIsFalse() {
    WechatUser wechatUser = new WechatUser();
    wechatUser.setId("abc");
    this.volunteerRepository.existsByWechatUserAndDeletedIsFalse(wechatUser);
  }

  @Test
  void findAllByBeStarIsTrueAndDeletedIsFalse() {
    this.volunteerRepository.findAllByBeStarIsTrueAndDeletedIsFalse();
  }

  @Test
  void findByWechatUserAndDeletedIsFalse() {
    WechatUser wechatUser = new WechatUser();
    wechatUser.setId("abc");
    this.volunteerRepository.findByWechatUserAndDeletedIsFalse(wechatUser);
  }
}

切面的测试稍微复杂一些,在此不阐述。但不得不说,单元测试是有一定的门槛的,推荐先学习基本的在逐步向单元测试迈进,这有个入门的教程,推荐阅读下。https://www.kancloud.cn/yunzhiclub/springboot_angular_guide/1391113

另外:不管什么年头,都应该写单元测试,而且应该尝试把写单元测试变成一种习惯。

就是你想的那样. 测试每个方法, 给一个输入数据, 方法会有输出数据, 用assertXXX判断这个输出是对的, 就是单元测试. spring 控制器测试也一样. 你看spring提供的测试方法, 有get() post()之类的请求, 会把请求转到相应的控制器中.

不过这年头谁闲的写测试啊.

单元测试的话,基本只测一些util和service
util直接测,没啥好说的
service用mockito自己mock依赖做测试
controller一般就不是单元测试了,其实有点集成测试的意思,但是因为正常项目controller都很薄,所以service的单元测试也基本够用

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题