視窗驅動程式
提供一個程式化 API 來驅動和查詢 UI 視窗。
2004 年 8 月 26 日
這是 進階企業應用程式架構開發 的一部分,我在 2000 年代中期撰寫。遺憾的是,自那時起,太多其他事情吸引了我的注意力,所以我沒有時間進一步研究它們,我也看不到在可預見的未來會有太多時間。因此,這份文件仍處於草稿階段,在我找到時間再次研究它之前,我不會進行任何更正或更新。
使用者介面視窗扮演系統的重要閘道角色。儘管它在電腦上執行,但它通常並非真正對電腦友善,特別是它通常很難編寫程式來自動執行任務。這對於測試來說是一個問題,因為自動化測試在簡化整個開發過程中扮演著重要的角色。
視窗驅動程式 是 UI 視窗的程式化 API。視窗驅動程式 應該允許程式控制視窗的所有動態面向,呼叫任何動作並擷取人類使用者可用的任何資訊。
運作方式
對於 視窗驅動程式 的基本經驗法則,它應該允許軟體用戶端執行人類可以做或看到的任何事情。它還應該提供一個易於編寫程式的介面,並隱藏視窗中底層的小工具。因此,要存取文字欄位,您應該有存取方法來取得和傳回字串,核取方塊應該使用布林值,而按鈕應該由面向動作的方法名稱表示。視窗驅動程式 應該封裝實際操作 gui 控制項中資料所需的機制。一個好的經驗法則,就是想像一下改變具體控制項,在這種情況下,視窗驅動程式 介面不應該改變。
視窗驅動程式 介面中的名稱應該反映可見的 UI,因此請根據螢幕上的標籤命名元素。
豐富的用戶端視窗通常會將其控制項組織成複雜的階層結構。當您使用基於流程的配置,而不是絕對座標配置時,情況尤其如此。視窗驅動程式 應該盡可能從介面中隱藏配置設計。這樣,對內部配置的變更就不會導致用戶端變更。例外情況可能是使用標籤或側邊欄選取器等技術的多部分視窗。在這種情況下,大量的控制項使得將程式化 API 分割成獨立的 視窗驅動程式 類別變得有價值。
具備現代使用者介面的Window Driver較為棘手的其中一項面向在於處理多執行緒。通常,一旦啟動視窗,它就會在與驅動程式不同的執行緒中執行。這可能會導致難纏的執行緒錯誤,使Window Driver不可靠。有幾種方法可以處理這個問題。一種方法是使用一個會將要求放入使用者介面執行緒的函式庫。另一種方法是不要實際啟動視窗,這樣它就永遠不會實際進入使用者介面執行緒。
Window Driver的介面可以公開小工具本身,或公開在小工具上進行您有興趣進行的變更的方法。因此,如果您想要在 swing 中公開文字欄位,您可以透過單一方法JTextField getArtistField();
或透過欄位的不同面向的方法String getArtistField(), void setArtistField(String text), bool getArtistFieldEnabled()
來執行。傳回欄位本身較為簡單,但表示Window Driver的用戶端依賴於視窗系統,而使用Window Driver的程式設計人員需要熟悉視窗系統運作的方式。這也表示視窗系統必須在對小工具進行變更時觸發事件,而這些變更是透過直接呼叫小工具上的方法來完成的。總的來說,除非有充分的理由不這麼做,否則我比較喜歡傳回小工具。
何時使用
Window Driver最常見的用途是進行測試,特別是在使用Autonomous View時。Window Driver會將測試與檢視實作的詳細資料隔離,簡化撰寫測試並將它們與檢視組織的變更隔離。
Window Driver也可以用於在應用程式上方提供指令碼介面。然而,在多數情況下,最好在系統的較低層撰寫此類介面。當您有一個將大量行為嵌入檢視的應用程式,而且將此行為移至較低層太困難時,這可能會是一個困難的案例。如果可以,我仍然比較喜歡移動行為。
視窗驅動程式在您努力提供一個非常精簡的檢視時可能不需要。例如 監督控制器、被動檢視 和 簡報模型 等模式旨在使 視窗驅動程式 變得不必要。
範例:Swing 專輯範例 (Java)
以下是使用於專輯執行範例的 視窗驅動程式。我的其中一項需求是,我可以撰寫一組測試,使用不同的模式來測試此視窗的實作。這並非常見需求,但有助於說明 視窗驅動程式 如何提供一些實作獨立性。
我從定義 視窗驅動程式 的共用介面開始
public interface AlbumWindowDriver { JList getAlbumList(); JTextField getTitleField(); JPanel getMainPane(); JPanel getAlbumDataPane(); JPanel getApplyPanel(); JScrollPane getAlbumListPane(); JTextField getComposerField(); JCheckBox getClassicalCheckBox(); JSplitPane getSplitPane(); JButton getApplyButton(); JButton getCancelButton(); JTextField getArtistField(); JFrame getWindow(); }
正如您所見,這只會公開各種小工具以供操作。然後,我在擁有的各種檢視實作中直接實作此介面。
在我的案例中,我使用 Jemmy 驅動測試。測試案例類別使用 Jemmy 的運算子來包裝 視窗驅動程式 公開的控制項。
private JTextFieldOperator title; private JTextFieldOperator artist; private JListOperator list; private JFrameOperator window; private JCheckBoxOperator isClassical; private JTextFieldOperator composer; private JButtonOperator applyButton, cancelButton; private Album[] albums = (Album[]) Mother.albums().toArray(new Album[Mother.albums().size()]); protected void setUp() throws Exception { AlbumWindowDriver frame = doCreateFrame(); window = new JFrameOperator(frame.getWindow()); title = new JTextFieldOperator(frame.getTitleField()); list = new JListOperator(frame.getAlbumList()); artist = new JTextFieldOperator(frame.getArtistField()); isClassical = new JCheckBoxOperator(frame.getClassicalCheckBox()); composer = new JTextFieldOperator(frame.getComposerField()); applyButton = new JButtonOperator(frame.getApplyButton()); cancelButton = new JButtonOperator(frame.getCancelButton()); } protected abstract AlbumWindowDriver doCreateFrame();
我使用 Jemmy 的運算子來處理執行緒問題。Jemmy 也提供功能,讓您可以在表單的階層中尋找控制項,但我不需要這項功能,因為 視窗驅動程式 會直接帶我到需要的控制項。
抽象方法 doCreateFrame()
在那裡,因此我可以使用子類別來設定我使用的實際檢視實作。除非您有這個奇怪的需求,否則您只要內嵌實例化檢視即可。
在設定各種變數後,我現在可以撰寫直接操作控制項的測試。
public void testCheckClassicalBoxEnablesComposerField() { list.setSelectedIndex(4); assertEquals("Zero Hour", title.getText()); isClassical.doClick(); assertTrue(isClassical.isSelected()); assertTrue("composer field not enabled", composer.isEnabled()); applyButton.doClick(); list.setSelectedIndex(0); list.setSelectedIndex(4); assertTrue("composer field not enabled after switch", composer.isEnabled()); }