ささいなことですが。

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

Friendlyハンズオン 4.操作対象プロセスに内部APIを実行させる - 続き

これは「Friendly Advent Calendar 2014 - Qiita」の記事です。

引き続き、詳細なドキュメントは公式ページも参照しながらでお願いします。
テスト自動化 Friendly - 株式会社Codeer (コーディア)
それから、以前に書いたこの記事も参考になります。
Friendlyの処理実行~プロセス、スレッド、同期、非同期~ その2 - ささいなことですが。

昨日までで、内部API呼び出しに関しては、かなり理解できたと思います。
でも、少し残りがあるので今日はそれをやります。

今日やること

1.AppFriendExtensions
2.out ref
3.Async

AppFriendExtensions

話すと長いのですが・・・
なので、詳細はこっち
Friendlyの基本(.NetFramework4.0が使えない場合) - 株式会社Codeer (コーディア)
Codeer.Friendly.Dynamic - 株式会社Codeer (コーディア)

実は _app.Type っていうのは、AppFriendの拡張メソッドなのです。
AppFriendExtensionsに定義されています。
これを有効にするのに

using Codeer.Friendly.Dynamic

が必要なのですね。

で、_app.Type の使い方は説明したのですが、あと二つ拡張メソッドは定義されています。

public static class AppFriendExtensions
{
    public static dynamic Copy(this AppFriend app, object obj);
    public static dynamic Null(this AppFriend app);
}
Copy

これは、文字通りテストプロセスのオブジェクトをコピーして、相手プロセスに送り込むのですね。
使ってみましょう。
TargetFormTestに書き足してください。
あ、

using System.Collections.Generic;

も追加お願いします。

[TestMethod]
public void リストのコピーを転送()
{
    var list = new List<int>();
    list.Add(0);
    list.Add(1);
    list.Add(2);
    dynamic listInTarget = _app.Copy(list);
    Assert.AreEqual(3, (int)listInTarget.Count);
}

Copyの戻り値で、相手プロセス内の参照が返ってきます。
似たようなことは、以下のコードでもできます。
これも書いて実行してみましょう。

[TestMethod]
public void 相手プロセス内にリストを生成してアイテムを追加()
{
    dynamic listInTarge = _app.Type<List<int>>()();
    listInTarge.Add(0);
    listInTarge.Add(1);
    listInTarge.Add(2);
    Assert.AreEqual(3, (int)listInTarge.Count);
}

でも、コピーの方が圧倒的に速いです。
Friendlyの呼び出しは、一つ一つプロセス間通信が走ります。
これは凄く重いのです。
Copyの場合は転送時の一回で済みますが、相手プロセス内に生成したリストにアイテムを追加していくときは、追加ごとにプロセス間通信が実行されます。
まあ、実際これくらいでは体感できませんが、ループを回すような処理だと効いてきます。
場合によって使い分けてください。

Null

相手プロセスにNullで初期化された変数を作成します。
何に使うかというと、outの引数とかですね。
実はこれは _app.Copy(null) でも代用できます。
(入れるかどうかは迷いました)
使用例は次の ref out の項で書きます。

ref out

Friendly越しにメソッドを呼ぶ場合、その引数にoutとかrefがあったら、どうでしょうか?
実はそれも呼び出せます。

[TestMethod]
public void out引数のメソッド呼び出し()
{
    var dic = new Dictionary<int, string>();
    dic.Add(0, "0");
    dic.Add(1, "1");
    dic.Add(2, "2");
    dynamic dicInTarget = _app.Copy(dic);

    dynamic val = _app.Null();
    bool ret = dicInTarget.TryGetValue(1, val);
    Assert.IsTrue(ret);
    Assert.AreEqual("1", (string)val);
}

out, ref は書かなくてOKです。
値を格納するバッファは、当然相手プロセスの中にある必要があるのですね。
outの場合はnullのバッファでOKです。

Async

Friendlyの呼び出しは基本は同期です。
でも、実行自体は相手プロセス内のスレッドでおこなわれます。
頑張って同期させているのですね。
でも、時には非同期にしたい場合もあります。
その時は、Asyncクラスを渡してください。
実行を待ちわせないようになります。
戻り値やout引数はどうなるのか?
もちろん、相手プロセスでの実行が終わると格納されています。
それからAsyncクラスには、非同期で実行させた処理の進捗を管理する機能もあります。
今回のハンズオンでは、基本の練習だけにしますが、そのうち実際のテストで使うユースケースに合わせたハンズオンも開催します。

まず、usingを足してください。

using Codeer.Friendly;

テストメソッドです。

[TestMethod]
public void 非同期で実行()
{
    var dic = new Dictionary<int, string>();
    dic.Add(0, "0");
    dic.Add(1, "1");
    dic.Add(2, "2");
    dynamic dicInTarget = _app.Copy(dic);

    //非同期で実行
    //Asyncは引数のどこに入れてもよい
    var async = new Async();
    dynamic val = _app.Null();
    bool ret = dicInTarget.TryGetValue(1, val, async);

    //処理が完了しているか確認
    bool isCompleted = async.IsCompleted;

    //完了待ち
    async.WaitForCompletion();

    //処理が完了したら値が格納される
    Assert.IsTrue(ret);
    Assert.AreEqual("1", (string)val);
}

どうだったでしょうか?
これで、内部API呼び出しのハンズオンは全部終わりです。
あと一つだけ、Friendlyの基本機能があります。
それはDLLインジェクションです。

明日に続く・・・