領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多在過去的幾個月裡,我一直在與不同的客戶合作使用Google Web Toolkit [GWT] 的專案。我喜歡GWT主要是因為它的Java到javascript編譯器。這是讓普通Java開發人員無需學習新語言即可建立RIA的關鍵。
我一直都是測試驅動開發的粉絲,令我失望的是,乍一看,TDD和GWT似乎無法協同工作。
測試GWT程式碼有點問題。問題的核心是GWT程式碼在執行前被編譯成javascript。在許多情況下,GWT.create()語句用於掛接到動態繫結機制。當在正常的Java環境中執行時,該語句會導致異常。
即使你使用像EasyMock這樣的mocking庫來模擬罪魁禍首,如果你從建構函式中觸發它,例如,你仍然很可能會遇到問題。為所有小部件建立介面開銷太大,即使你這樣做了,你也無法測試包含GWT.create()的特定類。
GWT在GWTTestCase中為此問題提供了一個解決方案,但這個類本身也存在不少問題。僅舉幾個例子
當你進行測試驅動開發時,你需要能夠編寫至少具有以下屬性的測試
那麼我們如何做才能防止程式碼因缺乏測試而變得一團糟,同時將那些緩慢的GWTTestCase保持在最低限度呢?MVC來拯救
MVC是一個老牌模式。它有許多應用,其中一些除了名稱外與原始MVC模式幾乎沒有共同之處,但我想要指出的總體方向最好由Martin Fowler以Humble View的名義概括。
我更簡短的總結是:檢視通常很難測試,因此它們應該知道和做盡可能少的事情。在GWT中,檢視是Widget的同義詞。現在這裡有一個小問題:任何在客戶端上執行的東西都歸Widget所有。大多數GWT程式碼的邏輯幾乎都在小部件中,所以大多數使用GWT的開發人員會擴充套件一個小部件並新增更多邏輯。
我的建議很簡單:不要這樣做。型別安全對於獲得IDE支援很好,但它並不能阻止你編寫糟糕的程式碼。所以如果你不進行單元測試,你的程式碼最終會一團糟。
如果你不將邏輯放入檢視(Widget子類)中,你需要另一個地方來放置它。這就是控制器發揮作用的地方。回到我之前提到的問題,我們需要從Widget或EntryPoint引導控制器。真的沒有辦法繞過這一點,所以我們只管去做,看看它看起來有多糟糕
public class NoteEditor extends Composite {
public NoteEditor() {
//do the dependency injection stuff
NoteModel noteModel = new NoteModel();
NoteEditorController controller =
new NoteEditorController(noteModel, NoteService.App.getInstance(), new AlertCallback());
//...
}
}
正如你所看到的,我在這裡做了一些程式碼上的依賴注入,因為我們還沒有在客戶端使用Spring。我說“還沒有”,是因為我們以後可以使用GWToolbox或Rocket來實現。該服務透過GWT.create()進行引導,並且AlertCallback用於將控制器與Window解耦,以應對偶爾的警報。我想這還不算太糟,我可以輕鬆入睡而無需為此程式碼編寫測試。麻煩並未完全結束,因為我們想要在檢視中使用的任何元素(按鈕、標籤等)都需要在檢視中例項化,然後向控制器註冊
controller.registerDetailViewSelector(new DeckPanelSelector(detailView));
detailView.addStyleName("detailPanel");
main.add(detailView, DockPanel.CENTER);
//some buttons at the bottom of the screen
buttonPanel.add((Widget)controller.registerClearButton(new Button("Clear")));
buttonPanel.add((Widget)controller.registerLoadButton(new Button("Load existing Note")));
控制器透過它們的介面(SourcesClickEvents)接受按鈕,這額外的好處是允許我們用外觀不同的Widget替換按鈕而無需更改我們的控制器。這裡沒有什麼新東西,這正是MVC所關心的關注點分離。老實說,我通常更喜歡編寫一個測試來檢查註冊是否正確發生,但這在沒有GWTTesCase的情況下是無法完成的。現在是時候讓我們的IDE為我們建立控制器+方法並編寫測試,以便我們可以實現邏輯了。例如,載入按鈕的測試會是這樣的
public void testLoadButtonPressed_success() throws Exception {
final Foo expectedFoo = new Foo("expected");
fooServiceMock.loadFoo(isA(String.class), isA(AsyncCallback.class));
expectLastCall().andAnswer(new IAnswer() {
public Object answer() throws Throwable {
((AsyncCallback) getCurrentArguments()[1]).onSuccess(expectedFoo);
return null;
}
});
fooModelMock.setFoo(expectedFoo);
replay(allMocks);
loadButton.fireClick();
verify(allMocks);
}
我們開始吧!我附上了一個快速示例來展示這個想法。你可以將其用作實驗的起點。
更新2008-10:示例的來源已過時。一般建議仍然適用。
總結一下GWT風格MVC模式的簡要草圖。當然,你可以選擇自己的風格,只要你將邏輯保留在一個可測試的類中。
