zoukankan      html  css  js  c++  java
  • 持续集成之路——服务层的单元测试

            在完成了数据访问层的单元之后,接下来看如何编写服务层(Service)的单元测试。服务层应该是整个系统中得重中之重,严密的业务逻辑设计保证了系统稳定运行,所以这一层的单元测试也应该占很大比重。虽然一般情况下单元测试应该尽量通过mock剥离依赖,但是由于在当前的项目中数据访问层使用spring-data框架,并没有包含太多的逻辑,因此我就把服务层和数据访问层放在做了一个伪单元测试。

            一、一般逻辑的单元测试。

            这里采用的方式和数据访问层几乎是一样的,主要包含三步:

            1. 通过@DatabaseSetup指定测试用数据集

            2. 执行被测试方法

            3. 通过Dao从数据库中查询数据验证执行结果

            假设要被测试的代码方法是:

    @Service
    @Transactional(readOnly = true)
    public class ShopServiceImpl extends BaseService implements ShopService{
        private Logger logger = LoggerFactory.getLogger(ShopServiceImpl.class);
    
        @Transactional(readOnly = false)
        public Floor addFloor(String buildingName, int floorNum, String layout) {
            //如果已经存在对应的楼层信息,则抛出已经存在的异常信息
            Floor floor = floorDao.findByBuildingNameAndFloorNum(buildingName, floorNum);
            if (floor != null) {
                throw new OnlineShopException(ExceptionCode.Shop_Floor_Existed);
            }
            
            //如果不存在对应的商场信息,则添加新的商场
            Building building = buildingDao.findByName(buildingName);
            if (building == null) {
                building = new Building();
                building.setName(buildingName);
                buildingDao.save(building);
            }
     
            //添加并返回楼层信息
            floor = new Floor();
            floor.setBuilding(building);
            floor.setFloorNum(floorNum);
            floor.setMap(layout);
    
            floorDao.save(floor);
    
            return floor;
        }
    }
    
    

    其对应的接口是:

    public interface ShopService {
        public Floor addFloor(String buildingName, int floorNum, String layout);
    }


            这段逻辑代码的意思十分简单和直白,那么要编写的单元的测试必须要包含所有分支情况:a. 商场和楼层信息都存在的,抛出异常 b. 商场存在,而楼层不存在, 楼层信息都被添加的。 c.  商场和楼层都不存在,全部新增。这里就以第一种情况为例,先准备测试数据:

    <?xml version="1.0" encoding="UTF-8"?>
    <dataset>
        <building id="1" name="New House"/>
        <floor id="1" building="1" floor_num="2"/>
    </dataset>


    接着编写测试用例,注意要必须得注解不能忘掉:

      

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext-test.xml")
    @Transactional
    @TestExecutionListeners({
            DependencyInjectionTestExecutionListener.class,
            DirtiesContextTestExecutionListener.class,
            CustomTransactionDbUnitTestExecutionListener.class,
            ForeignKeyDisabling.class})
    public class ShopServiceTest {
        @Autowired
        private ShopService shopService;
    
        @Test
        @DatabaseSetup("shop/ShopService-addFloorExistException-dataset.xml")
        public void testAddFloorExistException(){
            try {
                shopService.addFloor("New House", 2, "");
                fail();
            } catch(Exception e){
                assertTrue(e instanceof OnlineShopException);
                assertEquals(ExceptionCode.Shop_Floor_Existed.code(), ((OnlineShopException)e).getCode());
            }
        }
    
    }


    这个测试和数据访问层的测试看起来没有什么两样。

            二、使用Mock对象隔离第三方接口

            软件开发中一般都存在和第三方集成的情况,比如调用新浪的认证、百度的地图等等。那么在编写测试的时候,基于效率的考虑,一般情况不会真的去调用这些远程API(当然应该有其他测试可以及时发现第三方接口的变化),而是假定它们一直会返回预期的结果。这个时候就需要用到mock对象,来模拟这些API产生相应的结果。

            在这里,我是用了mockito,使用十分方便。假如现在用户登录时,需要去第三方系统验证,那么现在来看如何对这个场景进行测试。还是先来看被测试的方法:

    private boolean validateUser(String inputName, String inputPassword) {
            return thirdPartyAPI.authenticate(inputName, inputPassword);
    }


     其中thirdPartyAPI就是第三方用来认证的API。下面来看测试代码:

    public class UserServiceTest {
        @Autowired
        private UserService userService;
        private ThirdPartyAPI mockThirdPartyAPI = mock(ThirdPartyAPI.class);
        
        @Test
        public void testLogin(){
            //指定mock对象特定操作的返回结果
            when(mockThirdPartyAPI.authenticate("jiml", "jiml")).thenReturn(true);
            //通过Setter用mock对象替换由Spring初始化的第三方依赖
            ((UserServiceImpl)userService).setThirdPartyAPI(mockThirdPartyAPI);
            boolean loginStatus = userService.login("jiml", "jiml");
            assertTrue(loginStatus);
        }
    }


          其实服务层的测试并没有太多的新东西,而最关键的问题是如何把逻辑中各个分支都能测试到,使测试真正起到为软件质量保驾护航的作用。

  • 相关阅读:
    HDU 1059 Dividing(多重背包)
    新华网,要厚道
    js与DOM初步:访问html元素
    Hacker News网站的文章排名算法工作原理
    做技术,是个精益求精的事情
    百度贴吧客户端(Android)网络通信行为分析
    Storm同时接收多个源(spout和bolt)
    各种流处理系统的比较
    Kafka学习
    关联规则推荐及Apriori算法
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3211908.html
Copyright © 2011-2022 走看看