程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> AOP@Work: 對方面進行單元測試-驗證橫切行為的八種新模式

AOP@Work: 對方面進行單元測試-驗證橫切行為的八種新模式

編輯:關於JAVA

簡介:AOP 使編寫特定於應用程序的橫切關注點測試比任何時候都要容易了 。 要了解其原因以及如何實現,請聽 Nicholas Lesiecki 介紹測試面向方面的代 碼 所具有的好處,並展示在 AspectJ 中測試橫切行為的樣式。

在過去五年中廣泛采用的程序員測試是由顯著的生產率和得到的代碼質量所 驅 動的。不過,在面向方面編程(AOP)出現之前,為橫切行為(如安全、事務管 理 或者持久性)編寫測試很困難。為什麼呢?因為這些行為沒有很好地模塊化。如 果沒有可測試的單元,那麼就很難編寫出單元測試。隨著 AOP 的普及,編寫與 橫 切關注點在目標系統中的實現無關的、對其進行檢查的測試已變得可行並且值得 去做。

在本文中,我將介紹測試用方面實現的橫切行為的一組技術。重點放在方面 的 單元測試,但是我也展示了有助於建立對面向方面應用程序的信任度的其他模式 。很快您就會看到,測試方面涉及許多與測試對象相同的技巧和概念,並且有許 多相同的做法和設計好處。

本文的撰寫基於我在 AspectJ 中的經驗。許多概念應當可以移植到其他 AOP 實現中,但是有些概念是特定於語言的。請參閱 下載 以下載本文源代碼,請參 閱 參考資料 以下載完成例子所需要的 Aspectj 和 AJDT。

面向方面代碼的單元測試

應用程序的好的自動測試集應當像圖 1 那樣:以隔離的、對各個類的測試構 成一個廣泛的基礎,使測試覆蓋面廣,並能迅速分離出錯誤。在這之上是集成的 、端到端的系統測試,它驗證各單元是否可以協調工作。如果這些層是良構的並 且頻繁運行,那麼它們結合在一起可以增加對於應用程序行為的信任度。

在金字塔底部的單元測試很重要,這有幾個理由。首先,它們幫助您產生那 些 在集成測試中難於再現或者需要繁瑣步驟才能再現的關注點用例。其次,由於它 們涉及的代碼更少,因此它們運行起來通常更快(因此可以更經常地運行它們) 。第三,它們可以幫助您思考每個單元的接口和要求。好的單元測試要求單元間 的松散耦合,這是在測試條件下讓測試得以運行的條件。

圖 1. 分層的測試

但是橫切行為會怎麼樣呢?想像一位客戶的要求:“在執行對 ATM 類的任何 操作之前要檢查調用者的安全憑證。”當然可以(並且應該)針對這項要求編寫 集成測試。不過,非面向對象的開發環境使得編寫“在操作之前檢查安全性”這 種行為的單元測試很難將這種行為分離出來。這是因為行為混入到了目標系統中 ,讓人很難把握或者用工具分析。但是如果用方面開發,那麼就可以將這些行為 表示為建議(advice),應用到所有匹配某個切點(pointcut)的操作。現在行 為就可以很好地用單元表示,可以在隔離的情況下測試或者在 IDE 顯示它。

面向方面的代碼在哪裡出現問題了

在討論對方面進行單元測試的技術之前,我要簡單討論錯誤類型。橫切行為 分 為兩個主要部分:行為做什麼(我稱之為橫切功能)和行為用在什麼地方(我稱 之為橫切規范)。回到 ATM 的例子,橫切功能檢查調用者的安全憑證。橫切規 范 檢查 ATM 類中每一個公共方法。

為了實現的真正信任度,需要同時檢查功能和規范(或者不嚴格地說,建議 和 切點)。在完成這個例子的過程中,我將強調給定的測試模式是驗證橫切功能、 規范,還是兩者同時驗證。

注意我將重點放在測試切點、建議和支持它們的代碼上。類型間聲明(和其 他 方面功能)當然是可測試的。我在本文中展示的一些技巧稍加修改就可以對它們 使用。它們還有自己的一組技巧,其中許多是很直觀的。不過為了節省篇幅,我 決定不在本文中明確地討論它們。

測試模式編目

