ささいなことですが。

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

Friendly.Windows.2.14.0(.NetCore対応)をリリースしました。

.NetCore対応しました!プレビューくらいの位置づけです。
github.com
コードはこんな感じ。今までと違うのはWindowsAppFriendのコンストラクタの引数にcoreclr.dllのパスを渡しているとこだけです。今はプレビューなのでパスを渡してますけど、もう少ししたらこれは別の方法も提供するかも。(まだ最適な方法がわかってないんですよね)

[TestCase]
public void TestCore()
{
    //普通にプロセス取得
    var targetApp = Process.GetProcessesByName("TestTargetCore")[0];

    //※ここだけ違う
    //WindowsAppFriendのコンストラクタにcoreclr.dllのパスを渡す
    var dllPath = @"C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.0.0-preview7-27912-14\coreclr.dll";
    var app = new WindowsAppFriend(targetApp, dllPath);

    //後は今まで通り操作可能
    var w = app.Type<Application>().Current.MainWindow;
    w.Title = "タイトル変更";

    //RM.Friendly.WPFStandardControlsとかも利用可能
    var textBox = new WPFTextBox(w._textBox);
    textBox.EmulateChangeText("abc");
}

以前にプロトでFriendly.DotNetCoreを作りましたが、最終的には全然違うアプローチにしました。Friendly.Windowsをちょっとだけ修正して対応するというものです。

.NetFrameworkで使ってたコードがそのまま使えます。

正確にはWindowsAppFriendのコンストラクタは変更しないとダメですけど、ほぼそのまま使えます。つまり.NetFrameworkのアプリがあって、それに対応するテストを作っておけば、そのアプリを.NetCoreに変更したら、その後の回帰検査にそのテストを使うことができます。

.NetCore対応は何をしたか(何をしなければならないと思っていたか)

Friendlyの初期化処理です。こんな感じになってます。
f:id:ishikawa-tatsuya:20190815133115p:plain
以下二つの理由でFriendly.Windowsでは動かないと思っていました。

  1. ホストAPIでは.NetCoreのランタイムにはアクセスできない
  2. .NetCoreから.NetFrameworkのdllは参照できない

なんで、.NetCore対応は超面倒って思ってました。でもよく調べてると、両方とも誤解だということがわかりました。

ホストAPI

結論から言うと.NetCoreのランタイムを操作するホストAPIがありました。
docs.microsoft.com
ICLRRuntimeHostってのを使ってたんですけど、.NetCoreに対応したICLRRuntimeHost4ができてたようです。

.NetCoreから.NetFrameworkのdllは参照できる

これも結論から言うとできるんですよ。びっくりしてちょっと前にもブログ書きました。
ishikawa-tatsuya.hatenablog.com
.NetStandardか.NetCoreじゃないと参照できないって思ってましたけど。じゃあ別にexeだけ.NetCore対応したらそれでよくね?
一応ポータビリティをチェックしましたが、Friendly系のライブラリは.NetCoreへのポータビリティは100%でした。
marketplace.visualstudio.com

最終的にやったこと

.NetCore用のホストAPIを使うネイティブのdllを新しく作って、.NetCoreの場合はそれを呼びだすようにしました。操作方法は数種類あって、調査時間かかりそうだなーって思ってましたが、@tmyt@nak763が手伝ってくれたんで、サクッと終わりました。ありがとうございました!
https://github.com/Codeer-Software/Friendly.Windows/blob/master/Project/CodeerFriendlyWindowsCore/dllmain.cpp
でもそれだけで.NetCore対応できたってすごいなー。現行のWinFormsとかWPFも結構さらっと移行できるんじゃないかなー。(こなみかん

不具合対応

実は前回の2.13.1に不具合がありました。操作プロセスが管理者権限で対処プロセスが普通権限の場合に操作対象が不正終了してしまうようでした。修正しておきました。不具合報告をくれた方、ありがとうございました!

LambdicSqlサンプル

LambdicSqlの書き方の質問来たんで、久しぶりに書きます。

あえとす 8/18 ボドゲ会 (@aetos382) | Twitterさん、ありがとうございます!

お題のクエリです。

select
  foo,
  bar,
from
  table
where
  foo = 1
  and bar = 2
union all
select
  foo,
  bar,
from
  table
where
  foo = 3
  and bar = 4

LambdicSqlで書いたらこんな感じ

var sql = Db<DB>.Sql(db =>
    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(db.table.foo == 1 && db.table.bar == 2).

    Union(All()).

    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(db.table.foo == 3 && db.table.bar == 4).
);

LambdicSqlはクエリを分けて書くことができます。

var select1= Db<DB>.Sql(db =>
    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(db.table.foo == 1 && db.table.bar == 2)
);
var select2 = Db<DB>.Sql(db =>
  Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(db.table.foo == 3 && db.table.bar == 4).
);
var union = Db<DB>.Sql(db => Union(All()));

//+で演算
var sql 1=  select1+ union + select2;

//これもOK
var sql 2= Db<DB>.Sql(db =>select1 + Union(All()) + select2);

whereとかで便利です。

var select = Db<DB>.Sql(db =>
    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table)
);

