kapok

          垃圾桶,嘿嘿,我藏的這么深你們還能找到啊,真牛!

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            455 隨筆 :: 0 文章 :: 76 評論 :: 0 Trackbacks

          http://www.dsc.com.tw/newspaper/43/43-2.htm

          前言

            物件導(dǎo)向程式設(shè)計(jì)(以下簡稱:OOP)經(jīng)過一段時(shí)間的演進(jìn)與發(fā)展,目前已漸漸成為設(shè)計(jì)系統(tǒng)的主流,由於物件(object)的概念與實(shí)際世界的個(gè)體非常相似,因此用這種方式來進(jìn)行系統(tǒng)的設(shè)計(jì),可以很真實(shí)的模擬實(shí)際個(gè)體的動(dòng)作,縮短系統(tǒng)與真實(shí)世界的距離。但是,以O(shè)OP設(shè)計(jì)系統(tǒng),有時(shí)候還是會遇到一些困擾,以下面的類別(class)為例:

          public class SomeBusinessClass {
          //Core data members //此class的核心資料
          //Other data members: Log stream, data-consistency //其他Class的資料
          public void performSomeOperation(OperationInformation) {
          //Ensure authentication//進(jìn)行身分驗(yàn)證
          //Lock the object to ensure data-consistency in case other threads access it
          //Ensure the cache is up to date
          //Log the start of operation //記錄日誌
          // ==== Perform the core operation ==== //處理核心作業(yè)
          //Log the completion of operation //記錄日誌
          //Unlock the object
          }
          //More operations similar to above
          public void save(PersitanceStorage ps) {
          }
          public void load(PersitanceStorage ps) {
          }
          }

          即使是這樣簡單的類別,還是有一些地方值得我們注意:
          1. “Other data members”部分所宣告的物件,基本上並不屬於這個(gè)類別主要的關(guān)心事項(xiàng)(concern),雖然還是必須要用到它。
          2. “performSomeOperation()”這個(gè)method除了處理自己的核心作業(yè)外,還必須負(fù)責(zé)呼叫身分驗(yàn)證、鎖定欲處理的資料、記錄日誌等額外的工作,這些工作似乎與他的責(zé)任不太相關(guān),但又找不出必須由誰來做這些事情。
          3. 如果save()及l(fā)oad()兩個(gè)method也是這個(gè)類別的核心程式的話,我們很容易就忽略了這層關(guān)係,使注意力集中到別的地方了。

             基於這一類的問題,我們可以看出,OOP雖然可以很適當(dāng)?shù)谋憩F(xiàn)出系統(tǒng)的模組化,但當(dāng)遇到需要跨越模組(或類別)的應(yīng)用時(shí)(這些模組(或類別)可能與主要作業(yè)邏輯是沒有什麼關(guān)係的,如身分驗(yàn)證、日誌記錄等),OOP就無法以較自然的方式來表現(xiàn)或處理。這種被稱為橫切關(guān)係(crosscutting)的行為,常會造成軟體設(shè)計(jì)或?qū)嵶鲿r(shí)不夠簡潔,不易了解,甚至?xí)斐扇蔗峋S護(hù)上的困難。雖然我們可以利用extend的方式來加以萃取,或引入design pattern來減少這種情形,但由於使用的地方不同,就必須引入新的設(shè)計(jì),不但造成設(shè)計(jì)的困難度增加,也造成日後他人學(xué)習(xí)、維護(hù)上的障礙;圖一 是另一個(gè)例子,紅色字是所謂的橫切關(guān)係的部分,程式的錯(cuò)綜複雜可想而知。為了解決上述橫切關(guān)係所造成的困擾,AOP的概念便被提出。


          圖 一

          AOP(Aspect Oriented Programming)
            AOP主要的概念是萃取互相獨(dú)立的橫切關(guān)係事項(xiàng)而加以模組化,使之可以有效的集中、管理,而不會分散在程式碼的各個(gè)地方。AOP能有這樣的能力,主要是其特殊的實(shí)作架構(gòu),實(shí)現(xiàn)AOP的運(yùn)作原理,首先必須先告訴aspect的實(shí)作者(例如稍後會提到的AspectJ),程式的哪些特殊點(diǎn)為橫切關(guān)係(我們可以稱其為連接點(diǎn)(join point),一般如method被呼叫的點(diǎn)),將被攔截並加入aspect的規(guī)則,這些aspect規(guī)則可能是額外的動(dòng)作或取代原來程式功能等,接著透過aspect實(shí)作者特有的compiler,將相對應(yīng)的aspect程式碼加入之前設(shè)定的連接點(diǎn)中(一般稱為aspect weaver,參考 圖二),然後再加以執(zhí)行,便可以使原來的程式不用在程式碼中呼叫這些特殊橫切關(guān)係,而仍能得到所要的結(jié)果(以log的例子來說,我們不用再呼叫l(wèi)og,但程式執(zhí)行的結(jié)果卻會幫我們產(chǎn)生log)。


          圖 二

          所以加入了AOP的概念後,我們可以看到 圖 1 的程式可以有這樣的改變(如:圖 三)


          圖三

            由於AOP在使用上有這種特殊性,當(dāng)我們在使用它的時(shí)候,一般會搭配其他主要的程式設(shè)計(jì)的方法來共同建構(gòu)一個(gè)系統(tǒng),例如,以O(shè)OP來當(dāng)成它的主要底層結(jié)構(gòu),建構(gòu)系統(tǒng)主要的核心作業(yè),然後再用AOP填補(bǔ)OOP不足的特殊橫切關(guān)係。

             有了aspect的理論架構(gòu),接著我們來看看怎麼將它實(shí)際的應(yīng)用在我們的另一個(gè)主題“單元測試(Unit Test)”上;我們將使用Xerox的PARC實(shí)驗(yàn)室發(fā)展出來的AspectJ來作為接下來實(shí)作的語言。AspectJ(Java base AOP implementation language)是實(shí)作AOP的語言之一,它擴(kuò)充自Java語言,並與Java語言互相搭配,一同實(shí)現(xiàn)aspect的機(jī)制。

          傳統(tǒng)的單元測試(Unit Test)方式
            當(dāng)系統(tǒng)開發(fā)時(shí),常常需要對某一個(gè)個(gè)別的物件進(jìn)行單元測試,以求得物件的正確性;圖 4 是一個(gè)簡單的單元測試模型。

            當(dāng)我們針對某個(gè)物件進(jìn)行單元測試時(shí),如果這個(gè)被測的物件又呼叫到別的物件(如 圖 四 中LoginView物件用到AccessController物件時(shí)),此時(shí)為了使這個(gè)被呼叫的物件不會影響到我們的被測物件,我們常常就會用一個(gè)假的物件來取代這個(gè)額外物件,這個(gè)假物件就是單元測試中常常被提到的Mock Object(如 圖 5 的例子)。使用Mock Object的好處是我們可以保留唯一一個(gè)真實(shí)的被測物,使其他額外的物件都是模擬的,如此便可以很容易的餵入測試資料或模擬其他如資料庫或網(wǎng)路連線等狀態(tài),來達(dá)到測試的需求。

          圖四

          雖然Mock Object可以解決這些問題,但隨著系統(tǒng)的發(fā)展及變更,Mock Object會跟著越來越多,反而造成Mock Object維護(hù)不易,單元測試的成本愈來愈高;於是,我們必須找出一種方法,既可以不用維護(hù)Mock Object的程式碼,又可以達(dá)到Mock Object的功能與好處。

            由於AOP的概念之一是攔截特殊method的呼叫,並取代其內(nèi)容,這樣的行為與Mock Object的功能非常類似,再加上部分單元測試的研究文獻(xiàn),也提到這樣的做法,於是我們認(rèn)為使用AspectJ,應(yīng)該可以解決Mock Object所產(chǎn)生的問題。


          圖五

          使用Aspect的概念來進(jìn)行單元測試?yán)肁OP的概念,整個(gè)測試架構(gòu)可以修改如 圖 六。

          圖六

          詳細(xì)的動(dòng)作過程,說明如下:
          1. 首先設(shè)定攔截連接點(diǎn)的條件(利用AspectJ的程式來實(shí)作,如 圖 6 MethodInterceptor部分),我們假設(shè)所有被測物件及其相關(guān)的所有物件的method呼叫都是可攔截的連接點(diǎn),而Unit Test class及AspectBasedTest class則不在攔截的範(fàn)圍,因?yàn)閠est case內(nèi)的程式碼,並沒有被攔截取代的必要。
          2. 接著提供一個(gè)可以設(shè)定哪些method將被取代的設(shè)定機(jī)制(如 圖六 的AspectBasedTest class);當(dāng)程式執(zhí)行時(shí),很多相關(guān)的method呼叫都會被攔截,我們必須可以設(shè)定哪一些method要用我們的設(shè)定值來取代其執(zhí)行結(jié)果,哪一些method被攔截後不會被取代,而是進(jìn)行正常的執(zhí)行動(dòng)作。這樣,當(dāng)測試程式執(zhí)行時(shí),我們才能掌握所有的測試環(huán)境。
          3. 完成了上述的機(jī)制後,我們看一下整個(gè)動(dòng)作的過程;當(dāng)測試程式開始執(zhí)行時(shí),會先設(shè)定要被取代的method call(即連接點(diǎn)),取代後又會傳回哪一些回傳值等,接著便開始執(zhí)行測試的程式碼,當(dāng)它呼叫設(shè)定的method call時(shí),AspectJ的程式便會去判斷這些method call是否被指定要被取代,如果不需要被取代,則程式便會執(zhí)行原來的method,如果這些method call已經(jīng)被設(shè)定必須被取代,則AspectJ便會傳回被指定的回傳值,使被測物件能在執(zhí)行時(shí)取到事先設(shè)定的測試資料,最後,測試完成並顯示測試的結(jié)果。
          4. 以下是AspectJ攔截點(diǎn)與設(shè)定模擬method機(jī)制的程式碼及說明:
          /** 此class讓使用者可以設(shè)定哪一些method call要在攔截時(shí)被取代以及取代後要傳回什麼要的值,這就好像模擬一個(gè)Mock Object的method call,只是我們不需真的去寫Mock Object
          */
          public class ComponentTestCase extends TestCase
          {
          public ComponentTestCase(String name)
          {
          super(name);
          }
          //設(shè)定哪些method call要被當(dāng)成mock的method call以及當(dāng)它被呼叫時(shí),要傳回什麼值。這裡我們主要是使用一個(gè)Hashtable來儲存使用者設(shè)定的回傳值,當(dāng)程式執(zhí)行時(shí)呼叫到這個(gè)method,取代的機(jī)制就會啟動(dòng),於是就會到Hashtable內(nèi)去取得相對應(yīng)的回傳值;如果找不到相對應(yīng)的值,就傳回null,代表該method並未被設(shè)定要被取代
          public static void setMock(String className, String methodName, Object returnValue)
          {
          testData.put(makeKey(className, methodName), returnValue);
          }
          public static void setMock(String className, String methodName)
          {
          setMock(className, methodName, new Object());
          }
          //取回之前設(shè)定的回傳值
          public static Object getMockReturnValue(String className, String methodName)
          {
          return testData.get(makeKey(className, methodName));
          }
          //驗(yàn)證設(shè)定的mock method call是否如預(yù)期的被測試程式使用到
          public void assertCalled(String className, String methodName)
          {
          if ( ! isCalled(className, methodName) )
          fail("The method '" + methodName + "' in class '" +
          className + "' was expected to be called but it wasn't");
          }
          //驗(yàn)證使用mock method call的參數(shù)是否如預(yù)期正確輸入的相關(guān)程式
          public Object getArgument(String className, String methodName, String argumentName)
          {
          Object argument = null;
          Hashtable arguments = (Hashtable)callsMade.get(makeKey(className, methodName));
          if (arguments != null)
          argument = arguments.get(argumentName);
          return argument;
          }
          //驗(yàn)證設(shè)定的mock method call是否如預(yù)期的被測試程式使用到的相關(guān)程式
          public static void indicateCalled(String className, String methodName, Hashtable arguments)
          {
          callsMade.put(makeKey(className, methodName), arguments);
          }
          //驗(yàn)證設(shè)定的mock method call是否如預(yù)期的被測試程式使用到的相關(guān)程式
          public static boolean isCalled(String className, String methodName)
          {
          return callsMade.get(makeKey(className, methodName)) != null;
          }
          //驗(yàn)證使用mock method call的參數(shù)是否如預(yù)期正確輸入
          public void assertArgumentPassed(String className, String methodName,
          String argumentName, Object argumentValue)
          {
          Object argument = getArgument(className, methodName, argumentName);
          if (argument == null || !argument.equals(argumentValue))
          fail("The argument '" + argumentName + "' of method '" +
          methodName + "' in class '" +
          className + " ' should have the value '" +
          argumentValue + "' but it was '" +
          argument + "'!");
          }
          //組合Hashtable所使用的Key的程式
          private static String makeKey(String className, String methodName)
          {
          return className + "." + methodName;
          }
          private static Hashtable testData = new Hashtable();
          private static Hashtable callsMade = new Hashtable(); }

          /**此程式是AspectJ攔截點(diǎn)設(shè)定程式,主要是設(shè)定哪些method call會被此程式加以取代,也就是說,被設(shè)定了攔截的method call,在別人呼叫時(shí),會先跑到這個(gè)程式來執(zhí)行,再依程式的邏輯決定要執(zhí)行真正的method或用其他值取代
          */
          aspect AspectBasedMethodInterceptor
          {
          pointcut allCalls():execution(* *.*(..)) && !within(ajmock.*); //設(shè)定哪些method call是攔截點(diǎn)
          Object around() : allCalls() //攔截點(diǎn)攔截後要做的事(around()在AspectJ中是取代攔截的call)
          {
          String className = thisJoinPoint.getSignature().getDeclaringType().getName();
          Object receiver = thisJoinPoint.getThis();
          if (receiver != null)
          className = receiver.getClass().getName();
          String methodName = thisJoinPoint.getSignature().getName();
          //嘗試去取得mock method call的回傳設(shè)定值
          Object returnValue = ajmock.ComponentTestCase.getMockReturnValue(className, methodName);
          //回傳值如果不是空的,表示此method call是使用者設(shè)定的mock method call必須用使用者設(shè)定的回傳值來取代
          if (returnValue != null)
          {
          Hashtable arguments = (Hashtable)getArguments(thisJoinPoint);
          //呼叫此method主要是為之後的method call驗(yàn)證之用
          ComponentTestCase.indicateCalled(className, methodName, arguments);
          return returnValue;
          } else {
          //如果回傳值是空的,表示此method call不是使用者設(shè)定的mock method call,必須執(zhí)行原來的method,而不被取代
          return proceed();
          }
          }
          //取得method call的參數(shù)值以便做事後的驗(yàn)證(驗(yàn)證是否與使用者之前設(shè)定的參數(shù)值相同)
          private Hashtable getArguments(JoinPoint jp)
          {
          Hashtable arguments = new Hashtable();
          Object[] argumentValues = jp.getArgs();
          String[] argumentNames =
          ((CodeSignature)jp.getSignature()).getParameterNames();
          for (int i = 0; i < argumentValues.length; i++)
          {
          if (argumentValues[i] != null)
          arguments.put(argumentNames[i], argumentValues[i]);
          }
          return arguments;
          }
          }
          5. 測試程式便可以這樣寫
          public class TestLoginView extends ComponentTestCase { //繼承設(shè)定模擬method機(jī)制的class

          public TestLoginView(String s) {
          super(s);
          }
          protected void setUp() {
          }
          public void testValidateValidUser() {
          LoginView view = new LoginView();
          Integer mockResult = new Integer(AccessController.USER_INVALID);
          //設(shè)定ajusage.AccessController的login method要被取代,取代值是mockResult
          setMock("ajusage.AccessController","login", mockResult);
          /呼叫被測試method
          view.validate();
          //驗(yàn)證測試結(jié)果
          assertEquals("login successful", view.getStatus());
          }

          如此便達(dá)到我們的需求,不但可以使用Mock Object的好處,又可以不須維護(hù)額外的程式碼。

          總結(jié)
            麻省理工學(xué)院在2001一月份出刊的Technology Review雜誌中,特別選出可改變未來世界的10大創(chuàng)新科技(http://www.technologyreview.com/articles /tr10_toc0101.asp),在其中一項(xiàng)”解開糾結(jié)的程式碼”(Untangling Code)中提到,AOP的出現(xiàn)能有效幫助軟體發(fā)展者開發(fā)出容易解讀的程式碼,降低軟體開發(fā)的複雜度,所以將其列為可以改變未來世紀(jì)的科技之一。對AOP所擁有的功能來說,Unit Test祇是其中一小部份的應(yīng)用,它對程式的模組化或重整等,也都可發(fā)揮不小的益處,端看我們對它的活用程度而定。而這篇文章,也不過是一個(gè)開端而已。

          1. 參考資料
          如果你想更詳細(xì)的了解AOP的內(nèi)容,也可以參考以下資料:
          1. AspectJ網(wǎng)站:
          http://www.eclipse.org/aspectj/
          2. I want my AOP, Part1, Part2, part3 (By Ramnivas Laddad):
          http://www.javaworld.com/javaworld/jw-01-2002/jw-0118-aspect.html
          http://www.javaworld.com/javaworld/jw-03-2002/jw-0301-aspect2.html
          http://www.javaworld.com/javaworld/jw-04-2002/jw-0412-aspect3.html
          3. Junit網(wǎng)站:
          http://www.junit.org
          4. Virtual Mock Objects using AspectJ with JUNIT,
          http://www.xprogramming.com/xpmag/virtualMockObjects.htm
          5. AspectJ for JBuilder,
          http://aspectj4jbuildr.sourceforge.net/
          6. Mock Object,
          http://www.mockobjects.com

          posted on 2005-05-08 00:05 笨笨 閱讀(645) 評論(0)  編輯  收藏 所屬分類: J2EEHibernateAndSpringALL
          主站蜘蛛池模板: 巴彦淖尔市| 三明市| 涿州市| 教育| 林甸县| 射洪县| 东方市| 克拉玛依市| 阿克苏市| 肃北| 绥江县| 洪江市| 阜城县| 黎城县| 河源市| 南投县| 大田县| 洪江市| 壶关县| 嵊州市| 嘉黎县| 河西区| 承德县| 汾阳市| 乾安县| 孟津县| 丰都县| 会东县| 永靖县| 体育| 舒兰市| 高邑县| 页游| 台江县| 南康市| 仪征市| 昭苏县| 廉江市| 廊坊市| 镇康县| 达拉特旗|