我將本文寫成測試面向方面代碼的模式的編目。對於每種模式,我描述了它 針 對哪種類型的錯誤,對該模式進行總結,提供一個例子並討論該模式的優缺點。 編目分為四節:

測試完整的單元 :此節展示了測試完整的系統部分(換句話說,同時測試方 面和非方面類)的模式。這種技術是在沒有使用方面時獲得橫切行為信任度的惟 一方法,並在使用方面時仍然是一種重要的工具。

使用可視化工具 :這裡描述的兩種模式利用了 AspectJ IDE 對 Eclipse 的 支持,也稱為 AJDT。嚴格地講,使用可視化工具檢查應用程序的橫切結構不是 一 種測試技術。不過,它可幫助您理解並獲得對應用程序的橫切關注點的信任。

使用委派 :此節展示可以幫助將前面提到的兩類錯誤分開的兩種模式。通過 將一些邏輯從建議中分離到 helper 類中(或者方法中),可以編寫與橫切規范 無關的、檢查應用程序橫切行為的測試。

使用 mock 目標 :最後一節包括三種模式,它們使用模擬真實建議目標的 “mock 目標”類,可以在不將方面集成到真正目標的條件下測試聯結點匹配和 建 議行為。

Highlighter 方面

為了展示這個編目中的樣式,我使用了一個實現突出顯示搜索術語(即在搜 索 結果中突出顯示用戶的查詢術語)的方面。我實現了與在上一例子中展示的方面 非常相像的一個方面。該系統要在結果匯總頁、細節頁和應用程序的其他一些地 方中突出顯示術語。我在本文中展示的這個例子只橫切一個類,但是原理是一樣 的。清單 1 包含 Highlighter 方面的一個實現:

清單 1. Highlighter 定義了突出顯示行為

public aspect Highlighter{
 /* ITDs to manage highlighted words */
 private Collection<String> Highlightable.highlightedWords;

 public Collection<String> Highlightable.getHighlightedWords() {
   return highlightedWords;
 }
 public void Highlightable.setHighlightedWords(Collection<String>
   highlightedWords){
  this.highlightedWords = highlightedWords;
 }

 public pointcut highlightedTextProperties() :
   (
   execution(public String getProduct())
  || execution(public String getTitle())
  || execution(public String getSummary())
  );

 String around(Highlightable highlightable) :
   highlightedTextProperties() && this(highlightable)
 {
   String highlighted = proceed(highlightable);
  for (String word : highlightable.getHighlightedWords()) {
   Pattern pattern = patternForWord(word);
   Matcher matcher = pattern.matcher (highlighted);
   highlighted = matcher.replaceAll("<span class=
     \"bold\">$0</span>");
  }
     return highlighted;
 }

private Pattern patternForWord (String word) {
 return Pattern.compile("\\b\\Q" + word + "\\E\\b",
  Pattern.CASE_INSENSITIVE);
} 
}

Highlighter 方面捕獲聯結點的返回值並換成突出顯示的版本。它根據存儲 在 Highlightable 接口中一個類型間字段中的突出顯示術語清單選擇要突出顯示的 術語。可以對任何需要表現突出顯示行為的類使用 Highlightable 接口,既可 以 使用在類聲明中,也可以使用 declare parents 語句。

在這個例子的初始版本中,我選用一個非常簡單的切點。在本文的後面,我 將 重寫這個切點以展示一些測試模式。

I. 測試集成的單元

針對 :橫切功能和規范

概述 :如在介紹中說明的,使用方面很容易進行集成測試。這個模式非常簡 單:就像行為沒有實現方面那樣為系統編寫一個測試。換句話說,將對象放到一 起、設置狀態、調用方法,然後驗證結果。關鍵是編寫一個當方面行為錯誤或者 沒有應用到希望它應用的聯結點處時會失敗的測試。如果方面會影響多個聯結點 ,那麼就選擇幾個代表例子。

例子:Highlighter 的集成測試

在清單 2 中要注意的是,這個測試的操作就像對沒有使用方面的應用程序一 樣。它將對象放到一樣、設置狀態、調用方法並驗證結果。

清單 2. 對 Highlighter 的集成測試