var where1 = Db<DB>.Sql(db =>
    Where(db.table.foo == 1 && db.table.bar == 2).
);
var where2 = Db<DB>.Sql(db =>
    Where(db.table.foo == 3 && db.table.bar == 4).
);

//状況に応じて使い分け
bool is1 = false;
var sql = selectFrom + (is1 ? where1 : where2);

whereはさらに便利なのがあります。条件が有効なものだけ使われます。使われる条件がなくなるとwhereは消えます。

bool is1 = true;
bool is2 = false;
var sql = Db<DB>.Sql(db =>
    Select(new
    {
        db.table.foo,
        db.table.bar
    }).
    From(db.table).
    Where(new Condition(is1, db.table.foo == 1) &&
               new Condition(is2, db.table.bar == 2))
);

慣れると便利なので、是非使ってみてください!
github.com

Friendlyでx64のプロセスからx86のプロセスを操作する

Friendlyでは操作対象のプロセスと操作側のプロセスのCPUモードを合わせる必要がありました。そのため一つの操作プロセスからx86とx64のプロセスを操作することができませんでした。Friendly.Windows/2.13.1からできるようになりました。
www.nuget.org

IntPtrのサイズに注意してください。

基本はx64のプロセスからx86のプロセスを操作してもらうのが良いと思います。理由は後述します。

操作件をバイナリ化して引き継ぎます

何を言ってるんだ・・・。ってなると思います。残念なことにAttachは引き続き同一プロセスからしかできないのです。そのためアタッチしてその権限を委譲するという処理になります。

操作するためのx64のプロセスです

public void Test()
{
    //対象プロセス(x86)
     var targetApp = Process.Start(targetExePath);

    //x86のプロセスでアタッチしてその通信情報を引き継ぐためのバイナリを生成
    var myProcess = Process.GetCurrentProcess();
    var binPath = Path.GetTempFileName();
    Process.Start(attachExePath, $"{targetApp.Id} {myProcess.Id} {binPath}").WaitForExit();

    //バイナリを元にWindowsAppFriend生成
    var bin = File.ReadAllBytes(binPath);
    File.Delete(binPath);

    //以降はx64のプロセスからx86のプロセスが操作できる
    var app = new WindowsAppFriend(targetApp.MainWindowHandle, bin);
}

アタッチに使うx86のプロセスです。

namespace Attachx86
{
    class Program
    {
        static void Main(string[] args)
        {
            var app = new WindowsAppFriend(Process.GetProcessById(int.Parse(args[0])));
            //※今回作ったAPI
            //操作権の移譲 byte[]になる
            var bin = app.HandOverResources(int.Parse(args[1]));
            //ファイルに保存
            File.WriteAllBytes(args[2], bin);
        }
    }
}

セキュリティソフトに注意

Attachx86はそれぞれで作ってもらうのですが(基本は↑のコードでよいと思います)、セキュリティソフトに怒られないように注意してください。もしされたらホワイトリストに設定してください。Friendlyは強力な操作能力があるので野良exeから使うと怒られる場合があります。通常はNUnitやVSTestといった信頼のおけるexeから利用するので怒られません。でもFriendlyを使った普通のアプリを作ると怒ってくるセキュリティソフトもあります。

そもそもなんで同一CPUモードからじゃないと操作できなかったか?

理由は二つありました。

CreateRemoteThread利用時の関数のアドレスの計算が同一CPUでなければできない

何か対策があるかもしれませんが現在はわかっていないので対応できていません。そのためアタッチは引き続き同一CPUモードで行ってもらいます。ただ、いったんアタッチするとこれは使わないので、その処理を行って操作件を得た後にその情報をバイナリ化して引き継ぐという対応を今回入れました。

IntPtrのサイズが異なる

Friendlyは同一プロセスの中にあるように対象を操作できます。好きなようにメソッドを呼び出せます。ということはIntPtrを受け渡しするときに処理できないサイズのものが来る可能性があります。この問題も引き続き残ります。なので気を付けて使てくださいw。基本的には操作側をx64にしてもらってデータを送るときにはサイズを超えるものを送らないというアプローチが良いと思います。

x64とx86のアプリを使ったテストもかける

複数のアプリの絡みのテストも書きやすくなったと思います。最近結構この要望言われてたんですよねー。また感想お待ちしております。

