視窗驅動程式

提供一個程式化 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());
}