前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java自测心得、技术选型和实现方式

java自测心得、技术选型和实现方式

作者头像
逝兮诚
发布2019-10-30 18:08:21
9480
发布2019-10-30 18:08:21
举报
文章被收录于专栏:代码人生代码人生

程序员自测是很重要的一个环节,我认同测试驱动开发的理念,经过一段时间的测试代码的编写,发现测试代码需要保证几点,1.测试代码可重复跑,不能跑过一次,改了数据库数据就不能跑了。2.测试代码写好后,尽可能保持不变,哪怕代码变后,直接跑测试就能验证修改是否正确,而不是把测试代码,测试数据再改一遍。service层测试要与数据库解耦,不能因为数据库数据的变化影响测试,我曾经使用int.sql去对数据库做int操作来保证测试的进行,但是实践过程中会渐渐由于数据表结构更新导致int.sql维护不善,使得每跑一次测试都要修改int.sql。对于十分麻烦的工作,我一般的是不想继续做的,我的想法是无论代码,数据库怎么动,测试代码都是不用怎么改动的,直接跑就可以了,这样也方便项目重构。目前已经达到我对测试的预期了,故而总结现有技术和实现。。如果有更好的建议,也欢迎提出。

mock做service层的单元测试

service测试使用mockito,它可以保证service的获得的业务数据是预期的数据,得到的结果一定是预期的结果。

mockito的作用是动态代理调用替换mapper层,改用自己配置的数据。这样就可以直接做service单层的单元测试。不会出现由于数据库的数据变了,导致单元测试跑不过的情况,直接执行就可以了。

mock数据类

建议做法,创建一个mock数据类,存放所有测试数据 - mock所有的service或者mapper方法,让其返回设定的数据,所有的测试类继承它。

代码语言:javascript
复制
public class MockData {
    public static final LockerInfoService lockerInfoService = mock(LockerInfoService.class);
    public static final MedicineHistoryService medicineHistoryService = mock(MedicineHistoryService.class);
    public static final ParcelService parcelService = mock(ParcelService.class);
    public static final ParcelLogService parcelLogService = mock(ParcelLogService.class);

    static {
        when(lockerInfoService.findByLockerCode("006000001")).thenReturn(createLockerInfo001());
        when(lockerInfoService.findByLockerCode("006000002")).thenReturn(createLockerInfo002());
        when(medicineHistoryService.findByMHIdInFiveMinute("exist")).thenReturn(createExistMedicineHistory());
        when(medicineHistoryService.findByMHIdInFiveMinute("not_exist")).thenReturn(createNotExistMedicineHistory());
        when(parcelService.findByParcelCode("1")).thenReturn(createDropOffProcessingParcel());
        when(parcelService.findByParcelCode("2")).thenReturn(createAlreadyDropOffParcel());
        when(parcelService.findByParcelCode("5")).thenReturn(createAlreadyPickedUpParcel());
        when(parcelService.findByPickupCode("006000001","222222")).thenReturn(createAlreadyDropOffParcel());
        when(parcelService.findInLockerParcel("006000001", null)).thenReturn(Arrays.asList(createAlreadyDropOffParcel()));
        when(parcelService.getEffectiveCodes("006000001")).thenReturn(Arrays.asList(createAlreadyDropOffParcel().getPickupCode()));
        when(parcelService.getEffectiveParcels("006000001")).thenReturn(Arrays.asList(createAlreadyDropOffParcel()));
        when(parcelService.selectByParcelCode("006000001","1")).thenReturn(createDropOffProcessingParcel());
        when(parcelService.selectByParcelCode("006000001","2")).thenReturn(createAlreadyDropOffParcel());
        when(parcelService.selectByParcelCode("006000001","5")).thenReturn(createAlreadyPickedUpParcel());
    }