Friendlyを更新しました。

FriendlyとFriendly.Windowsを更新しました。
www.nuget.org
www.nuget.org

DynamicAppVarからの変換

コンストラクタにAppVarをとる型に変換できるようにした。こんな感じに書けるようにしました。人によっては気持ち悪いと思うかもしれませんが便利さ優先。

WPFTextBox textBox = app2.Type<Application>().Current.MainWindow._textBox;

WindowDriver作るときにちょっと楽になります。

public class EntryControl_Driver
{
    public WPFUserControl Core { get; }

    // new書かなくてよい
    public WPFTextBox Name => Core.Dynamic()._textBoxName;
    public WPFTextBox email => Core.LogicalTree().ByBinding("Mail.Value").Single().Dynamic();
    public WPFButtonBase Entry => Core.LogicalTree().ByType("System.Windows.Controls.Button").ByType<ContentControl>().ByContentText("Entry").Single().Dynamic();

    public EntryControl_Driver(AppVar core)
    {
        Core = new WPFUserControl(core);
    }
}

環境によって対象アプリのCPU使用率が高くなる不具合の修正

プルリク来ました。
けーすけ (@_ksuke) | Twitterさんありがとうございました!
github.com

WindowsAppFriendで接続状態の移譲

こんな感じのコードが書けます。

//アプリにアタッチ
var app = new WindowsAppFriend(targetApp);
var targetWindowHandle = targetApp.MainWindowHandle;

//接続状態を移譲
//これをするとappは所有権を失います。
var bin = app.HandOverResources(newOwnerProcessId);

//接続権限を引き継ぎ
var app2 = new WindowsAppFriend(targetWindowHandle, bin);

???
なんの役にやつの?
次のブログで解説します。

Facebookに宣伝出してみた

Codeerも一応Facebookページ作ってます。たまに写真とかアップしてます。「いいね」押していただけるとありがたいっす!
https://www.facebook.com/codeer.soft

Facebookからちょこちょこ「宣伝だしませんか?」的な通知が来てて気になってたんですよね。どれくらい効果あるのかなーって。ちょうど勉強会開催するのでやってみました。

効果あった?

効果あったのかどうかはわからんw。勉強会でアンケートとればよかった。クリックしてくれた人が来てくれたかどうかわかんないんですよね。(でも多分来てくれた人はそれ以外のソースな気がする)

金額

1000円かららしい。それで日数を減らすか、金額を増やすとリーチ数が増えるらしい。あと次の対象も絞ると恐らく表示される回数とか増えるんかなーと。

今回実験した対象

  1. 大阪在住の18-65才
  2. 大阪在住の18-65才でページにいいねした人
  3. 東京在住の18-65才でページにいいねした人とその友達

大阪の方は①やってて、途中で「いや、それは広すぎてイマイチやろ」って思って②もやりました。つまり2000円払ってます。東京の方は③だけで1400円分やりました。なんだけど、実は東京のは設定間違えてたんですよね・・・。

結果

いやいや、30クリックに3000円とか高すぎやしw

若干掲載期間に差はあるものの「ページにいいねした人とその友達」がやっぱり一番効果高いかなーって感じですね。
f:id:ishikawa-tatsuya:20190319230322p:plain
f:id:ishikawa-tatsuya:20190319230505p:plain
しかも東京にしたと思ったけど、間違えて大阪在住の人にしてた・・・。
f:id:ishikawa-tatsuya:20190319230652p:plain

大阪

15人枠に対して13人申し込みキャンセル2名
friendly.connpass.com

東京

20人枠に対して12人申し込みキャンセル4名
friendly.connpass.com

皆様ご参加ありがとうございました!

反省

ページに「いいね」が少ない

「いいね」増やすともっと効果あったかも。マイナーページなんで。100人くらいなんですよねw。ていうか真面目にもっと更新しないとダメですね。後せめてHPからリンク張っておこう。

.NetCoreから.NetFrameworkのdllを普通に参照できた・・・

え?これって常識だったの?僕が知らんかっただけ?

きっかけはこのブログ

qiita.com
えーと、.NetCore3でWinFormsとかWPFつくるコマンドってどうだったけなー。って感じでググってたら、こんなブログがありました。サーとみてると、「.NET Frameworkライブラリの使用」ってのがありました。えええ?どういうこと?なんでそんなことできんの?だって、objectクラスの実装されているdll別々やで・・・

あ...ありのまま 今 起こった事を話すぜ

な… 何を言っているのか わからねーと思うが 
おれも 何をされたのか わからなかった…
↑書きたかっただけ。

とりあえず、WinFormsでCore3.0とFrameworkのexe作りました。

