Friendlyで始める
Windowsアプリ
システムテスト自動化
+
内部使用技術解説
石川達也
株式会社Codeer代表取締役
Microsoft MVP for C#
Windowsアプリテスト自動化歴9年
自己紹介
システムテスト自動化って?
文字通り人間の代わりに
アプリケーションをプログラムが動かして
成否判定をすることです。
お得?
コストを抑えたらね
(・∀・)
エラー!
成功
開発期間中、
実行し続ける。
デグレを早期に検出。
テストの作りが悪くて不安定
仕様変更等でメンテ
作成
指定のケースではデグレがなかった
という情報を取得できた!
BugFix
実行
OS層
Winコア(User32,Kernel32とか)
MS,他ベンダGUI
ユーザ実装
←SendInput 不安定、遅い
←Win32Api
←UIAutomation
難しい
操作できないのもある
GUIアプリをプログラムからどうやって操作するの?
ところで、同一プロセスからだったら操作できるでしょ?
//当たり前
void Operation()
{
_comboBox.SelectedIndex = 1;
}
そこで、Friendlyですよ!
Friendly
他のとは根本的に違います。
対象プロセスと、友達になって、
まるで、同一プロセスでプログラムするように
我が物顔で操作できるのです!
最強
①君のものは僕のもの
public partial class MainForm : Form
{
ComboBox _comboBox;
string MyFunc(int value)
{
return value.ToString();
}
}
public void YourThingIsMine()
{
var process=
Process.GetProcessesByName("Target")[0];
//友達になると・・・
var app = new WindowsAppFriend(process);
//別プロセスのオブジェクトを
//自分のプロセスのもののように操作できる。
dynamic form = app.Type<Application>().OpenForms[0];
form._comboBox.SelectedIndex = 1;
string ret = form.MyFunc(3);
}
そ、そんな・・・
.Netはもちろん
NativeのDLL公開関数もOK!
②僕のものは君のもの
void MyThingIsYours()
{
var process= Process.GetProcessesByName("Target")[0];
var app = new WindowsAppFriend(process);
//自分のコードを動的にインジェクション!
WindowsAppExpander.LoadAssembly(app, GetType().Assembly);
//挿入したコードを相手プロセスで実行
app.Type(GetType()). ForTest();
}
static void ForTest() { /*テスト用*/ }
え!? 勝手に?
上位ライブラリ紹介
基本
内部メソッド操作、DLLインジェクション
Win32 WinForms
WPF
(めとべや)
GUI操作ライブラリ
PinInterface
(VSHTC)
記述性UP
拡張も自由自在!
Friendlyの上に構築されているから安定感抜群!
//各コントロールをラップする
//シンプルで直感的なインターフェイスのみ定義されている
var _comboBox = new FormsComboBox(form._comboBox);
デモ
Codeer で検索
eが一個多い
これらはNugetで無料で入手できます!
自動化環境
+ +
VSもTest作成だけならExpressでOK
+
Friendly
Friendly.Windows
Friendly.Windows.NativeStandardControls
Friendly.FormsStandardControls
Friendly.WPFStandardControls
Friendly.PinInterface
VSTest
ということは?
・ツール購入コスト無料
・簡単なインターフェイス、既知のインターフェイス
→作成コスト減
・安定感抜群
→運用コスト減
・テスタビリティーを容易に操作
→作成、メンテコスト減
・高速な動作
→実行時間減
備考) テスタビリティー操作(自動化と相性悪いコード)
//ここの結合は不安が少ない
void Event(object sender, EventArgs e)
{
EventCore(PointToClient(Control. MousePosition));
}
//これをFriendlyで呼び出す
void EventCore(Point mousePosClient)
{
//ここから先のロジックをテストしたい。
}
プロダクトを変更。
難易度高くて効果の低い
部分は自動化しない。
効果の高い部分のみ
呼び出せるようにする。
・キー、マウス直接参照
・D&D
・OS提供のGUI
etc…
・タイマ
・非同期
・ペイントイベントを利用したトリッキーコード
etc…
コスパに優れた自動テスト構築が可能!
明日からでも自動化しよう!
・・・
知ってますよ。
わんくま横浜は、
こんなことじゃ満足しないんでしょ?
本邦初公開!
友達の作り方
最終的にはリフレクションを使っています。
でも、普通は自プロセスにしか使えないですよね?
//いくら探してもみつからない
foreach (var assembly in
AppDomain.CurrentDomain.GetAssemblies())
{
var type = assembly.GetType(“Target.ClassA”);
}
//当たり前だけど、自分のプロセスのFormしか見つからない
var forms =(FormCollection)typeof(Application).GetProperty("OpenForms").
GetGetMethod().Invoke(null, new object[0]);
ということは、こうですよ。
リフレクション実行!
Dll Injection!
これに話しかける。
関数名、引数を渡す。
あれ?
テストプロセスと友達だった気がする…
Win32APIを使ってDLLインジェクション
//インジェクションできるのはネイティブDLLのみ
//対象プロセスにメモリ作成
IntPtr path = VirtualAllocEx(...);
//ロードさせたいパスを書き込み
WriteProcessMemory(path, ...);
//LoadLibraryのアドレスを取得
//★Kernel32は常に同じアドレスにロードされる!
IntPtr pFunc = GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryW");
//LoadLibraryを対象プロセスで実行!
CreateRemoteThread(..., pFunc, path, ...);
入りました!
ところで、 CreateRemoteThreadに渡す関数ポインタの型
CreateRemoteThread(..., pFunc, ...);
//pFuncの型はコレに合致するもの
typedef DWORD (__stdcall *LPTHREAD_START_ROUTINE) (
[in] LPVOID lpThreadParameter
);
//あれ?戻り値・・・
HMODULE LoadLibraryW(
LPCWSTR lpFileName
);
64bitのとき、合ってないやん! ∑( ゚Д゚ノ)ノ
HMODULE Func(LPCWSTR)
{
HMODULE m = nullptr;
return m;
mov rax,qword ptr [rsp]
}
int _tmain(int argc, _TCHAR* argv[])
{
LPTHREAD_START_ROUTINE f = (decltype(f))Func;
auto ret = f(nullptr);
call qword ptr [f]
mov dword ptr [ret],eax
return 0;
}
実験してみました!
戻り値使わなかったら
全く問題なし (゚∀゚;)
HMODULEを戻り値に使っても
レジスタしかつかわない
情報が落ちただけ
挿入したDLLのAPIを呼び出し
//自分のプロセスで関数ポインタを取得
IntPtr mod = LoadLibrary(dllPath);
IntPtr proc = GetProcAddress(mod, procName);
//差分を計算
var distance = …;//proc - mod; x64とx86で型が違う
//相手プロセスの中でのDLLのアドレスを取得
IntPtr targetDllAddress;
EnumProcessModules(...)
...
//差分を足したら、対象プロセス内での関数ポインタになる
IntPtr pFunc = …;//targetDllAddress + distance;
//指定の関数を対象プロセスで実行!
CreateRemoteThread(..., pFunc, path, ...);
Init()
初期化開始!
通信サーバー
立ち上げるよー
呼び出された関数内で.Netのアセンブリをロード
//ホストAPIを使って.Netの機能呼び出し
ICLRMetaHost *pMetaHost;
ICLRRuntimeInfo *pRuntimeInfo;
ICLRRuntimeHost *pClrRuntimeHost;
CLRCreateInstance(... , IID_PPV_ARGS(&pMetaHost));
//pRuntimeInfoの検索
//ルールはWindowsAppFriendのコンストラクタ参照
...
//CLR開始
pRuntimeInfo->IsLoadable(...);
pRuntimeInfo->GetInterface(... , IID_PPV_ARGS(&pClrRuntimeHost));
pClrRuntimeHost->Start();
//.Netのアセンブリのロードと目的のメソッド呼び出し
pClrRuntimeHost->ExecuteInDefaultAppDomain
(asm, type, method, args, &ret);
もちろん.Netの機能が
いるよねー。
.Netのアセンブリインジェクションで気を付けること
//アセンブリの解決
AppDomain.CurrentDomain.AssemblyResolve += ...;
アセンブリの解決!
GAC、プロービングパス以外のDLLは、
中途半端にしか使えない。
AssemblyResolveで解決
ハマりポイント!
後は、通信サーバーを立ち上げる
僕たち、
友達になりました! サーバーと言っても単なるWindow
WM_COPYDATAを使うと
リッチなデータが送受信できる!
SendMessageでデータもらってます。
でも、COM対策はしてるので例の
ルールは気にしなくてOK!
求む! Friendlyエバンジェリスト
今日実演したデモができるセットをご用意いたしております。
Friendlyのデモは手品のようなので、
LTや宴会でやったら、うけること間違いなし!
また、会社に導入したいとき、上司に見せると効果アリ!?
こちらからダウンロードできます。
http://www.codeer.co.jp/download
お知らせ 登壇予定
9/20 Boost.勉強会 #16 大阪
http://osakaboostjp.doorkeeper.
jp/events/14150
9/11 SQIPシンポジウム
http://www.juse.jp/sqip/symp
osium/timetable/day1/
ご清聴ありがとうございました。
明日からでもFriendlyで自動化を始めましょう!
http://www.codeer.co.jp/AutoTest
picture Dawn Huczek

Friendlyで始めるwindowsアプリシステムテスト自動化+内部使用技術解説

Editor's Notes