public class HighlightSearchResultsIntegrationTest extends TestCase {
 Collection<String> words;
 private SearchResult result;
 public void setUp() throws Exception {
   super.setUp();
  words = new ArrayList<String>();
   words.add("big");
  words.add("grrr");
  result = new SearchResult();
  result.setTitle("I am a big bear!");
   result.setSummary("grrr growl!");
  result.setHighlightedWords (words);
 }
 public void testHighlighting() {
   String expected = "I am a <span class=\"bold\">big</span> bear!";
  assertEquals(expected, result.getTitle());
   expected = "<span class=\"bold\">grrr</span> growl!";
   assertEquals(expected, result.getSummary());
 }
}

優缺點

不管是否使用 AOP,集成測試的代價和優點是類似的。不管哪種情況,主要 的 好處是驗證代碼的高層目標(換句話說,正確突出顯示標題和結束語)。在進行 大的重構時它會提供幫助。它還會找出當組件交互時才會出現的問題。

不過,只進行集成測試會帶來一些問題。如果 HighlightSearchResultsIntegrationTest 失敗,那麼這可能是因為建議邏輯或 者所涉及的其他類(如 SearchResult)有錯誤,而使方面根本沒有運行。事實 上 ,我在開發這個集成測試例子時就遇到了這種情況。我花了 20 分鐘試圖搞清楚 為什麼方面沒有運行,最後發現在正則表達式中有一個暗藏的問題!

集成測試還需要更復雜的設置和斷言,這使它們比分別測試單獨的方面更難 編 寫。並且很難用集成測試模擬代碼需要正確處理的所有臨界情況。

橫切數個類的行為給集成測試帶來了一個特定問題。假定對應用程序中的所 有 類有統一的異常處理。我們不想對每一個類測試這個新行為。相反,希望選擇一 個代表性的例子。但是如果選擇了特定的 域類(比如 Customer 類),並測試 了 它的錯誤處理方面,那麼就會有模糊測試目的的可能性。測試是驗證 Customer 的行為還是驗證應用程序的錯誤處理呢?

II. 使用可視化工具

關於測試廣泛分布的橫切關注點的一個難題是它會報告太多的聯結點。執行 並 檢查所有的匹配是個大麻煩。(另一方面,意外加入不需要的聯結點會更糟糕) 。相應地,下面兩個模式展現了使用在 AJDT 這樣的工具中提供的人工檢測橫切 視圖補充正常測試的好處。(在撰寫本文時,AspectJ 與 AJDT 結合提供了大多 數可視化支持,不過,JBoss AOP 和 JBoss IDE 等其他組合同樣提供了很好的 可 視化工具。)

模式 1. 可視化地檢查橫切

針對 :橫切規范

概述 :在開發方面時使用 AJDT 的 cross-references 視圖查看它要建議哪 些聯結點。人工驗證清單是否完整,並且不包含應忽略的聯結點。

例子:找出不需要的匹配

假定要突出顯示標題、產品和搜索結果的匯總。不用像在 清單 1 中那樣枚 舉 每一個方法,可以說明想要找的是一個更健壯的切點。(關於健壯的切點的更多 內容,請參閱 參考資料 中 Adrian Colyer 的 blog。)下面的切點看來是抓住 了原來的想法:

public pointcut highlightedTextProperties() :
(
  execution(public String get*())
  && ! execution(public * Highlightable.*(..))
);

不過,在用 AJDT 的 cross-references 視圖檢查切點時,會看到圖 2 所示 的內容:

圖 2. AJDT cross-references 視圖中四個建議的聯結點

注意有一個多余的匹配:SearchResult.getWebsite()。您知道這個 Website 不應該突出顯示,因此重新編寫這個切點以排除不需要的匹配。

優缺點

使用 AJDT 的 cross-references 視圖檢查橫切規范有三個主要的好處。首 先 ,cross-references 視圖可以在開發方面時馬上給出反饋。其次,它使您可以 容 易地發現難於測試的結果。(要編寫驗證 getWebsite() 沒有 突出顯示的測試 , 需要猜出 getWebsite() 可能會出錯,或者檢查 SearchResult 中每一個 String getter。越不容易出的錯誤,就越難很好地測試。)第三,自動生成的視圖可以 驗證正確情況,在代碼中驗證它們是很麻煩的。例如,如果搜索 highlighter 需 要影響 20 個聯結點,那麼檢查 cross-references 視圖比為每一個聯結點編寫 測試更容易。

