ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

Friendly ハンズオン 13 アプリケーションドライバ -その5-

前回の続きです。
あと、ひと手間かけてやりましょう。それは対象プロセスのデバッグです。

デグレ検出→調査したい

自動検査でデグレを検出しました!作った甲斐がありましたね!
では調査しましょう。もちろん対象プロセスはデバッガから起動したもので調査したいですね。

あれ?EXE起動から始まってる・・・

あー、今の方式だと、EXEを起動させてテスト実行開始してますね。まあ、アタッチすればよいのですが面倒です。それに、ゆっくりデバッグしてたらタイムアップで終了させられてしまいますよねw。
こんな仕様でどうでしょうか?

  • デバッグ起動したプロセスがあればそれにアタッチ
  • テスト実行後もプロセスを終了させない
  • タイムアウト終了もしない

調査中は勝手にプロセス終了されたくないですよね。情報が減るので。ではこれで実装してみましょう。AppDriverのプロセス管理の部分を通常用とデバッグ用に分けたらいいですね。こんな時はストラテジーパターン的なアプローチがいいですね。

通常用とデバッグ用で異なる部分を抽出し、インターフェイス化して切り分けるようにします。差分が出るのは、プロセスのライフサイクル管理の部分ですね。

using Codeer.Friendly;
using Codeer.Friendly.Dynamic;
using Codeer.Friendly.Windows;
using Codeer.Friendly.Windows.Grasp;

namespace EmployeeManagementDriver
{
    public class AppDriver
    {
        IAppDriverCore _core;
        WindowsAppFriend _app;

        public MainFormDriver MainForm { get; private set; }
        public bool IsDebug { get { return _core is AppDriverDebug; } }

        public AppDriver()
        {
            _core = AppDriverDebug.Exists ? (IAppDriverCore)new AppDriverDebug() : new AppDriverNormal();
        }

        public void Attach()
        {
            _core.Attach();
            _app = new WindowsAppFriend(_core.Process);
            MainForm = new MainFormDriver(new WindowControl(_app, _core.Process.MainWindowHandle));
            InitApp();
        }

        public void SetTimeup(int time)
        {
            _core.SetTimeup(time);
        }

        public void Release(bool isContinue)
        {
            _app.Dispose();
            _core.Release(isContinue);
        }

        private void InitApp()
        {
            MainForm.ListBoxEmployee.Dynamic().Items.Clear();
        }

        public void EndProcess()
        {
            _core.EndProcess();
        }
    }
}

差分はこんな感じでインターフェイス化されます。

using System.Diagnostics;

namespace EmployeeManagementDriver
{
    interface IAppDriverCore
    {
        Process Process { get; }
        void SetTimeup(int time);
        void Attach();
        void Release(bool isSuccess);
        void EndProcess();
    }
}

通常時用ですね。
前回のコードから抽出しただけです。

using System.Diagnostics;

namespace EmployeeManagementDriver
{
    class AppDriverNormal : IAppDriverCore
    {
        public Process Process { get; private set; }
        Killer _killer;

        public void Attach()
        {
            if (Process == null)
            {
                Process = Process.Start("EmployeeManagement.exe");
            }
            _killer = new Killer(1000 * 60 * 5, Process.Id);
        }

        public void Release(bool isContinue)
        {
            if (isContinue)
            {
                _killer.Finish();
            }
            else
            {
                EndProcess();
            }
        }

        public void SetTimeup(int time)
        {
            _killer.Timeup = time;
        }

        public void EndProcess()
        {
            try
            {
                _killer.Finish();
            }
            catch { }
            try
            {
                Process.Kill();
            }
            catch { }
            Process = null;
        }
    }
}

デバッグ用です。.Netアプリの場合は[vshost]ってのが名前につくのでそれで区別しましょう。ネイティブアプリの場合はまた別の方法でやってみてください。まあこれはプロジェクトに応じた方法で。

これは終了処理系は何にもやらないですね。
デバッガで起動しているものを勝手に終了させる必要はないのです。

using System;
using System.Diagnostics;
using System.Linq;

namespace EmployeeManagementDriver
{
    class AppDriverDebug : IAppDriverCore
    {
        public Process Process { get; private set; }

        public static bool Exists { get { return GetDebugProcess() != null; } }

        public void Attach()
        {
            Process = GetDebugProcess();
        }
        public void Release(bool isContinue) { }
        public void SetTimeup(int time) { }
        public void EndProcess() { }

        static Process GetDebugProcess()
        {
            return Process.GetProcessesByName("EmployeeManagement.vshost").Where(e => e.MainWindowHandle != IntPtr.Zero).FirstOrDefault();
        }
    }
}

EmployeeManagementのslnファイルを作る

面倒だったんで一つのソリューションでやりましたが、デバッグを考えるとプロダクトだけのソリューションファイルがあった方がいいですね。ソリューションファイルを二つメンテするのはちょっと手間ですが、プロジェクトファイルを追加するときに両方に追加するだけなので、まあアリかなと。
嫌な場合は、テストコードと共有する型を含んだdllだけ参照するとかもアリです。実際そうしているお客さんもいます。

まあ、今回はslnファイルをもう一つ作りましょう。次の手順で作れます。
①一旦VisualStudioを閉じる
②HandsOn13.slnの拡張子を一回消す
③EmployeeManagement.csprojをダブルクリック
④全保存ボタンを押す→slnファイルを任意の名前で保存する
⑤HandsOn13の拡張子を戻す

②の手順がいるのは、このファイルあったらEmployeeManagement.csprojから起動しても、そのslnファイルを使っちゃうんですよねー。

では、EmployeeManagement.exeをデバッグ実行してテストを実行してみてください。
ちゃんとそのEXEにアッタッチされましたよね?

備考

それから、もう一つ、このサンプルでの手抜きを言っておくと、プロセス起動でEXEを直接参照しているので、同一フォルダにEXEがあって、それを起動していますが、実際はこんなことしないですよ。どこか別のフォルダ置かれたファイルを起動させます。パスは相対パスにして、そのルールはプロジェクトごとに決めてください。

これで、一旦アプリケーションドライバの話は終わりです。

どうだったでしょうか?
気が付いたかもしれませんが、Friendly関係ない部分も多かったですね。
アプリケーションドライバ作成時はこの辺の.Netの知識も必要になってきます。
でも、それは開発者ならおそらくは知っているはずのことです。
(だって、対象アプリ作りましたよね?)
だから、その人たちに作ってもらいましょう。

長く説明しましたが、ボリューム自体はそんなに大きくないですね。
最終形はこちらからダウンロードできます。

次回からはテストシナリオを書いてみます。

はい。ここまでで作ったアプリケーションドライバを使ってテストシナリオを書いてみます。
アプリケーションドライバの実装を頑張ったので、そちらは簡単に作れますよー。