ウィンドウドライバ

UIウィンドウを操作および調査するためのプログラムAPIを提供します。

これは、私が2000年代半ばに行っていた「さらなるエンタープライズアプリケーションアーキテクチャの開発」に関する執筆の一部です。残念ながら、他の多くのことが私の注意を引いてしまい、それ以降、それらをさらに研究する時間がありませんでした。近い将来もそうなる見込みです。そのため、この資料は非常にドラフト版であり、再び取り組む時間ができるまで、修正や更新を行う予定はありません。

ユーザーインターフェースウィンドウは、システムへの重要なゲートウェイとして機能します。コンピュータ上で実行されるにもかかわらず、必ずしもコンピュータフレンドリーではありません。特に、タスクを自動的に実行するようにプログラムすることが難しい場合があります。これは、自動テストが開発プロセス全体を大幅に簡素化するテストにおいて特に問題となります。

ウィンドウドライバは、UIウィンドウのためのプログラムAPIです。ウィンドウドライバは、プログラムがウィンドウのすべての動的な側面を制御し、ユーザーが利用できるすべてのアクションを呼び出し、情報を取得できるようにする必要があります。

仕組み

ウィンドウドライバの基本的な経験則は、ソフトウェアクライアントが人間ができることすべてを実行し、見ることができるようにすることです。また、プログラムしやすく、ウィンドウの基盤となるウィジェットを隠すインターフェースを提供する必要があります。したがって、テキストフィールドにアクセスするには、文字列を受け渡すアクセサメソッドを使用し、チェックボックスはブール値を使用し、ボタンはアクション指向のメソッド名で表現する必要があります。ウィンドウドライバは、GUIコントロール内のデータを実際に操作するために必要なメカニズムをカプセル化する必要があります。良い経験則は、具体的なコントロールを変更する場合を想定することです。その場合、ウィンドウドライバのインターフェースは変更されるべきではありません。

ウィンドウドライバのインターフェースの名前は、画面上のラベルに基づいて要素に名前を付けるなど、可視のUIを反映する必要があります。

リッチクライアントウィンドウは通常、コントロールを複雑な階層構造に整理します。これは、絶対座標レイアウトではなく、フローベースのレイアウトを使用する場合に特に当てはまります。ウィンドウドライバは、レイアウト設計をインターフェースから可能な限り隠す必要があります。そうすれば、内部レイアウトの変更によってクライアントが変更されることはありません。タブやサイドバーセレクターなどの手法を使用したマルチパートウィンドウは、この例外になる可能性があります。この場合、コントロールの数が非常に多いため、プログラムAPIを個別のウィンドウドライバクラスに分割する価値があります。

現代のUIにおけるウィンドウドライバのより厄介な側面の1つは、マルチスレッドの処理です。通常、ウィンドウが起動すると、駆動プログラムとは異なるスレッドで実行されます。これにより、ウィンドウドライバが信頼できなくなるような厄介なスレッドバグが発生する可能性があります。これに対処する方法はいくつかあります。1つは、UIスレッドにリクエストを投入するライブラリを使用することです。もう1つは、ウィンドウを実際に起動せず、UIスレッドに決して移行させないことです。

ウィンドウドライバのインターフェースは、ウィジェット自体を公開するか、ウィジェットに対して行いたい変更を行うメソッドを公開することができます。したがって、Swingでテキストフィールドを公開する場合は、単一のメソッドJTextField getArtistField();または、フィールドのさまざまな側面に対するメソッドString getArtistField(), void setArtistField(String text), bool getArtistFieldEnabled()を使用できます。フィールド自体を返すのは簡単ですが、ウィンドウドライバのクライアントがウィンドウシステムに依存し、ウィンドウドライバを使用するプログラマがウィンドウシステムの仕組みを理解する必要があることを意味します。また、ウィンドウシステムは、ウィジェットに対してメソッドを直接呼び出して行われた変更時にイベントを発行する必要があります。全体として、そうしない正当な理由がない限り、ウィジェットを返すことを好みます。

いつ使用するか

ウィンドウドライバの最も一般的な用途は、特に自律ビューを使用する場合のテストです。ウィンドウドライバは、ビューの実装の詳細からテストを分離し、テストの記述を簡素化し、ビューの組織の変更からテストを隔離します。

ウィンドウドライバは、アプリケーションの上にスクリプトインターフェースを提供するためにも使用できます。ただし、ほとんどの場合、このようなインターフェースは、システムのより低いレイヤーに記述する方が適切です。これが難しい可能性があるケースの1つは、ビューに埋め込まれた動作が多数あり、この動作をより低いレイヤーに移動するのが難しい場合です。可能であれば、それでも動作を移動することを好みます。

ウィンドウドライバは、非常に薄いビューを提供するように努力する場合は必要ないかもしれません。監視プレゼンターパッシブビュープレゼンテーションモデルなどのパターンは、ウィンドウドライバを不要にすることを目指しています。

例:Swingアルバムの例(Java)

これが、アルバムの実行例で使用したウィンドウドライバです。私の要件の1つは、さまざまなパターンを使用してこのウィンドウの複数の実装をテストする単一のテストセットを作成できることでした。これは一般的な要件ではありませんが、ウィンドウドライバが実装の独立性を提供できることを示すのに役立ちます。

まず、ウィンドウドライバの共通インターフェースを定義します。

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());
}