使用視圖驗證的主要缺點是不能自動檢查。它需要程序員的自律。匆忙的程 序 員可能看過圖 2,卻沒有發現問題。(下一個模式展示了對這個問題的部分解決 方案。)另一個問題是橫切視圖只顯示了基於靜態聯結點 shadow 的匹配。換句 話說,如果有依賴於運行時檢查的切點,如 cflow() 或者 if(),那麼 cross- references 視圖不能肯定地說聯結點會在運行時匹配,只能說看來如此。

模式 2. 檢查隨橫切比較工具改變

針對 :橫切規范

概述 :利用 AJDT 的橫切比較功能在重構之前或者其他代碼改變前保存項目 的橫切圖。在完成改變後保存另一個圖。(還可以每晚保存一個圖以便比較。) 在橫切比較工具中比較這些圖,以發現受方面影響的聯結點所出現的不希望的改 變。注意在撰寫本文時,只有 AJDT 提供橫切比較工具。

例子:改寫一個切點

假定要改正上一個例子中表現出的問題,決定修改切點以使用 Java 5 注釋 , 如下所示:

public pointcut highlightedTextProperties() :
    execution (@Highlighted public String Highlightable+.*())

然後在源代碼中適當位置上添加注釋,例如:

@Highlighted
 public String getTitle() {
  return title;
 }

下一步是比較在改變前後所抓取的項目快照,並得到如圖 3 所示的結果。如 您所見,重構消除了 getWebsite() 的建議匹配,但是也消除了 getSummary() 的匹配。(它看上去就像沒有添加上注釋。)

圖 3. 在橫切比較工具中顯示的改變結果

優缺點

這項技術實際上是對上一項技術的優化。通過只顯示改變,橫切比較工具可 以 幫助防止信息盲點。同時,cross-references 視圖要求選擇需要分析的建議或 者 類,而橫切比較工具使您可以檢查整個項目的改變。

缺點是橫切比較工具在方面影響多個聯結點時會不好用。考慮一個記錄所有 公 共方法的日志。這樣一個方面在哪怕一天的開發後也會增加十來個新改變,使得 查看其他更重要的改變變得困難了。在真實世界中,橫切比較工具可以有很多配 置,對某些方面的改變發出警報,而忽略與其他方面有關的改變。

III. 使用委派

方面可以並且通常用普通對象實現橫切行為。可以利用這種關注點的分離分 別 測試橫切規范及它們的行為。下面兩個模式展示如何使用委派和 mock 對象檢查 方面的這兩個部分。

模式 1. 測試委派的建議邏輯

針對 :橫切功能

概述 :如果還沒有做的話,可以將一些或者全部建議邏輯委派給其他可以直 接測試的類。(如果願意的話,還可以將行為委派給方面的公共方法。)

例子:將突出顯示邏輯轉移到其他類

要更好地在隔離狀態下測試突出顯示邏輯,可以將它轉移到一個專門的工具 類 中:

private HighlightUtil highlightUtil = new CssHighlightUtil ();

 public void setHighlightUtil(HighlightUtil highlightUtil) {
 this.highlightUtil = highlightUtil;
 }

 String around(Highlightable highlightable) :
    highlightedTextProperties() && this(highlightable)
 {
   String result = proceed(highlightable);
  return highlightUtil.highlight(result, highlightable.getHighlightedWords ());
 }

通過抽取突出顯示邏輯,可以編寫調用 HighlightUtil 類的方法的單元測試 。

優缺點

這項技術使得在域邏輯中產生邊緣用例更容易了。它還有助於隔離問題,如 果 helper 類的測試失敗,就會知道是它而不是方面有問題。最後,委派邏輯通常 會 得到更干淨的關注點分離。在這個例子中,通過將文字突出顯示邏輯抽取到其它 類,它變成系統其他部分可以獨立於這個方面使用的一項操作。從而使方面獲得 了使用不同的突出顯示策略的靈活性(HTML 的 CSS 突出顯示、純文本的全部大 寫突出顯示等等)。

不利的一面是,這種技術在邏輯難於抽取時就無能為力了。例如,最好讓簡 單 的邏輯留在原處。同時,一些方面將狀態存儲到本地或者它們建議的類的 ITD 中 。狀態存儲通常構成了方面邏輯的簽名部分,它並不總能干淨地轉移到 helper 類中。

