friendlyで始めるwindowsアプリシステムテスト自動化+内部使用技術解説
DESCRIPTION
2014/08/23 わんくま横浜勉強会の資料TRANSCRIPT
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 WinFormsWPF
(めとべや)
GUI操作ライブラリPinInterface(VSHTC)
記述性UP
拡張も自由自在!Friendlyの上に構築されているから安定感抜群!
//各コントロールをラップする//シンプルで直感的なインターフェイスのみ定義されているvar _comboBox = new FormsComboBox(form._comboBox);
デモ
Codeer で検索
eが一個多い
これらはNugetで無料で入手できます!
自動化環境
+ +
VSもTest作成だけならExpressでOK
+
FriendlyFriendly.WindowsFriendly.Windows.NativeStandardControlsFriendly.FormsStandardControlsFriendly.WPFStandardControlsFriendly.PinInterface
VSTest
ということは?
・ツール購入コスト無料
・簡単なインターフェイス、既知のインターフェイス→作成コスト減
・安定感抜群→運用コスト減
・テスタビリティーを容易に操作→作成、メンテコスト減
・高速な動作→実行時間減
備考) テスタビリティー操作(自動化と相性悪いコード)
//ここの結合は不安が少ないvoid Event(object sender, EventArgs e){
EventCore(PointToClient(Control. MousePosition));}//これをFriendlyで呼び出すvoid EventCore(Point mousePosClient){//ここから先のロジックをテストしたい。
}
プロダクトを変更。難易度高くて効果の低い部分は自動化しない。効果の高い部分のみ呼び出せるようにする。
・キー、マウス直接参照・D&D・OS提供のGUIetc…
・タイマ・非同期・ペイントイベントを利用したトリッキーコード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/symposium/timetable/day1/
ご清聴ありがとうございました。明日からでもFriendlyで自動化を始めましょう!
http://www.codeer.co.jp/AutoTest
picture Dawn Huczek