C # using APIs to compile a Black Box Automated Testing Tool
Download this article code (vs2010 development): http://download.csdn.net/source/2796362
Summary:
1: A simple example
1.1: Introduction to enumchildwindows
1.2: main source code
2: Difficulty: how to obtain the specified control handle
2.1: use Spy ++
2.2: Get the widget location
2.3: Get the Control ID
1: A simple example
In the daily coding process, we often perform automated tests. The automated test here is not a unit test, but a simulation of manual input for fast and high concurrency testing. The available automatic chemical industry has LoadRunner, and currently has a powerful testing platform in vs2010 (recording operation steps, automatic generation of code ). However, mastering these tools also requires a certain amount of time, and most importantly, it is not flexible enough for a programmer. Therefore, a more efficient method is to call the Windows API and manually write the code.
The following is a simple demonstration. For simplicity, assume that an application exists:
1: provides a winform with a textbox and a button;
2: click the button. A prompt box is displayed. The prompt box contains the textbox value;
Now, the test requirements are as follows:
1: run the above program on 300 machines;
2: Go to the 300 machines and click the button to see if function 2 has been implemented;
Obviously, there is not such a simple program in the actual situation. The actual situation may be that you click the button to download a file in a unified manner, and the test requirements may become the server load assessment. Now, the test department obviously does not have 300 people sitting on the client to verify the test results. In this case, we need to provide an automated test tool to complete the necessary test tasks.
The testing tool is also a C # program. Its main purpose is:
1: Get the window handle of the above application, and then get the textbox handle and button handle;
2: Enter some characters randomly for textbox;
3: click a button;
1.1: Introduction to enumchildwindows
Here we need to introduce enumchildwindows,
Enumchildwindows is a good thing. You can enumerate all the child windows of a parent window:
Bool enumchildwindows (
HwndHwndparent, // Handle to parent window // parent window handle
WndenumprocLpenumfunc, // Callback function address
LparamLparam// Application-defined value // The parameter you have defined
);
In this simple way, Let's define another callback function, as shown below:
Bool callback enumchildproc (
HwndHwnd, // Handle to Child Window
LparamLparam// Application-defined value
);
When you call the enumchildwindows function, the function is always enumerated until the maximum subwindow is called or the callback function returns a false value. Otherwise, the function is always enumerated.
1.2: main source code of a simple example
The main code of the test tool is as follows:
Private void button#click (Object sender, eventargs e) {// obtain the form handle of the test program intptr mainwnd = findwindow (null, "formlogin "); list <intptr> listwnd = new list <intptr> (); // obtain the handle of the OK button on the form intptr hwnd_button = find1_wex (mainwnd, new intptr (0), null, "OK"); // obtain the handle enumchildwindows (mainwnd, new callback (delegate (intptr hwnd, int lparam) {listwnd. add (hwnd); Return true;}), 0); foreach (intptr item in Listwnd) {If (item! = Hwnd_button) {char [] userchar = "luminji ". tochararray (); foreach (char CH in userchar) {sendchar (item, CH, 100) ;}} sendmessage (hwnd_button, wm_click, mainwnd, "0 ");} public void sendchar (intptr hand, char CH, int sleeptime) {postmessage (hand, wm_char, CH, 0); system. threading. thread. sleep (sleeptime);} public static int wm_char = 0x102; public static int wm_click = 0x00f5; [dllimport ("user32.dll", entrypoint = "sendmessage")] public static extern int sendmessage (intptr hwnd, int MSG, intptr wparam, string lparam); [dllimport ("user32.dll")] public static extern intptr find1_wex (intptr hwndparent, intptr hwndchildafter, string lpszclass, string lpszwindow); [dllimport ("user32.dll", setlasterror = true)] public static extern intptr findwindow (string lpclassname, string lpwindowname); [dllimport ("user32.dll") public static extern int anypopup (); [dllimport ("user32.dll", charset = charset. auto, setlasterror = true)] public static extern int getwindowtext (intptr hwnd, stringbuilder lpstring, int nmaxcount); [dllimport ("user32.dll")] public static extern int enumthreadwindows (intptr dwthreadid, callback lpfn, int lparam); [dllimport ("user32.dll")] public static extern int enumchildwindows (intptr hwndparent, callback lpfn, int lparam); [dllimport ("user32.dll ", charset = charset. ANSI)] public static extern intptr postmessage (intptr hwnd, int wmsg, int wparam, int lparam); [dllimport ("user32.dll", charset = charset. ANSI)] public static extern intptr sendmessage (intptr hwnd, int wmsg, intptr wparam, intptr lparam); [dllimport ("user32.dll", charset = charset. unicode)] public static extern intptr sendmessagea (intptr hwnd, int wmsg, int wparam, int lparam); [dllimport ("user32.dll", charset = charset. auto)] Static extern int getclassname (intptr hwnd, stringbuilder lpclassname, int nmaxcount); [dllimport ("user32.dll", setlasterror = true, charset = charset. auto)] public static extern int getwindowtextlength (intptr hwnd); [dllimport ("user32.dll", charset = charset. auto, setlasterror = false)] public static extern intptr getparent (intptr hwnd); Public Delegate bool callback (intptr hwnd, int lparam );
Running effect:
2: Difficulty: how to obtain the specified control handle
Careful people may have discovered that the following code is used to assign values to the text box in the previous article:
foreach (IntPtr item in listWnd) { if (item != hwnd_button) { char[] UserChar = "luminji".ToCharArray(); foreach (char ch in UserChar) { SendChar(item, ch, 100); } } }
Assume that there are multiple text boxes on the form. In fact, this code will input "luminji" to all text boxes. This is not allowed in most applications. We need to precisely locate the control to be controlled.
When we get the handle of the OK button, we use the function:
IntPtr hwnd_button = FindWindowEx(mainWnd, new IntPtr(0), null, "OK");
This function cannot be used when you want to obtain the text box handle, because all text boxes have no title, that is, the value similar to "OK. Some people say, use the control ID. See:
2.1: Get the Control ID
Non-. net programs, once the program is generated, the Control ID is fixed, so this trick is used in non-. net programs, it is even better.
The function declaration for obtaining the control handle Based on the ID is as follows:
[DllImport("user32.dll ", EntryPoint = "GetDlgItem")] public static extern IntPtr GetDlgItem( IntPtr hParent, int nIDParentItem);
The first parameter is the form handle, and the second parameter is the Control ID.
However, obviously, this method does not apply to our. Net program, because we will find that our. Net program does not run once, And this ID is changed.
2.2: Get the widget location
Therefore, the final solution is to manually obtain the desired control handle based on the control location. The declaration of this function is as follows:
Now the key is how to obtain the position of the control. In vs, we can see that a widget has X and Y coordinates. For the textbox of the preceding program, the position displayed in vs is ", however, vs does not contain the coordinates of the title and border. However, this coordinate value can be used as a reference for manual comparison.
For more precise coordinate values, we can write code to implement them as follows:
EnumChildWindows(mainWnd, new CallBack(delegate(IntPtr hwnd, int lParam) { listWnd.Add(hwnd); StringBuilder className = new StringBuilder(126); StringBuilder title = new StringBuilder(200); GetWindowText(hwnd, title, 200); RECT clientRect; GetClientRect(hwnd, out clientRect); int controlWidth = clientRect.Width; int controlHeight = clientRect.Height; int x = 0, y = 0; IntPtr parerntHandle = GetParent(hwnd); if (parerntHandle != IntPtr.Zero) { GetWindowRect(hwnd, out clientRect); RECT rect; GetWindowRect(parerntHandle, out rect); x = clientRect.X - rect.X; y = clientRect.Y - rect.Y; Debug.Print(x.ToString()); Debug.Print(y.ToString()); } return true; }), 0);
Note that X and Y in the code above are the precise x and y values of a control. Record them and compare them to get the exact coordinate values. In the preceding example, the coordinates of the text box are "78,113 ". With this coordinate value, we will know the control handle, that is, the control of which hwnd belongs. 2.3: Get the handle according to the enumchildwindows enumeration order. If you don't want to be so troublesome, there is also a simple solution, that is, to use the enumchildwindows enumeration order. You must know that on different machines, the subcontrols in the enumchildwindows enumeration form are in the same order, that is, if there are two text boxes, the order in which they are enumerated on this machine is 2 and 3. the order in which they are enumerated on other machines is also a fixed order. By comparison, we can also get their respective handles. Of course, if we have these handles, what else can't we do? 2.4: use Spy ++
Spy ++ is a tool of Microsoft. You can obtain the ID, type, handle, and other information on the form. In our example, IDS and handles remain unchanged on each machine, so this tool is of little use to us. However, when you hack someone else's program, it will play a role.
Refer:
1: http://book.21www.cn/info/vb/api/4076.html
2: http://dev.firnow.com/course/3_program/cshapo/csharpjs/20100714/441439.html