f:id:ishikawa-tatsuya:20190302164246p:plain
両方ともWinFormsのアプリですね。
こんなコードを入れて、objectとFormの入ってるアセンブリのパスを表示させてます。

var text = new TextBox { Multiline = true, Dock = DockStyle.Fill };
text.Text = string.Join(Environment.NewLine, new[]
{
    typeof(object).Assembly.Location,
    GetType().BaseType.Assembly.Location
});
Controls.Add(text);

f:id:ishikawa-tatsuya:20190302164548p:plain
C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms\v4.0_4.0.0.0__b77a5c561934e089\System.Windows.Forms.dll

f:id:ishikawa-tatsuya:20190302164527p:plain
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.0.0-preview-27122-01\System.Private.CoreLib.dll
C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\3.0.0-alpha-27128-4\System.Windows.Forms.dll

ここまでは、あたり前な話。.NetFrameworkはMicrosoft.NET以下、Core3.0はdotnet以下のアセンブリを参照してますね。

.NetCoreから.NetFrameworkのアセンブリを参照

え?マジで?普通にできたよ。これって常識やったの?
f:id:ishikawa-tatsuya:20190302165620p:plain
これ実行したらどうなるの?
ってことで、Core3.0のexeからFrameworkのFormを呼び出してみました。

//これはCore3.0のWinForms
public partial class CoreForm : Form
{
    public CoreForm()
    {
        InitializeComponent();

        Text = "DotNetCore3";

        var text = new TextBox { Multiline = true, Dock = DockStyle.Fill };
        text.Text = string.Join(Environment.NewLine, new[]
        {
            typeof(object).Assembly.Location,
            GetType().BaseType.Assembly.Location
        });
        Controls.Add(text);

        var button = new Button { Text = "Show Framework", Dock = DockStyle.Bottom};
        button.Click += (_, __) => 
        {
            //FrameworkのFormを呼び出し
            using (var framework = new FrameworkForm())
            {
                framework.ShowDialog();
            }
        };
        Controls.Add(button);
    }
}

.NetFrameworkのFormのアセンブリの参照がDotNetCoreの方に変わった!

f:id:ishikawa-tatsuya:20190302170157p:plain

これって実質.NetStandardなのでは?

これって正式仕様なの?よくわかんないなー。

Test Assistant Pro リリース!

Test Assistant Pro っていうツールをこっそり作っていました。
https://www.codeer.co.jp/Tools/TestAssistantPro

Codeer初の有料ツールです。実装補助ツールという位置づけです。(キャプチャリプレイ的なこともできますが、そういうツールとちょっと違う感じです)実装補助ツールなんで結局C#とFriendlyの理解は必要なのですw。
結局UIレベルのテストで価値出るくらいのものを作ろうを思ったらプログラムで制御は必須になってくるんですよね・・・。
でもFriendlyユーザーの皆さんは今まで手書きでDriverとScenario書いていたと思うのですが、その大部分を自動でサクッと作れるようになります。Codeer内部でも使っているのですけど、超便利です。

比較表

「比較表書いて」ってよく言われますけど、客観的に書くのは難しいですよね。(だって自分たちで作ったツールが使いやすいに決まってるじゃん)Codeerメンバーで某社ツールと使い比べた感想はこんな感じでした。とは言え、某社ツールから手書きFriendlyに乗り換えたお客さんもいますよ。

初期学習難易度 カスタマイズ プロジェクトごとの調整 作成速度 ソース可読性 メンテ効率 動作確実性 実行速度 テスト実行時ライセンス
某社ツール 不要 必要
TestAssistantPro 高※1 必要 不要

※1 Friendlyの学習コストも入れてます。

ツール詳細

詳細はこちらに書いてるので、ご興味ある人は是非見てください。(日本語もあります)ダウンロードしてREADMEに沿って自分でトライすることもできます。(ライセンスは必要な方はお問い合わせいただくと、トライアルライセンスを発行いたします)
github.com

Friendlyと同じくカスタマイズできるってとこがポイント高いかなーと思ってます。

  • 特殊なコントロールへの対応
  • プロジェクトごとに欲しいコード生成機能の追加

なんかも簡単にできます。

ハンズオンやります。

それで、その紹介もかねてハンズオンを大阪と東京で開催することにしました!ご興味ある方はお気軽にご参加お願いいたします。ライセンスは当日貸出ます。

大阪

2/21(木) 19:30~ 場所:Codeer
friendly.connpass.com

東京

3/10(日) 13:00~ 場所:株式会社ビズリーチさま 本社
friendly.connpass.com
ハンズオンではないですが、.NetCore3のリリースがあるんでそれに関連したネタでも話します。
3/8(金) 15:00~ インフラジスティックス様
connpass.com