qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請訪問 http://qaseven.github.io/

          持續(xù)集成之路—服務(wù)層的單元測試

           在完成了數(shù)據(jù)訪問層的單元之后,接下來看如何編寫服務(wù)層(Service)的單元測試。服務(wù)層應(yīng)該是整個系統(tǒng)中得重中之重,嚴密的業(yè)務(wù)邏輯設(shè)計保證了系統(tǒng)穩(wěn)定運行,所以這一層的單元測試也應(yīng)該占很大比重。雖然一般情況下單元測試應(yīng)該盡量通過mock剝離依賴,但是由于在當前的項目中數(shù)據(jù)訪問層使用spring-data框架,并沒有包含太多的邏輯,因此我就把服務(wù)層和數(shù)據(jù)訪問層放在做了一個偽單元測試。
            一、一般邏輯的單元測試。
            這里采用的方式和數(shù)據(jù)訪問層幾乎是一樣的,主要包含三步:
            1. 通過@DatabaseSetup指定測試用數(shù)據(jù)集
            2. 執(zhí)行被測試方法
            3. 通過Dao從數(shù)據(jù)庫中查詢數(shù)據(jù)驗證執(zhí)行結(jié)果
            假設(shè)要被測試的代碼方法是:
          @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) {
          //如果已經(jīng)存在對應(yīng)的樓層信息,則拋出已經(jīng)存在的異常信息
          Floor floor = floorDao.findByBuildingNameAndFloorNum(buildingName, floorNum);
          if (floor != null) {
          throw new OnlineShopException(ExceptionCode.Shop_Floor_Existed);
          }
          //如果不存在對應(yīng)的商場信息,則添加新的商場
          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;
          }
          }
            其對應(yīng)的接口是:
            public interface ShopService {
            public Floor addFloor(String buildingName, int floorNum, String layout);
            }

          這段邏輯代碼的意思十分簡單和直白,那么要編寫的單元的測試必須要包含所有分支情況:a. 商場和樓層信息都存在的,拋出異常 b. 商場存在,而樓層不存在, 樓層信息都被添加的。 c.  商場和樓層都不存在,全部新增。這里就以第一種情況為例,先準備測試數(shù)據(jù):
            <?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());
          }
          }
          }
            這個測試和數(shù)據(jù)訪問層的測試看起來沒有什么兩樣。
            二、使用Mock對象隔離第三方接口
            軟件開發(fā)中一般都存在和第三方集成的情況,比如調(diào)用新浪的認證、百度的地圖等等。那么在編寫測試的時候,基于效率的考慮,一般情況不會真的去調(diào)用這些遠程API(當然應(yīng)該有其他測試可以及時發(fā)現(xiàn)第三方接口的變化),而是假定它們一直會返回預(yù)期的結(jié)果。這個時候就需要用到mock對象,來模擬這些API產(chǎn)生相應(yīng)的結(jié)果。
            在這里,我是用了mockito,使用十分方便。假如現(xiàn)在用戶登錄時,需要去第三方系統(tǒng)驗證,那么現(xiàn)在來看如何對這個場景進行測試。還是先來看被測試的方法:
            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對象特定操作的返回結(jié)果
          when(mockThirdPartyAPI.authenticate("jiml", "jiml")).thenReturn(true);
          //通過Setter用mock對象替換由Spring初始化的第三方依賴
          ((UserServiceImpl)userService).setThirdPartyAPI(mockThirdPartyAPI);
          boolean loginStatus = userService.login("jiml", "jiml");
          assertTrue(loginStatus);
          }
          }
            其實服務(wù)層的測試并沒有太多的新東西,而最關(guān)鍵的問題是如何把邏輯中各個分支都能測試到,使測試真正起到為軟件質(zhì)量保駕護航的作用。

          posted on 2014-06-25 11:26 順其自然EVO 閱讀(224) 評論(0)  編輯  收藏 所屬分類: 測試學(xué)習(xí)專欄

          <2014年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 肇源县| 射洪县| 扶沟县| 莫力| 马山县| 长白| 长治县| 岐山县| 清原| 墨江| 奉化市| 东平县| 黎川县| 泰和县| 明水县| 凉城县| 高陵县| 兴业县| 胶南市| 土默特右旗| 双牌县| 故城县| 聂拉木县| 隆化县| 格尔木市| 商丘市| 班玛县| 章丘市| 毕节市| 张家港市| 明星| 宁国市| 中江县| 昌黎县| 兴国县| 嘉祥县| 常宁市| 商南县| 长武县| 嵩明县| 谢通门县|