    public static LockerInfo createLockerInfo001() {
        LockerInfo lockerInfo = new LockerInfo();
        lockerInfo.setLockerCode("006000001");
        lockerInfo.setAddressDetail("深圳北大医院");
        lockerInfo.setSecret("$2a$04$N5bj6tbPthUtnbfLQ2hIRuU5KGrMXkdYpy5mZP66EDkchTyjk.9D.");
        return lockerInfo;
    }

    public static LockerInfo createLockerInfo002() {
        LockerInfo lockerInfo = new LockerInfo();
        lockerInfo.setLockerCode("006000002");
        lockerInfo.setAddressDetail("南山医院");
        lockerInfo.setSecret("$2a$04$N5bj6tbPthUtnbfLQ2hIRuU5KGrMXkdYpy5mZP66EDkchTyjk.9D.");
        return lockerInfo;
    }

    // 存在的病历数据
    public static MedicineHistory createExistMedicineHistory() {
        MedicineHistory medicineHistory = new MedicineHistory();
        medicineHistory.setMedicineHistoryId("01");
        medicineHistory.setHospitalCode("001");
        medicineHistory.setHospitalName("深圳北大医院");
        medicineHistory.setPickupMobile("13456450931");
        medicineHistory.setDataStatus(DataStatusEnum.EXIST.getValue());
        return medicineHistory;
    }

    // 不存在的病历数据
    public static MedicineHistory createNotExistMedicineHistory() {
        MedicineHistory medicineHistory = new MedicineHistory();
        medicineHistory.setMedicineHistoryId("02");
        medicineHistory.setHospitalCode("002");
        medicineHistory.setHospitalName("南山医院");
        medicineHistory.setDataStatus(DataStatusEnum.NO_EXIST.getValue());
        return medicineHistory;
    }

    // 未投递包裹
    public static Parcel createDropOffProcessingParcel() {
        Parcel parcel = new Parcel();
        parcel.setParcelCode("1");
        parcel.setParcelStatus(ParcelStatusEnum.DROP_OFF_PROCESSING.getValue());
        parcel.setMedicineHistoryId("exist");
        parcel.setPickupMobile("18676660540");
        return parcel;
    }

    // 已投递包裹
    public static Parcel createAlreadyDropOffParcel() {
        Parcel parcel = new Parcel();
        parcel.setParcelCode("2");
        parcel.setParcelStatus(ParcelStatusEnum.ALREADY_DROP_OFF.getValue());
        parcel.setMedicineHistoryId("exist");
        parcel.setPickupMobile("18676660540");
        parcel.setDropoffUser("医院工作人员");
        parcel.setPickupCode("332211");
        parcel.setLockerCode(createLockerInfo001().getLockerCode());
        return parcel;
    }

    // 已取出包裹
    public static Parcel createAlreadyPickedUpParcel() {
        Parcel parcel = new Parcel();
        parcel.setParcelCode("5");
        parcel.setParcelStatus(ParcelStatusEnum.ALREADY_PICKED_UP.getValue());
        parcel.setMedicineHistoryId("exist");
        parcel.setPickupMobile("18676660540");
        parcel.setDropoffUser("医院工作人员");
        parcel.setPickupCode("患者");
        parcel.setPickupType(PickupTypeEnum.PATIENT.getValue());
        return parcel;
    }
}

mock数据建议使用方法的形式,比如一个取件人的数据,就写一个User getPickupUser()的方法,调用方法参数是mock数据,返回的结果也是用改数据去assert。不用static bean,在static{}赋值的原因是赋值的数据放在一起,看起来恶心;使用get()可以直接去改对象的构造,看看对象属性值,一目了然。

