隨筆 - 23  文章 - 11  trackbacks - 0
          <2008年8月>
          272829303112
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          常用鏈接

          留言簿(3)

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          數(shù)據(jù)庫(kù)測(cè)試

          在創(chuàng)建企業(yè)級(jí)應(yīng)用的時(shí)候,數(shù)據(jù)層的單元測(cè)試因?yàn)槠鋸?fù)雜性往往被遺棄,Unitils大大降低了測(cè)試的復(fù)雜性,使得數(shù)據(jù)庫(kù)的測(cè)試變得容易并且易維護(hù)。已下介紹databasemodule和dbunitmodule進(jìn)行數(shù)據(jù)庫(kù)的單元測(cè)試。

          用dbUnit管理測(cè)試數(shù)據(jù)

          數(shù)據(jù)庫(kù)的測(cè)試應(yīng)該在單元測(cè)試數(shù)據(jù)庫(kù)上運(yùn)行,單元測(cè)試數(shù)據(jù)庫(kù)給我們提供了一個(gè)完整的并有著很好細(xì)粒度控制的測(cè)試數(shù)據(jù),DbUnitModule是在dbunit的基礎(chǔ)上進(jìn)一步的為數(shù)據(jù)庫(kù)的測(cè)試提供數(shù)據(jù)集的支持。

          加載測(cè)試數(shù)據(jù)集

          讓我們以UserDAO中一個(gè)簡(jiǎn)單的方法findByName(檢查姓氏和名字)為例子開(kāi)始介紹。他的單元測(cè)試如下:

          @DataSet

          public class UserDAOTest extends UnitilsJUnit4 {

              @Test

              public void testFindByName() {

                  User result = userDao.findByName("doe", "john");

                  assertPropertyLenEquals("userName", "jdoe", result);

              }

              @Test

              public void testFindByMinimalAge() {

                  List<User> result = userDao.findByMinimalAge(18);        

                  assertPropertyLenEquals("firstName", Arrays.asList("jack"), result);

              }

          }

              @DateSet 注解表示了測(cè)試需要尋找dbunit的數(shù)據(jù)集文件進(jìn)行加載,如果沒(méi)有指明數(shù)據(jù)集的文件名,則Unitils自動(dòng)在class文件的同目錄下加載文件名為 className.xml的數(shù)據(jù)集文件。(這種定義到class上面的數(shù)據(jù)集稱為class級(jí)別的數(shù)據(jù)集)

              數(shù)據(jù)集 文件必須是dbunit的FlatXMLDataSet文件格式,其中包含了所要測(cè)試的數(shù)據(jù)。測(cè)試數(shù)據(jù)庫(kù)表中所有的內(nèi)容將會(huì)被刪除,然后再插入數(shù)據(jù)集中的 數(shù)據(jù)。如果表不屬于數(shù)據(jù)集中的,哪么該表的數(shù)據(jù)將不會(huì)被刪除。你也可以明確的加入一個(gè)空的表元素,例如<MY_TABLE/>(可以達(dá)到刪除 測(cè)試數(shù)據(jù)庫(kù)表中內(nèi)容的作用),如果要明確指定一個(gè)空的值,那么使用值[null]。

             為UserDAOTest我們創(chuàng)建一個(gè)數(shù)據(jù)集,并放在UserDAOTest.class文件同目錄下。

          <?xml version='1.0' encoding='UTF-8'?>

          <dataset>

              <usergroup name="admin" />  

              <user userName="jdoe"  name="doe"   firstname="john"   userGroup="admin" />

              <usergroup name="sales" />    

              <user userName="smith" name="smith" userGroup="sales" />

              

          </dataset>

             測(cè)試運(yùn)行的時(shí)候,首先將刪除掉usergroup表和user表中的所有內(nèi)容,然后將插入數(shù)據(jù)集中的內(nèi)容。其中name為smith的firstname的值將會(huì)是null。

             假設(shè)testFindByMinimalAge()方法將使用一個(gè)特殊的數(shù)據(jù)集而不是使用class級(jí)別的數(shù)據(jù)集,你可以定義一個(gè)UserDAOTest.testFindByMinimalAge.xml 數(shù)據(jù)集文件并放在測(cè)試類的class文件同目錄下。

          <?xml version='1.0' encoding='UTF-8'?>

          <dataset>

              <user userName="jack" age="18" />

              <user userName="jim"  age="17" />

          </dataset>

          這時(shí),你在testFindByMinimalAge()方法使用@DataSet注解,他將覆蓋class級(jí)的數(shù)據(jù)集

          public class UserDAOTest extends UnitilsJUnit4 {

          @Test

          @DataSet("UserDAOTest.testFindByMinimalAge.xml")

          public void testFindByMinimalAge() {

          List<User> result = userDao.findByMinimalAge(18); 

          assertPropertyLenEquals("firstName", Arrays.asList("jack"), result);

          }

          }

          不要過(guò)多的使用method級(jí)的數(shù)據(jù)集,因?yàn)檫^(guò)多的數(shù)據(jù)集文件意味著你要花大量的時(shí)間去維護(hù),你優(yōu)先考慮的是使用class級(jí)的數(shù)據(jù)集。

          配置數(shù)據(jù)集加載策略

          缺省情況下數(shù)據(jù)集被寫(xiě)入數(shù)據(jù)庫(kù)采用的是clean insert策略。這就意味著數(shù)據(jù)在被寫(xiě)入數(shù)據(jù)庫(kù)的時(shí)候是會(huì)先刪除數(shù)據(jù)集中有使用的表的數(shù)據(jù),然后在將數(shù)據(jù)集中的數(shù)據(jù)寫(xiě)入數(shù)據(jù)庫(kù)。加載策略是可配額制的,我們通過(guò)修改DbUnitModule.DataSet.loadStrategy.default 的屬性值來(lái)改變加載策略。假設(shè)我們?cè)趗nitils.properties屬性文件中加入以下內(nèi)容:

          DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.InsertLoadStrategy 

          這時(shí)加載策略就由clean insert變成了insert,數(shù)據(jù)已經(jīng)存在表中將不會(huì)被刪除,測(cè)試數(shù)據(jù)只是進(jìn)行插入操作。

          加載策略也可以使用@DataSet的注解屬性對(duì)單獨(dú)的一些測(cè)試進(jìn)行配置:

          @DataSet(loadStrategy = InsertLoadStrategy.class) 

          對(duì)于那些樹(shù)形DbUnit的人來(lái)說(shuō),配置加載策略實(shí)際上就是使用不同的DatabaseOperation,以下是默認(rèn)支持的加載策略方式:

          CleanInsertLoadStrategy: 先刪除dateSet中有關(guān)表的數(shù)據(jù),然后再插入數(shù)據(jù)。

          InsertLoadStrategy: 只插入數(shù)據(jù)。

          RefreshLoadStrategy: 有同樣key的數(shù)據(jù)更新,沒(méi)有的插入。

          UpdateLoadStrategy: 有同樣key的數(shù)據(jù)更新,沒(méi)有的不做任何操作。

          配置數(shù)據(jù)集工廠

           在Unitils中數(shù)據(jù)集文件采用了multischema xml 格式,這是DbUnits的FlatXmlDataSet 格式的擴(kuò)展。配置文件格式和文件的擴(kuò)展可以采用DataSetFactory 

          雖然Unitils當(dāng)前只支持一種數(shù)據(jù)格式,但是我們可以通過(guò)實(shí)現(xiàn)DataSetFactory來(lái)使用其他文件格式。當(dāng)你想使用excel而不是xml格式的時(shí)候,可以通過(guò)unitils.property中的DbUnitModule.DataSet.factory.default 屬性和@DataSet 注解來(lái)創(chuàng)建一個(gè)DbUnit's XlsDataSet 實(shí)例。

          驗(yàn)證測(cè)試結(jié)果

          有些時(shí)候我們想在測(cè)試時(shí)完畢后使用數(shù)據(jù)集來(lái)檢查數(shù)據(jù)庫(kù)中的內(nèi)容,舉個(gè)例子當(dāng)執(zhí)行完畢一個(gè)存儲(chǔ)過(guò)程后你想檢查一下啊數(shù)據(jù)是否更新了沒(méi)有。

          下面的例子表示的是禁用到一年內(nèi)沒(méi)有使用過(guò)的帳戶

          public class UserDAOTest extends UnitilsJUnit4 { 

              @Test @ExpectedDataSet 

              public void testInactivateOldAccounts() { 

                  userDao.inactivateOldAccounts(); 

              } 

          注意在test方法上增加了一個(gè)@ExpectedDataSet 注解。這將指明unitils將使用UserDAOTest.testInactivateOldAccounts-result.xml 這個(gè)數(shù)據(jù)集的內(nèi)容和數(shù)據(jù)庫(kù)的內(nèi)容進(jìn)行比較。

          <?xml version='1.0' encoding='UTF-8'?> 

          <dataset> 

              <user userName="jack" active="true" /> 

              <user userName="jim"  active="false" /> 

          </dataset> 

          根據(jù)這個(gè)數(shù)據(jù)集,將會(huì)檢查是否有兩條和記錄集的值相同的記錄在數(shù)據(jù)庫(kù)中。而其他的記錄和表將不理會(huì)。

          使用的是@DataSet 注解的話,文件名可以明確指出,如果文件名沒(méi)有明確指出來(lái),那么文件名將匹配className .methodName -result.xml 

          使用少使用結(jié)果數(shù)據(jù)集,加入新的數(shù)據(jù)集意味著更多的維護(hù)。替代方式是在代碼中執(zhí)行相同的檢查(如使用一個(gè)findactiveusers()方法)。

          使用多模式的數(shù)據(jù)集

          一個(gè)程序不單單只是連接一個(gè)數(shù)據(jù)庫(kù)shema。Unitils采用了擴(kuò)展的數(shù)據(jù)集xml來(lái)定義多schemas下的數(shù)據(jù)。以下就是一個(gè)讀取數(shù)據(jù)到2個(gè)不同的schemas中的例子:

          <?xml version='1.0' encoding='UTF-8'?> 

          <dataset xmlns="SCHEMA_A" xmlns:b="SCHEMA_B"> 

              <user id="1" userName="jack" />     

              <b:role id="1" roleName="admin" /> 

          </dataset> 

          在這個(gè)例子中我定義了兩個(gè)schemas,SCHEMA_A 和 SCHEMA_B第一個(gè)schema,SCHEMA_A 被連接到默認(rèn)的xml命名空間中,第二個(gè)schema,SCHEMA_B 被連接到命名空間b。如果表xml元素的前綴使用了命名空間b,那么該表就是schema SCHEMA_B 中的,如果沒(méi)有使用任何的命名空間那么該表將被認(rèn)為是SCHEMA_A  

          中的。以上例子中測(cè)試數(shù)據(jù)定義了表SCHEMA_A.user SCHEMA_B.role

          如果在數(shù)據(jù)集中沒(méi)有配置一個(gè)默認(rèn)的命名空間,那么將會(huì)采用在unitils.properties中的屬性database.schemaNames 的第一個(gè)值作為默認(rèn)的

          database.schemaNames=SCHEMA_A, SCHEMA_B 

          這個(gè)配置將SCHEMA_A 作為缺省的schema,這樣你可以簡(jiǎn)化數(shù)據(jù)集的聲明。

          <?xml version='1.0' encoding='UTF-8'?> 

          <dataset xmlns:b="SCHEMA_B"> 

              <user id="1" userName="jack" />     

              <b:role id="1" roleName="admin" /> 

          </dataset> 

          連接測(cè)試數(shù)據(jù)庫(kù) 

          在以上所有的例子中,我們都有一件重要的事情沒(méi)有做:當(dāng)我們進(jìn)行測(cè)試的時(shí)候,怎樣連接數(shù)據(jù)庫(kù)并得到DataSource

          當(dāng)測(cè)試套件的第一個(gè)測(cè)試數(shù)據(jù)庫(kù)的案例運(yùn)行的時(shí)候,Unitils將會(huì)通過(guò)屬性文件創(chuàng)建一個(gè)DataSource 的實(shí)例來(lái)連接你單元測(cè)試時(shí)的數(shù)據(jù)庫(kù),以后的測(cè)試中都將使用這個(gè)DataSource 實(shí)例。連接配置的詳細(xì)內(nèi)容如下:

          database.driverClassName=oracle.jdbc.driver.OracleDriver 

          database.url=jdbc:oracle:thin:@yourmachine:1521:YOUR_DB 

          database.userName=john 

          database.password=secret 

          database.schemaNames=test_john 

          配置章節(jié)所說(shuō)的那樣,你可以將連接數(shù)據(jù)庫(kù)的驅(qū)動(dòng)類和url地址配置到unitils.properties 中去,而用戶名,密碼以及schema可以配置到unitils-local.properties 中去,這樣可以讓開(kāi)發(fā)人員連接到自己的單元測(cè)試數(shù)據(jù)庫(kù)中進(jìn)行測(cè)試而不會(huì)干預(yù)到其他的人。

          在屬性或者setter方法前使用注解@TestDataSource ,將會(huì)將DataSource 實(shí)例注入到測(cè)試實(shí)例中去,如果你想加入一些代碼或者配置一下你的datasource,你可以做一個(gè)抽象類來(lái)實(shí)現(xiàn)該功能,所有的測(cè)試類都繼承該類。一個(gè)簡(jiǎn)單的例子如下:

          public abstract class BaseDAOTest extends UnitilsJUnit4 { 

              @TestDataSource 

              private DataSource dataSource; 

               

              @Before     

              public void initializeDao() { 

                  BaseDAO dao = getDaoUnderTest(); 

                  dao.setDataSource(dataSource); 

              } 

              protected abstract BaseDAO getDaoUnderTest(); 

          上面的例子采用了注解來(lái)取得一個(gè)datasource的引用,另外一種方式就是使用DatabaseUnitils.getDataSource() 方法來(lái)取得datasource。

          事務(wù)

          出于不同的原因,我們的測(cè)試都是運(yùn)行在一個(gè)事務(wù)中的,其中最重要的原因如下:

          數(shù)據(jù)庫(kù)的很多action都是在事務(wù)正常提交后才做,如SELECT FOR UPDATE 和觸發(fā)器

          許多項(xiàng)目在測(cè)試數(shù)據(jù)的時(shí)候都會(huì)填寫(xiě)一些測(cè)試數(shù)據(jù),每個(gè)測(cè)試運(yùn)行都會(huì)修改或者更新了數(shù)據(jù),當(dāng)下一個(gè)測(cè)試運(yùn)行的時(shí)候,都需要將數(shù)據(jù)回復(fù)到原有的狀態(tài)。

          如果使用的是hibernate或者JPA的時(shí)候,都需要每個(gè)測(cè)試都運(yùn)行在事務(wù)中,保證系統(tǒng)的正常工作。

          缺省情況下,事務(wù)管理是disabled的,事務(wù)的默認(rèn)行為我們可以通過(guò)屬性文件的配置加以改變:

          DatabaseModule.Transactional.value.default=commit 

          采用這個(gè)設(shè)置,每個(gè)的測(cè)試都將執(zhí)行commit,其他的屬性值還有rollback disabled 

          我們也可以通過(guò)在測(cè)試類上使用注解@Transactional 來(lái)改變默認(rèn)的事務(wù)設(shè)置,如:

          @Transactional(TransactionMode.ROLLBACK) 

          public class UserDaoTest extends UnitilsJUnit4 { 

          通過(guò)這種class上注解的事務(wù)管理,可以讓每個(gè)測(cè)試都確保回滾,@Transactional 注解還可以繼承的,因此我們可以將其放在父類中,而不必每個(gè)子類都進(jìn)行聲明。

          .........

          如果你使用Unitils的spring支持(見(jiàn)使用spring進(jìn)行測(cè)試)你如果配置了PlatformTransactionManager 的bean,那么unitils將會(huì)使用這個(gè)事務(wù)管理。

          posted on 2008-08-25 15:11 小小~咖啡豆 閱讀(1944) 評(píng)論(2)  編輯  收藏 所屬分類: 測(cè)試

          FeedBack:
          # re: Unitils開(kāi)發(fā)指南(三) 2008-08-26 12:13 learnsea
          寫(xiě)的非常好!對(duì)我很有用!非常感謝!期待繼續(xù),時(shí)該關(guān)注著!  回復(fù)  更多評(píng)論
            
          # re: Unitils開(kāi)發(fā)指南(三) 2009-04-13 11:17 ss
          不錯(cuò)  回復(fù)  更多評(píng)論
            

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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 临桂县| 霸州市| 朔州市| 全州县| 将乐县| 福海县| 怀仁县| 西城区| 建湖县| 黔南| 法库县| 泰安市| 云浮市| 兰州市| 府谷县| 荥经县| 射洪县| 岑溪市| 遂平县| 新郑市| 盘山县| 湾仔区| 屯昌县| 怀化市| 望江县| 屯留县| 小金县| 鄂伦春自治旗| 尼木县| 桐庐县| 修文县| 杭锦后旗| 云南省| 四平市| 泸州市| 庄浪县| 舒兰市| 恩施市| 永德县| 类乌齐县| 丰镇市|