模式 2. 使用模擬對象記錄建議觸發

針對 :橫切規范和功能

概述 :這項技術補充了前一項技術。如果將建議行為抽取到另一個類中,那 麼就可以用一個 mock 對象替代 helper 類對象,並驗證建議是否在正確的聯結 點上觸發。還可以驗證建議將正確的上下文傳遞給了 helper 類,不管是直接用 建議參數還是用之前存儲的狀態。

注: 如果需要對 mock 對象的介紹,請參閱 參考資料。

例子:用一個 mock HighlightUtil 測試 Highlighter 方面

我們已經看到了方面如何委派到另一個類中以處理實際的文字突出顯示。這 使 得在測試中向方面注入不同的 highlighter 實現成為可能。清單 3 中的代碼利 用 JMock 庫做到了這一點。(請參閱 參考資料。)

清單 3. 用 JMock 測試來自方面的調用

public class DelegatedHighlightingUnitTest extends MockObjectTestCase {
Collection<String> words;
private HighlightUtil original;
private SearchResult result;
private Mock mockUtil;
public void setUp() throws Exception {
super.setUp();
setUpMockHighlightUtil();
words = Collections.singleton("big");
result = new SearchResult();
result.setTitle("I am a big bear!");
result.setHighlightedWords(words);
}
private void setUpMockHighlightUtil() {
original = HighlightResults.aspectOf().getHighlightUtil();
mockUtil = mock(HighlightUtil.class);
HighlightResults.aspectOf().setHighlightUtil((HighlightUtil) mockUtil.proxy());
}
public void testHighlightUtilAppliedToTitleOfSearchResult() {
mockUtil.expects(once())
.method("highlight")
.with(eq("I am a big bear!"), eq(words));
result.getTitle();
}
}

setUp() 方法實例化 mock 對象並將它注入到方面中。測試方法告訴 mock 等待對名為 “highlight” 的方法的調用,這個方法有兩個參數:getTitle() 的返回值和在 SearchResult 中存儲的單詞清單。設置了期望後,測試調用 getTitle() 方法,它應當觸發方面並產生預期的對 mock 的調用。如果 mock 沒有收到調用,那麼它就會在銷毀時自動使測試失敗。

注意 setUp() 方法存儲了到原來 HighlightUtil 的引用。這是因為方面像 大多數對象一樣,是單元素(singleton)的。因此,銷毀時撤銷 mock 注入的 影響很重要,否則,mock 會持續留在方面中並影響其他測試。這個方面的正確 銷毀如下所示:

@Override
protected void tearDown() throws Exception {
try {
HighlightResults.aspectOf().setHighlightUtil(original);
} finally {
super.tearDown();
}
}

優缺點

這個模式對前一個模式做了補充,只是它測試方面的橫切規范和上下文處理 而不是橫切行為。因為不用檢查方面的輸出的間接副作用,所以可以更容易地產 生聯結點匹配和上下文傳遞行為中的臨界用例。

重要的是要認識到委派邏輯的優缺點,用 mock 進行測試對於使用對象或方 面的技術都是類似的。在這兩種情況下,都是分離關注點,然後以更隔離的方法 驗證每一個關注點。

對於注入 mock 來說,有一個特定於方面的問題。如果使用單元素方面(默 認的),那麼對於方面的字段所做的所有改變,如用 mock 替換一個字段,在測 試結束時都必須撤銷。(否則,mock 會掛起並可能影響系統的其他部分。)這 種銷毀邏輯很難實現和記憶。編寫一個測試清理方面,自動在每次測試後像在例 子中那樣重新設置方面從概念上來說是簡單的,但是其細節超出了本文的范圍。

IV. 使用 mock 目標

在最後一節中,我介紹了我自己發明的、用於描述在編寫方面測試時用到的 一種測試 helper 類類型的術語:mock 目標。在方面之前的世界中,一個 mock 對象 表示一個(手寫或者動態生成)的類,它模仿要測試的一些類的協作器。 與此類似,mock 目標 是一個模仿要測試的一些方面的合法建議目標的類。

