grails學(xué)習(xí)(二)grails單元測(cè)試

          以前我在小公司,完成項(xiàng)目功能是終極目標(biāo)。開(kāi)發(fā)人員很害怕需求變化,因?yàn)樗麄兏呐铝恕D菃?wèn)題出在哪里呢?后來(lái)我仔細(xì)想想,是沒(méi)有做測(cè)試造成。那開(kāi)發(fā)人員為什么如此害怕需求變化,我舉個(gè)例子,a服務(wù)給b服務(wù)和c服務(wù)調(diào)用,后來(lái)需求改變,導(dǎo)致a服務(wù)無(wú)法滿(mǎn)足b服務(wù),能完成自身的功能是天大的事,于是沒(méi)有和別人溝通把a(bǔ)服務(wù)直接改了。項(xiàng)目上線(xiàn),突然有一天客戶(hù)打電話(huà)說(shuō)你們網(wǎng)站這里出問(wèn)題,那里出問(wèn)題,以前都不會(huì)的啊。你們?cè)趺磁摹S谑歉鶕?jù)頁(yè)面錯(cuò)誤信息,開(kāi)發(fā)人員很快找到錯(cuò)誤根源,原來(lái)a服務(wù)改動(dòng),導(dǎo)致b服務(wù)不正常。而d,e,f服務(wù)依賴(lài)于b,那么導(dǎo)致d,e,f相關(guān)功能都出錯(cuò)了。立馬動(dòng)手改,改完上線(xiàn),能知道的問(wèn)題都沒(méi)了,哈哈,真高興,可是不能高興太早哇,也許還有潛在bug。

          軟件的bug是無(wú)法避免,但是我們可以盡量減少bug,不斷提升代碼質(zhì)量。剛我也說(shuō)過(guò),上述問(wèn)題造成的原因是沒(méi)有做測(cè)試。測(cè)試包括很多了,單元測(cè)試、集成測(cè)試和功能測(cè)試等等。既然測(cè)試如此重要,每完成一個(gè)類(lèi)都能進(jìn)行測(cè)試。

          以前也許你比較糾結(jié),沒(méi)有好的工具,現(xiàn)在java社區(qū)非常活躍,我們可以選擇的太多太多了:junit4,jmock,mockito,easymock,TestNg等等。如果你用過(guò)grails,那么你更清楚,此類(lèi)快速開(kāi)發(fā)框架已經(jīng)幫我們集成好了。使用起來(lái)非常簡(jiǎn)單。所以今天我主要講述下grails的單元測(cè)試。

          假設(shè)需求:我們給每個(gè)用戶(hù)分配工作,每個(gè)人都要完成兩件事情,第一件事情:根據(jù)自己的用戶(hù)名返回歡迎信息;第二件事情:根據(jù)自己的地址返回國(guó)家地區(qū)。

          詳細(xì)設(shè)計(jì)

          用戶(hù)信息類(lèi):
          package com.test.domian

          class User {

              
          int id
              String name
              String address
              
              
          static constraints = {
              }
          }

          工作服務(wù)接口:
          package com.test.services

          class WorkService {

              
          /**
               * 根據(jù)用戶(hù)名返回歡迎字符
               * 
          @param userName
               * 
          @return
               
          */
              def processWorkOne(String userName) {
              }
              
              
          /**
               * 根據(jù)地址返回地區(qū)
               * 
          @param address
               * 
          @return
               
          */
              def processWorkTwo(String address){
              }
              
          }

          用戶(hù)工作服務(wù):
          package com.test.services

          import com.test.domian.User

          class UserService {

              def workService
              
              def doWork() {
                  
                  def userList 
          = User.list()
                  userList.each {
                      it.name 
          = workService.processWorkOne(it.name)
                      it.address 
          = workService.processWorkTwo(it.address)
                      
                  }
              }
          }

          我們重點(diǎn)來(lái)看下測(cè)試類(lèi):
          package com.test.services

          import grails.test.*

          import com.test.domian.User

          class UserServiceTests extends GrailsUnitTestCase {
              
          protected void setUp() {
                  
          super.setUp()
              }

              
          protected void tearDown() {
                  
          super.tearDown()
              }

              
          void testDoWork() {
                  
                  
          //構(gòu)造數(shù)據(jù),類(lèi)似于數(shù)據(jù)庫(kù)存在三條記錄
                  def user1 = new User(id:1, name:"lucy", address:"hangzhou")
                  def user2 
          = new User(id:2, name:"lily", address:"wenzhou")
                  def user3 
          = new User(id:3, name:"lilei", address:"beijing")
                  mockDomain User, [user1, user2, user3]
                  
                  
          //mock WorkService接口的processWorkOne方法和processWorkTwo方法
                  def workControl = mockFor(WorkService)
                  def userCount 
          = User.count()
                  
          while(userCount-- > 0){
                      workControl.demand.processWorkOne(
          1..1){String userName ->
                          
          return "hello world, " << userName
                      }
                      workControl.demand.processWorkTwo(
          1..1){String address ->
                          
          return "location in " << address
                      }
                  }
                  def workService 
          = workControl.createMock()
                  
                  
          //把構(gòu)造好的workservice傳給userservice
                  UserService userService = new UserService()
                  userService.workService 
          = workService
                  
                  userService.doWork()
                  
                  def user4 
          = User.findById(1)
                  assertEquals 
          "hello world, lucy", user4.name
                  assertEquals 
          "location in hangzhou", user4.address
              }
          }

          以下著重來(lái)具體說(shuō)明:
          1、mockDomain方法就是構(gòu)造數(shù)據(jù),包括domain類(lèi)的動(dòng)態(tài)方法都可以使用,比如:save(),list(),findby*()等。代碼中的User.count(); User.list();就是因?yàn)檎{(diào)用了mockDomain方法才可以正常使用。如果是集成測(cè)試的話(huà),grails會(huì)幫我們構(gòu)造好,可以直接使用。但這里是單元測(cè)試,所以需要自己mock。

          2、mockFor方法就是給WorkService構(gòu)造一個(gè)對(duì)象,然后給
          workControl對(duì)象的demand代理創(chuàng)建兩個(gè)UserService中用的processWorkOne和processWorkTwo方法,代碼中用到了1..1,表示mock對(duì)象只能調(diào)用這個(gè)方法一次,為什么要循環(huán)三次設(shè)置processWorkOne和processWorkTwo方法呢?因?yàn)槲覀冊(cè)赨serService是對(duì)三個(gè)對(duì)象分別進(jìn)行調(diào)用處理這兩件事情。也許你會(huì)想,干嘛不直接把1..3(最少調(diào)用一次,最多調(diào)用三次)。是的,我最開(kāi)始也是這么來(lái)處理,可是單元測(cè)試就是同不過(guò)。
          如果把
          UserService類(lèi)中的
          workControl.demand.processWorkOne(1..1){String userName ->
                
          return "hello world, " << userName
           }

          改成
          workControl.demand.processWorkOne(1..3){String userName ->
                
          return "hello world, " << userName
           }

          然后把UserServiceTests類(lèi)中的:
          userList.each {
                      it.name 
          = workService.processWorkOne(it.name)
                      it.address 
          = workService.processWorkTwo(it.address)
          }
          改成
          userList.each {
                      it.name 
          = workService.processWorkOne(it.name)
                     
          it.name = workService.processWorkOne(it.name)
                      it.name = workService.processWorkOne(it.name)
                      it.address = workService.processWorkTwo(it.address)
          }

          單元測(cè)試可以通過(guò),但是改成這樣
          userList.each {
                      it.name 
          = workService.processWorkOne(it.name)
                     
          it.name = workService.processWorkOne(it.name)
                      it.address = workService.processWorkTwo(it.address)
                     
          it.name = workService.processWorkOne(it.name)
          }
          單元測(cè)試通不過(guò)。
          以上就是表明1..3的含義:這個(gè)方法要連續(xù)被調(diào)用至少一次,至多三次。
          但是有的人說(shuō)我在UserService中就要這么寫(xiě)
          userList.each {
                      it.name 
          = workService.processWorkOne(it.name)
                     
          it.name = workService.processWorkOne(it.name)
                      it.address = workService.processWorkTwo(it.address)
                     
          it.name = workService.processWorkOne(it.name)
          }
          那我要怎么改單元測(cè)試才能通過(guò)?
          我們把
          UserServiceTests的demand這段代碼
          workControl.demand.processWorkOne(1..1){String userName ->
               
          return "hello world, " << userName
          }
          workControl.demand.processWorkTwo(
          1..1){String address ->
                
          return "location in " << address
          }

          改成
          workControl.demand.processWorkOne(1..2){String userName ->
                
          return "hello world, " << userName
          }
          workControl.demand.processWorkTwo(
          1..1){String address ->
                 
          return "location in " << address
           }

          workControl.demand.processWorkOne(1..1){String address ->
                 
          return "location in " << address
           }
          這樣就通過(guò)了。
          以上就是說(shuō)明構(gòu)造出來(lái)的函數(shù)只能按照構(gòu)造的順序調(diào)用。今天就是因?yàn)檫@個(gè)花了我好長(zhǎng)時(shí)間啊,希望我理解是正確的。如有不對(duì),請(qǐng)留言糾正。



          posted on 2011-05-13 21:40 yangpingyu 閱讀(1888) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): grails


          只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          <2011年5月>
          24252627282930
          1234567
          891011121314
          15161718192021
          22232425262728
          2930311234

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿

          隨筆分類(lèi)

          隨筆檔案

          收藏夾

          linux

          產(chǎn)品交互

          分析,設(shè)計(jì),架構(gòu)

          安全

          技術(shù)牛人

          數(shù)據(jù)庫(kù)

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 苍溪县| 舞阳县| 庆云县| 遵义市| 怀化市| 武乡县| 杭州市| 盐津县| 平顺县| 东兰县| 呈贡县| 泽普县| 怀来县| 九台市| 鄂伦春自治旗| 黎川县| 中江县| 延津县| 龙川县| 宁都县| 松江区| 威宁| 澄迈县| 许昌市| 铜山县| 靖边县| 利津县| 读书| 钟祥市| 大厂| 灵宝市| 沿河| 浦江县| 广河县| 彭泽县| 犍为县| 岳普湖县| 江山市| 嘉祥县| 宜宾县| 深水埗区|