代码语言:javascript
复制
    static FileParcel getParcel() {
        FileParcel parcel = new FileParcel();
        parcel.setId(2);
        parcel.setFileId(1);
        parcel.setDropoffUser("韩雷");
        parcel.setDropoffUserNo("FC001");
        parcel.setPickupUser("李梅梅");
        parcel.setPickupUserNo("FC002");
        parcel.setReceiveStatus(ReceiveStatusEnum.NOTPICKUP.getValue());
        parcel.setParcelStatus(ParcelStatusEnum.UN_DROP_OFF.getValue().byteValue());
        return parcel;
    }
    
    @Test
    public void parcelInfo() {
        mobileService.setUserRepository(userRepository);
        mobileService.setParcelRepository(parcelRepository);
        mobileService.setFileInfoRepository(fileRepository);
        MobileParcelVO parcelVO = mobileService.parcelInfo(getParcel().getId());
        assert parcelVO !=null;
        assert parcelVO.getId() == getParcel().getId().intValue();
        assert parcelVO.getCanDelete().byteValue() == CanDeleteEnum.ALLOW_DELETE.getValue();
    }

单元测试类中使用

service的注入类写法要写成set的形式,让mock对象能注入到service对象中。

代码语言:javascript
复制
private ParcelService parcelService;

@Autowired
public void setParcelService(ParcelService parcelService) {
    this.parcelService = parcelService;
}

在测试类中,通过new的形式构造service类对象,将mock的service对象set到service对象中。传入特定的参数,使用assert判断返回值是否符合预期;通过verify判断方法是否执行某些函数,参数是否符合预期(用于save,delete操作)。

代码语言:javascript
复制
public static LockerServiceClientService lockerServiceClientService = new LockerServiceClientService();

static {
    lockerServiceClientService.setLockerInfoService(lockerInfoService);
    lockerServiceClientService.setMedicineHistoryService(medicineHistoryService);
    lockerServiceClientService.setParcelService(parcelService);
    lockerServiceClientService.setParcelLogService(parcelLogService);
}

验证返回值是否符合预期

代码语言:javascript
复制
@Test
public void medicineHistoryInfo() {
    // 验证成功
    MedicineHistoryInfoVO exist = lockerServiceClientService.medicineHistoryInfo("exist");
    assert DataStatusEnum.EXIST.getValue().equals(exist.getDataStatus());

    // 验证不存在-数据源不存在
    try {
        lockerServiceClientService.medicineHistoryInfo("not_exist");
    } catch (LockerWebException e) {
        assert e.getCode() == ResponseCode.MEDICINE_HISTORY_NOT_EXIST;
    }

    // 验证没有查到数据 - 没有拿到数据源结果
    MedicineHistoryInfoVO no_exist = lockerServiceClientService.medicineHistoryInfo("随意的key");
    assert no_exist == MedicineHistoryInfoVO.NO_EXIST_VO;
}

验证是否执行某个函数

代码语言:javascript
复制
@Test
public void dropoffSuccessBiz() {
    ParcelDTO parcel = new ParcelDTO();
    Parcel dropingParcel = createDropOffProcessingParcel();
    parcel.setQueryParcel(dropingParcel);
    Parcel updateParcel = new Parcel();
    BeanUtils.copyProperties(parcel, updateParcel);
    updateParcel.setDropoffUser("医院工作人员");
    updateParcel.setParcelStatus(ParcelStatusEnum.ALREADY_DROP_OFF.getValue().byteValue());
    updateParcel.setDropoffIsClose(IsCloseEnum.YES.getValue());
    updateParcel.setQueryParcel(null);
    AsyncService asyncService = mock(AsyncService.class);
    lockerServiceClientService.setAsyncService(asyncService);
    lockerServiceClientService.dropoffSuccessBiz(parcel);
    verify(parcelService, times(1)).save(updateParcel);
    verify(asyncService, times(1)).sendSmsPickupCode(dropingParcel.getPickupCode(),
            dropingParcel.getLockerAddress(), dropingParcel.getPickupMobile(), dropingParcel.getMedicineHistoryId());
}

单元测试类

代码语言:javascript
复制
public class LockerServiceClientServiceTest extends MockData {

    Logger logger = LoggerFactory.getLogger(LockerServiceClientServiceTest.class);

    public static LockerServiceClientService lockerServiceClientService = new LockerServiceClientService();