為了創建 mock 目標,編寫一個與生產中的建議有某些相似結構或者行為的 類。例如,如果對於由 getter 返回的文字的突出顯示感興趣,可以編寫下面這 樣的一個 mock 目標:

//an inner class of the enclosing test case
public class HighlightMockTarget implements Highlightable {
public String getSomeString() {
return "I am a big bear!";
}
}

然後,編寫測試用例以驗證方面正確地與目標交互,如清單 4 所示:

清單 4. 與 mock 目標交互以測試建議

public void setUp() throws Exception {
super.setUp();
setUpMockHighlightUtil();
words = Collections.singleton("big");
mockTarget = new HighlightMockTarget();
mockTarget.setHighlightedWords(words);
}
//mock setup/tearDown omitted
public void testHighlighting() {
mockUtil.expects(once())
.method("highlight")
.with(eq("I am a big bear!"), eq(words))
.will(returnValue("highlighted text"));
String shouldBeHighlighted = mockTarget.getSomeString();
assertEquals(shouldBeHighlighted, "highlighted text");
}

注意在這個例子中,我結合了 mock 目標和 mock 對象(如在 第 III 節, 模式 2 中所描述的)。mock 目標為下面三種技術提供了基礎。

模式 1. 通過擴展一個抽象方面並提供一個切點來測試建議

針對 :橫切功能

概述 :Prework :如果有必要,重新編寫方面,將它分為一個抽象方面以及 一個擴展它並具體化一個或者多個切點的具體方面。

有了抽象方面後,在測試類中創建一個 mock 目標。創建一個擴展了抽象方 面的測試方面。讓測試方面提供明確針對 mock 目標的切點。這個測試通過查找 建議的已知副作用或者使用一個 mock 對象來驗證方面中的建議是否成功。

示例:擴展 AbstractHighlighter

假定已經編寫了 上一節中的測試代碼。為了使測試通過,必須將 Highlighter 方面分解為一個抽象方面和一個子方面,如下所示:

public abstract aspect AbstractHighlighter {
public abstract pointcut highlightedTextProperties();
//... aspect continues
}
public aspect HighlightResults extends AbstractHighlighter {
public pointcut highlightedTextProperties() :
(
//...define pointcut as before
);
}

下一步,用一個只用於測試案例的方面擴展 AbstractHighlighter 方面。下 面我將它展示為測試案例的一個靜態內部方面:

private static aspect HighlightsTestClass extends AbstractHighlighter {
public pointcut highlightedTextProperties() :
execution(public String HighlightMockTarget.*(..));
}

這個方面通過選擇 mock 目標上所有的方法執行具體化了 highlightedTextProperties 切點。

優缺點

顯然,這種測試過程是一種人造的情況。對一個假的對象測試假的方面。不 過,這只是表明測試的不是真正的切點。仍然可以驗證建議和抽象方面所指定的 ITD 代碼。在例子中,測試驗證建議正確地編組了來自 ITD 的數據以及原來聯 結點的返回值、將它傳遞給一個工具類並返回新的結果。這涉及了相當多的行為 。使用一個 mock 目標還使測試更清晰了,因為測試的讀者不必閱讀真正目標的 行為以及方面的行為。這種測試在為方面庫編寫單元測試時特別有用,因為只有 到了方面加入到具體的應用程序中以後才會有真實的目標。

如果將方面分解以利用這種模式的好處,那麼您可能使它更具可擴展性。比 如,如果系統的新部分需要參與突出顯示行為,那麼它們可以擴展抽象的方面並 定義覆蓋新情況的切點。這樣,抽象方面就與它所建議的系統解耦了。

模式 2. 測試與 mock 目標匹配的切點

針對 :橫切規范和功能

概述 :這項技術與上一技術密切相關。這次不是擴展一個抽象類,而是編寫 mock 目標,以使它匹配要測試的方面上的一個切點。可以通過檢查方面是否建 議了 mock 目標來測試切點是否正確。如果要測試的切點過度專門化,那麼可能 需要重新編寫它,使得 mock 目標可以更容易地“預定”建議。

示例:基於一個標志接口測試切點

不是使突出顯示方面成為抽象的,而是改寫切點使它匹配 Highlightable 接 口上的方法執行:

public pointcut highlightedTextProperties() :
execution(public String Highlightable+.get*());

