概要‎ > ‎

受け入れテスト設計考察

受け入れテスト自動化時に気を付けること

実はテスト自動化は、それなりのコストが必要となります。
コストが必要となる点は実は受け入れテストだけでなく、単体テストも同様です。

①テスト作成時のコスト。
②テスト保守のコスト。

そのため、それらのコストを低減させ、かつ効果を高くするためには、
テスト自動化時には手動でやる場合以上に綿密なテスト設計が必要となります。
さらに、テストコードのプログラムとしての設計も必要になります。
自動化するということは、人間ではなくコンピュータに作業をさせるので、当然そこにはプログラムが作成されます。
そのプログラムも、当然可読性、メンテナンス性に優れたものにする必要があります。

よくやってしまいがちなのは、①のコストを軽減させるためにキャプチャリプレイツールを使ってブラックボックスのコードを作ってしまうことです。
キャプチャリプレイツール自体は様々なベンダーから発売されており、便利で優れたものです。
しかし、出力されるコードを理解せず、そのまま使ったのでは、少し仕様が変わっただけで、テストが使い物にならなくなってしまうこともあります。


アプリケーションドライバ

現在、受け入れテスト自動化でお勧めできる方法は、アプリケーションドライバというレイヤを設ける方法です。
これは、継続的デリバリーでも紹介されています。

弊社でも、@IT様で記事を書かせていただきました。
(この記事を書いているときは、「継続的デリバー」を読んでいなかったので、アプリケーションドライバではくAPIという表現になっています。)

この方法では、②のコストを抑えることができます。しかし、①の作成時にはそれなりの設計スキルが必要となります。
受け入れテスト自動化で効果を出すためには、できるだけ数多くテストを実行することです。つまり保守期間は長くなります。
①のコストは固定ですが、②のコストはプロジェクトの期間が長くなればなるほど増していくと言えます。
そのため、②のコストを抑える方が全体的に安くなります。

詳細は書籍や弊社の記事を参考にしていただきたいのですが、簡単に説明すると、テストシナリオからGUI操作のコードを排除するということです。
アプリケーションドライバ層が外部に公開するインターフェイスはアプリケーション操作仕様を蒸留した本質的なものにするのです。
下記の例では、テストシーケンスをGUI操作で作っています。
この例ではFriendlyを使っていますが、UIオートメーションを使っても同様です。

テストシナリオをGUI操作で記述した例

[TestMethod]
public void Test()
{
    string path = Path.GetFullPath("../../../EmployeeManagement.exe");
    Process process = Process.Start(path);
    using (WindowsAppFriend app = new WindowsAppFriend(process, "4.0"))
    {
        WindowControl mainForm = WindowControl.FromZTop(app);
        AppVar textBoxName = mainForm["textBoxName"]();
        AppVar textBoxAge = mainForm["textBoxAge"]();
        AppVar textBoxPosition = mainForm["textBoxPosition"]();
        AppVar buttonAdd = mainForm["buttonAdd"]();
        AppVar buttonFind = mainForm["buttonFind"]();

        //
登録
        textBoxName["Text"]("
山田太郎");
        textBoxAge["Text"]("28");
        textBoxPosition["Text"]("
係長");
        buttonAdd["PerformClick"]();
        Assert.IsTrue(string.IsNullOrEmpty((string)textBoxName["Text"]().Core));

        //
年齢は数値のみ入力可能。
        textBoxName["Text"]("
山田太郎");
        textBoxAge["Text"]("28");
        textBoxPosition["Text"]("
係長");
        buttonAdd["PerformClick"]();
        Assert.IsFalse(string.IsNullOrEmpty((string)textBoxName["Text"]().Core));

        //
検索
        textBoxName["Text"]("
山田太郎");
        buttonFind["PerformClick"]();
        Assert.AreEqual("
山田太郎", (string)textBoxName["Text"]().Core);
        Assert.AreEqual("28", (string)textBoxAge["Text"]().Core);
        Assert.AreEqual("
係長", (string)textBoxPosition["Text"]().Core);

        //
閉じる
        Process.GetProcessById(app.ProcessId).CloseMainWindow();
    }
}


これはGUI操作のコードの分量が多すぎて何を検証しているのか読み取るのに骨が折れます。
また、繰り返しコードもあります。

次の例では、GUI操作を別クラスに抜出し、意味のあるインターフェイスを定義して、それを使ってテストシナリオを記述します。

アプリケーションドライバを使った例

[TestMethod]
public void Test()
{
    //アプリの起動。スコープを抜けるときアプリ終了。
    using (EmployeeManagementApp app = EmployeeManagementApp.Start())
    {
        //登録。
        Assert.IsTrue(app.Add("山田太郎", "28", "係長"));
 
        //年齢は数値のみ入力可能。
        Assert.IsFalse(app.Add("鈴木次郎", "32", "部長"));

        //検索 ヒット
        EmployeeData findYamada = app.Find("山田太郎");
        Assert.AreEqual("山田太郎", findYamada.Name);
        Assert.AreEqual(28, findYamada.Age);
        Assert.AreEqual("係長", findYamada.Post);
    }
}


このようなインターフェイスであれば、もっとアプリケーションの操作の本質を表しています。
こちらの方が、仕様変更に強いのはもちろん、テストが何を検証したいのか簡単に読み取れます。


アプリケーションドライバの実装

アプリケーションドライバはGUI操作で実装してもよいのですが、無理にGUI操作で実装する必要もありません。
場合によればビジネスロジック層のみ操作することで実現しても良いのです。
Friendlyであれば、どちらも可能です。

どちらを選択するかは、対象とするプログラムによります。

GUIとビジネスロジックを上手く分離できているなら、ビジネスロジック層を操作する方が良いでしょう。
結果的には、コストが安く抑えられ、効果的なテストになります。またテスト実行時間もGUI操作に比べ短くなります。

GUI操作はトラブルが比較的多く発生します。
というのは、ブラックボックス要素が大きいのです。
多くの場合、マイクロソフトやサードパーティーのGUIライブラリを使用して実装すると思います。
そのため、その層でトラブルが発生した場合、その原因解明が困難になることがあります。
また、タイミング依存を排除しきるのは高度なテスト実装スキルが必要になってきます。

とは言え、全てそのような理想的な設計になっていることは稀なので、GUI操作をする場合も多いと思われます。
また、GUI層を操作すると、実際のユーザ操作に近い状態のテストができるというメリットがあります。
「継続的デリバリー」ではGUI操作でアプリケーションドライバを実装することはウィンドウドライバと呼ばれています。

つまり、テスト内容、アプリケーションの実装によって使い分ければよいと思います。


GUIマップ

GUI操作でアプリケーションドライバを実装する場合、もう一つレイヤを作ることをお勧めします。
GUIオブジェクト特定方法を抜き出しておくのです。
GUIオブジェクトの特定方法は変わりやすく、それがコード中に散らばるとメンテ効率が落ちます。
そのため、GUIオブジェクトの特定のみの責務を負ったクラスを作ります。
ソフトウェアテスト293の鉄則でも紹介されています。