    static {
        lockerServiceClientService.setLockerInfoService(lockerInfoService);
        lockerServiceClientService.setMedicineHistoryService(medicineHistoryService);
        lockerServiceClientService.setParcelService(parcelService);
        lockerServiceClientService.setParcelLogService(parcelLogService);
    }

    @Test
    public void lockerSecret() {
        LockerInfo lockerInfo = createLockerInfo001();
        lockerServiceClientService.lockerSecret(lockerInfo.getLockerCode(), "123456");
    }

    @Test
    public void medicineHistoryInfo() {
        // 验证成功
        MedicineHistoryInfoVO exist = lockerServiceClientService.medicineHistoryInfo("exist");
        assert DataStatusEnum.EXIST.getValue().equals(exist.getDataStatus());

        // 验证不存在-数据源不存在
        try {
            lockerServiceClientService.medicineHistoryInfo("not_exist");
        } catch (LockerWebException e) {
            assert e.getCode() == ResponseCode.MEDICINE_HISTORY_NOT_EXIST;
        }

        // 验证没有查到数据 - 没有拿到数据源结果
        MedicineHistoryInfoVO no_exist = lockerServiceClientService.medicineHistoryInfo("随意的key");
        assert no_exist == MedicineHistoryInfoVO.NO_EXIST_VO;
    }
    
    @Test
    public void dropoffSuccessBiz() {
        ParcelDTO parcel = new ParcelDTO();
        Parcel dropingParcel = createDropOffProcessingParcel();
        parcel.setQueryParcel(dropingParcel);
        Parcel updateParcel = new Parcel();
        BeanUtils.copyProperties(parcel, updateParcel);
        updateParcel.setDropoffUser("医院工作人员");
        updateParcel.setParcelStatus(ParcelStatusEnum.ALREADY_DROP_OFF.getValue().byteValue());
        updateParcel.setDropoffIsClose(IsCloseEnum.YES.getValue());
        updateParcel.setQueryParcel(null);
        AsyncService asyncService = mock(AsyncService.class);
        lockerServiceClientService.setAsyncService(asyncService);
        lockerServiceClientService.dropoffSuccessBiz(parcel);
        verify(parcelService, times(1)).save(updateParcel);
        verify(asyncService, times(1)).sendSmsPickupCode(dropingParcel.getPickupCode(),
                dropingParcel.getLockerAddress(), dropingParcel.getPickupMobile(), dropingParcel.getMedicineHistoryId());
    }
}

dao层的单元测试

对于dao层的测试,主要是为了测试sql语句是否正确执行。我的测试方式是了解开发数据库,做dao的测试,只测试sql是否成功执行。选择spring-test,导入spring的IOC容器,测试。对于insert,update,delete操作,建议开启事务回滚,不污染数据库数据,方便二次测试。使用spring-boot-test,只要在测试类上加上回滚注解@Transactional,方法执行完后,默认回滚,而spring-test需要在测试方法上加上回滚注解Rollback

使用spring-boot-test导入IOC的注解,并开启回滚

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
@Transactional
public class SpringBootEnvironmentTest {
}

使用spring-test导入IOC的注解

代码语言:javascript
复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
@Transactional(transactionManager="transactionManager")
public class BaseTest {
    @Test
    @Rollback
    public void insertAndSelect() {
    }
}

controller的测试

controller层主要做参数效验这类工作,基本不会有业务逻辑,单元测试在我看来不是那么重要,可以集成测试中联合service层,dao层一起测试。

对于controller层的测试的方式,我一般是启动项目,使用postman发送http请求或自己写httpclient发送http请求做测试。这个类似工具,技术很多,我就不介绍了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-05-15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • mock做service层的单元测试
    • mock数据类
      • 单元测试类中使用
        • 验证返回值是否符合预期
          • 验证是否执行某个函数
          • 单元测试类
          • dao层的单元测试
          • controller的测试
          相关产品与服务
          数据库
          云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档