這種寬泛的切點匹配 Highlightable 上的所有 String getter。因為切點不 枚舉特定的類,它已經匹配了 mock 目標上的 getSomeString() 方法。測試的 其余部分保持不變。

變化:使用一個注釋

還可以編寫切點以部分根據 Java 5.0 元數據進行匹配。例如,下面修改後 的切點匹配用 @Highlighted 注釋修飾的方法執行:

public pointcut HighlightedTextProperties() :
execution(@Highlighted public String Highlightable+.*());
//you can apply the annotation in the source, or using the declare- annotation form
declare @method : public String SearchResult+.getTitle(..) : @Highlighted;
declare @method : public String SearchResult+.getProduct(..) : @Highlighted;

可以通過添加注釋到其 getSomeString() 方法,使 mock 目標匹配新的切點 :

@Highlighted
public String getSomeString() {
return "I am a big bear!";
}

優缺點

這項技術還明確地分離了對方面行為與目標應用程序的行為的測試,使測試 變為更獨立。如果切點還沒有編寫為容納 mock 目標,那麼應當通過重新編寫它 們得到一個耦合更松散的方面。通過使方面足夠一般化,可以影響測試類中的 mock 目標,還會保證它可以容易地讓真實類參與方面的行為。

模式 3. 驗證更復雜的切點(一個特殊情況)

針對 :橫切規范和功能

概述 :上一個 mock 目標是簡單的,但是也可以將 mock 目標編寫為模擬復 雜的聯結點(如 cflow())或者要影響的一系列聯結點。

例子:模擬 cflow

假定希望對於下載的報告關閉突出顯示。可以加入一個 highlightExceptions切點以排除由 ReportGenerator 調用的任何 getter,如 下所示:

public pointcut highlightedTextProperties() :
execution(public String Highlightable+.get*())
&& !highlightExceptions();

public pointcut highlightExceptions() :
cflow(execution(* ReportGenerator+.*(..)));

然後可以編寫一個 mock ReportGenerator,它調用 HighlightMockTarget 以測試沒有進行突出顯示:

private class MockGenerator implements ReportGenerator {
public void write(OutputStream stream) throws IOException {
mockTarget.getSomeString();
}
}
public void testNoHighlight() throws Exception {
mockUtil.expects(never()).method("highlight");
MockGenerator accessor = new MockGenerator();
accessor.write(null);
}

不過,可以想像為更復雜的匹配情況(例如,somePointcut() && ! cflowbelow(somePointcut()))創建一個類似的 mock 目標。可視化工具不能給 出關於使用運行時檢查的切點(如 cflow())的匹配的詳細信息。用幾個代表性 的 mock 目標檢查這種切點是值得的。

結束語

當我看到未測試的代碼時,就覺得厭煩。沒有好的測試集的代碼通常有很多 問題,難於進行有信任度的改變,並且難以重構。不過,如果用方面實現橫切行 為,那麼就有了測試(並理解)應用程序的橫切關注點的新方法。

測試方面與測試對象很相似。這兩種測試都需要將行為分解為可以單獨測試 的組件。一個要掌握的關鍵概念是橫切關注點分為兩個區域。首先是橫切規范, 它要回答的是關注點影響的是程序的哪些部分。其次是功能,它回答的是這些點 上會發生什麼。如果只使用對象,那麼這兩個區域是交叉的,因為關注點在應用 程序中是糾纏在一起的。不過,使用了方面後,可以以一個領域為目標或者同時 分別以兩個領域為目標。

將方面編寫為可測試的,得到的設計好處與通過重構面向對象的代碼來實現 可測試性所得到的好處相似。例如,如果將建議的正文轉移到一個可獨立測試的 類中,那麼就可以分析其行為而不用理解它橫切應用程序的方式。如果修改切點 以使它們更能被 mock 目標訪問,也就使它們更可被系統中的非測試部分訪問。 不管是哪種情況,都提高了系統整體的靈活性和可插入性。

不久之前,我聽到了一個流傳的說法,說面向方面的程序不能測試。盡管這 個謠傳基本上已經消失,我仍然認為它是一個挑戰。我希望本文表明不僅可以對 方面進行測試,而且在測試橫切時,使用了方面後會好得多。

下載:http://www.ibm.com/developerworks/cn/java/j-aopwork11/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved