接著上一篇的話題,因為要為 win32 程式寫 UI 測試,自已也摸索出來一個大致的架構。在這裡做一個小結:
以下代碼都發表在公有領域(http://zh.wikipedia.org/zh-cn/Public_domain),你可以自由地使用它們。
1. 首先,你需要建立進程:
{<br /> TCHAR cmdLine[1] = _T("");<br /> STARTUPINFO si;</p><p> ZeroMemory(&si, sizeof(STARTUPINFO));<br /> ZeroMemory(&piPlanet, sizeof(PROCESS_INFORMATION));</p><p> si.cb = sizeof(STARTUPINFO);<br /> si.dwFlags = STARTF_USESHOWWINDOW;<br /> si.wShowWindow = SW_NORMAL;<br /> BOOL bRet = ::CreateProcess(_T("Planet.exe"), cmdLine, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &piPlanet);<br /> ASSERT(bRet);<br />}
建立進程後,PROCESS_INFORMATION 結構體中就有了進程 id 和主線程 id 。
2. 獲得主視窗控制代碼
主要通過 EnumWindows 來實現,如果視窗 title 是固定的,那麼也可以用 FindWindowEx 。我這裡由於 title 和 class name 都未知,用了一種比較搓的辦法:先通過線程 id 隨便找一個視窗,再用 GetParent 擷取最頂層的視窗
// 一個回呼函數,相當於 EnumWindows 的過濾器<br />static BOOL CALLBACK findWindowThreadIdProc(HWND hWnd, LPARAM param)<br />{<br /> DWORD id = ::GetWindowThreadProcessId(hWnd, NULL); // 根據 HWND 得到線程id<br /> if (id == (DWORD) param)<br /> {<br /> hWndFound = hWnd;<br /> return FALSE;<br /> }<br /> return TRUE;<br />}</p><p>{<br /> hWndFound = NULL;<br /> // 找到指定的線程的第一個視窗:<br /> ::EnumWindows(findWindowThreadIdProc, piPlanet.dwThreadId);<br /> HWND hTmp;<br /> // 迴圈找到最頂層的視窗:<br /> while ((hTmp = ::GetParent(hWndFound)) != NULL)<br /> {<br /> hWndFound = hTmp;<br /> }<br />}
當然,如果你提前就知道視窗的 class name ,就可以直接在回呼函數中指定這個條件。
順便說一下如何在 MFC 程式中指定 class name :讓你的 CMainFrame 繼承來自 CWnd 的 PreCreateWindow 函數,這個函數將在 MFC 調用 CreateWindow 之前調用:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)<br />{<br /> if (!CFrameWnd::PreCreateWindow(cs))<br /> return FALSE;</p><p> WNDCLASS wndclass;<br /> ::GetClassInfo(::AfxGetInstanceHandle(), cs.lpszClass, &wndclass);<br /> wndclass.lpszClassName= _T("PlanetMainFrame"); // 將 class name 設為指定的值<br /> VERIFY(::AfxRegisterClass(&wndclass));<br /> cs.lpszClass = wndclass.lpszClassName;</p><p> return TRUE;<br />} // CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
另外,可以通過 VS 內建的 spy++ 查看案頭上所有視窗的 class name 。
3. 類比點菜單
::PostMessage(hWnd, WM_COMMAND, MAKEWPARAM(CM_PROJECTPURCHASES, BN_CLICKED), NULL);<br />Sleep(1000);
hWnd 是你的視窗控制代碼,CM_PROJECTPURCHASES 是對應的菜單 ID 。注意要 Sleep 足夠的時間。
還要注意 PostMessage 和 SendMessage 的區別:SendMessage 必須要等相應程式處理完訊息之後才返回,而 PostMessage 是非同步,立即返回。所以 PostMessage 需要 Sleep,而 SendMessage 則不需要。
4. 獲得最表面的視窗
hWnd = ::GetForegroundWindow();
與 GetFocus() 不同,這個直接獲得直觀上最表層的那個視窗(比如點了菜單後彈出來的一個視窗),相當 imba 。而 GetFocus() 得到的控制代碼可能會是一個編輯框控制項。
5. 關閉視窗
#define closeWindowX(w) {::PostMessage(w, WM_CLOSE, 0, 0);Sleep(500);}
然後就是 GetDlgItem() 之流了,再加上上篇文章介紹的類比按鍵,基本上能搞定大部分的自動化 UI 測試工作。