zoukankan      html  css  js  c++  java
  • 《软件测试自动化之道》读书笔记 之 基于Windows的UI测试

    《软件测试自动化之道》读书笔记 之 基于Windows的UI测试

    2014-09-25

    测试自动化程序的任务
    待测程序
    测试程序
      启动待测程序
      获得待测程序主窗体的句柄
      获得有名字控件的句柄
      获得无名字控件的句柄
      发送字符给控件
      鼠标单击一个控件
      处理消息对话框
      处理菜单
      检查应用程序状态
      示例程序
    参考

    本章主要讲述如何使用底层的Windows自动化技术通过用户界面来测试应用程序。这些技术涉及Win32 API的调用(比如FindWindow()函数)以及想待测程序发送Windows消息(比如WM_LBUTTONUP)。

    测试自动化程序的任务


     返回

    基于Windows的UI测试,逍遥完成的工作主要有以下三类:

    • 找到目标窗体/控件的句柄
    • 操作这个窗体/控件
    • 检测这个窗体/控件

    待测程序


     返回

    待测程序是一个用来做颜色混合的应用程序,关键代码如下:

     1 using System;
     2 using System.Windows.Forms;
     3 
     4 namespace WinApp
     5 {
     6     public partial class Form1 : Form
     7     {
     8         public Form1()
     9         {
    10             InitializeComponent();
    11         }
    12 
    13         private void button1_Click(object sender, EventArgs e)
    14         {
    15             string tb = textBox1.Text;
    16             string cb = comboBox1.Text;
    17 
    18             if (tb == "<enter color>" || cb == "<pick>")
    19                 MessageBox.Show("You need 2  colors", "Error");
    20             else
    21             {
    22                 if (tb == cb)
    23                     listBox1.Items.Add("Result is " + tb);
    24                 else if (tb == "red" && cb == "blue" || tb == "blue" && cb == "red")
    25                     listBox1.Items.Add("Result is purple");
    26                 else
    27                     listBox1.Items.Add("Result is black");
    28             }
    29         }
    30 
    31         private void exitToolStripMenuItem1_Click(object sender, EventArgs e)
    32         {
    33             this.Close();
    34         }
    35     }
    36 }
    View Code

     图1 AUT 

    测试程序


     返回

    启动待测程序

    1 using System.Diagnostics;
    2 
    3         static void Main(string[] args)
    4         {
    5                //...
    6                 string path = "..\..\..\WinApp\bin\Debug\WinApp.exe";
    7                 Process p = Process.Start(path);
    8                //...
    9         }
    View Code

    获得待测程序主窗体的句柄

    要获得待测程序主窗体的句柄,可使用FindWindow() Win32 API函数来解决这个问题。

    FindWindow()的函数签名用C++来描述是:

    HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName); 

    Win32 API函数FindWindow()是Windows操作系统的一部分,是由传统的C++程序而不是用受控代码(managed code)编写的。它返回HWND(Handle of Window), 是一个窗体的句柄

    C#要使用Win32 API函数FindWindow(),可通过.Net平台的invoke(P/Invoke)机制,P/Invoke相关特性位于System.Runtime.InteropServices命名空间内。

            [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
            static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    DllImport是用来将特性化方法由非托管动态链接库 (DLL) 作为静态入口点公开。

    说明[1]
      1、DllImport只能放置在方法声明上。
      2、DllImport具有单个定位参数:指定包含被导入方法的 dll 名称的 dllName 参数。
      3、DllImport具有五个命名参数:
       a、CallingConvention 参数指示入口点的调用约定。如果未指定 CallingConvention,则使用默认值 CallingConvention.Winapi。
       b、CharSet 参数指示用在入口点中的字符集。如果未指定 CharSet,则使用默认值 CharSet.Auto。
       c、EntryPoint 参数给出 dll 中入口点的名称。如果未指定 EntryPoint,则使用方法本身的名称。
       d、ExactSpelling 参数指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配。如果未指定 ExactSpelling,则使用默认值 false。
       e、PreserveSig 参数指示方法的签名应当被保留还是被转换。当签名被转换时,它被转换为一个具有 HRESULT 返回值和该返回值的一个名为 retval 的附加输出参数的签名。如果未指定 PreserveSig,则使用默认值 true。
       f、SetLastError 参数指示方法是否保留 Win32"上一错误"。如果未指定 SetLastError,则使用默认值 false。
      4、它是一次性属性类。
      5、此外,用 DllImport 属性修饰的方法必须具有 extern 修饰符。

    注意:.NET平台大大简化了数据类型的模型,从而提高了程序开发的效率。要使用P/Invoke机制,必须为Win32数据类型找到相应的 C#数据类型。

    此示例中,在.NET坏境中,窗体句柄的类型是System.IntPtr,它是一个平台相关的类型,用来代表指针(内存地址)或者句柄。它对应于Win32的数据类型HWND。String对应于LPCTSTR。

    C#与win32 api数据类型对照 

    参数:

    • lpClassName:是窗体类名称,更OOP中类无任何关系。是由系统生成的一个字符串,用来把相应的窗体注册到操作系统。因为窗体/控件类的名称并不具有唯一性,对查找一个窗体/控件并没有太大帮助。因此,在本示例中传给它null。
    • lpWindowName:是窗体的名称。也被叫做window title或者window caption。在windows form程序中,这个值通常称为form name。

    获得待测程序主窗体的句柄示例代码如下: 

     1 using System.Runtime.InteropServices;
     2 
     3         static void Main(string[] args)
     4         {
     5                 //...
     6                 IntPtr mwh = FindMainWindowHandle("Form1", 100, 25);
     7                 //...
     8         }
     9 
    10         [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
    11         static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    12 
    13         static IntPtr FindMainWindowHandle(string caption, int delay, int maxTries)
    14         {
    15             return FindTopLevelWindow(caption, delay, maxTries);
    16         }
    17 
    18         static IntPtr FindTopLevelWindow(string caption, int delay, int maxTries)
    19         {
    20             IntPtr mwh = IntPtr.Zero;
    21             bool formFound = false;
    22             int attempts = 0;
    23 
    24             do
    25             {
    26                 //FindWindow
    27                 mwh = FindWindow(null, caption);
    28                 if (mwh == IntPtr.Zero)
    29                 {
    30                     Console.WriteLine("Form not yet found");
    31                     Thread.Sleep(delay);
    32                     ++attempts;
    33                 }
    34                 else
    35                 {
    36                     Console.WriteLine("Form has been found");
    37                     formFound = true;
    38                 }
    39             } while (!formFound && attempts < maxTries);
    40 
    41             if (mwh != IntPtr.Zero)
    42                 return mwh;
    43             else
    44                 throw new Exception("Could not find Main Window");
    45         } 
    View Code

    获得有名字控件的句柄

    1         static void Main(string[] args)
    2         {
    3                 //...
    4                 IntPtr tb = FindWindowEx(mwh, IntPtr.Zero, null, "<enter color>");
    5                 //...
    6         }
    7 
    8         [DllImport("user32.dll", EntryPoint = "FindWindowEx",CharSet = CharSet.Auto)]
    9         static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
    View Code

    参数:

    • hwndParent:控件目标的父窗体句柄
    • hwndChildAfter:从哪个控件开始找,即从下一个控件开始找
    • lpszClass:class name(如上参数lpClassName)
    • lpszWindow:目标控件的window name/title/caption

    图2 Spy++捕获控件button1  

    获得无名字控件的句柄

    如何获得一个没有名字的空间的句柄,可通过隐含索引来查找相应控件

     1         static void Main(string[] args)
     2         {
     3                 //...
     4                 IntPtr cb = FindWindowByIndex(mwh, 2);
     5                 //...
     6         }
     7 
     8         [DllImport("user32.dll", EntryPoint = "FindWindowEx",CharSet = CharSet.Auto)]
     9         static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
    10 
    11         static IntPtr FindWindowByIndex(IntPtr hwndParent, int index)
    12         {
    13             if (index == 0)
    14                 return hwndParent;
    15             else
    16             {
    17                 int ct = 0;
    18                 IntPtr result = IntPtr.Zero;
    19                 do
    20                 {
    21                     //FindWindowEx
    22                     result = FindWindowEx(hwndParent, result, null, null);
    23                     if (result != IntPtr.Zero)
    24                         ++ct;
    25                 } while (ct < index && result != IntPtr.Zero);
    26 
    27                 return result;
    28             }
    29         }
    View Code

    注意:这里索引的顺序是加入主窗体的顺序。见如下代码,主窗体的索引值为0,先后加入的控件button1的索引为1,comboBox1的索引为2,...

     1         private void InitializeComponent()
     2         {
     3             //...
     4             this.Controls.Add(this.button1);
     5             this.Controls.Add(this.comboBox1);
     6             this.Controls.Add(this.textBox1);
     7             this.Controls.Add(this.menuStrip1);
     8             this.Controls.Add(this.listBox1);
     9             //...
    10         }
    View Code

    你也可以通过工具“spy++”来查找先后。

    发送字符给控件

    SendMessage()的函数签名用C++签名如下:

    LRESULT SendMessage(HWND HwND, UINT Msg, WPARAM wParam, LPARAM lParam); 
    • HwND:目标窗体/控件的句柄
    • Msg:要发给该控件的Window消息
    • wParam, lParam:它们的意思和数据类型取决于相应的Windows消息

    本例中,我们要发送一个VM_CHAR消息。当按键按下时,VM_CHAR消息会发送给拥有键盘焦点的那个控件。实际上,VM_CHAR是一个Windows的常量符号,它定义为0x0102。wParam参数指定的是被按下按键的字符代码。lParam参数指定的是不同的按键状态码,比如重复次数、扫描码等。有了这些信息,就可以创建相应的C#签名:

            [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
            static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

    发送字符给控件的示例代码:

     1         static void Main(string[] args)
     2         {
     3                 //...
     4                 SendChars(tb, "red");
     5                 //...
     6         }
     7 
     8         static void SendChars(IntPtr hControl, string s)
     9         {
    10             foreach (char c in s)
    11             {
    12                 SendChar(hControl, c);
    13             }
    14         }
    15 
    16         static void SendChar(IntPtr hControl, char c)
    17         {
    18             uint WM_CHAR = 0x0102;
    19             SendMessage1(hControl, WM_CHAR, c, 0);
    20         }
    21         [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    22         static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);
    View Code

    鼠标单击一个控件

    PostMessage()的函数签名用C++签名如下:

    BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM LParam);

    PostMessage()和SendMessage()的参数列表完全一致,他们的不同是:SendMessage()会等相应的Windows消息之后才会返回;PostMessage()不会。
    相应的C#签名:

            [DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
            static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

    鼠标单击一个控件的示例代码: 

     1         static void Main(string[] args)
     2         {
     3                 //...
     4                 ClickOn(okButt);
     5                 //...
     6         }
     7 
     8         static void ClickOn(IntPtr hControl)
     9         {
    10             uint WM_LBUTTONDOWN = 0x0201;
    11             uint WM_LBUTTONUP = 0x0202;
    12             PostMessage1(hControl, WM_LBUTTONDOWN, 0, 0);
    13             PostMessage1(hControl, WM_LBUTTONUP, 0, 0);
    14         }
    15         [DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
    16         static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);
    View Code

    处理消息对话框

    消息对话框是一个上层(top-level)窗体,使用FindWindow()函数捕获它。

    处理菜单

    处理菜单的的示例代码:

     1         static void Main(string[] args)
     2         {
     3                 //...
     4 
     5                 //mwh: main window handle
     6                 IntPtr hMainMenu = GetMenu(mwh);
     7                 IntPtr hFile = GetSubMenu(hMainMenu, 0);
     8                 int iExit = GetMenuItemID(hFile, 2);
     9                 uint WM_COMMAND = 0x0111;
    10                 SendMessage2(mwh, WM_COMMAND, iExit, IntPtr.Zero);
    11                 //...
    12         }
    13 
    14         [DllImport("user32.dll")] // 
    15         static extern IntPtr GetMenu(IntPtr hWnd);
    16 
    17         [DllImport("user32.dll")] // 
    18         static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);
    19 
    20         [DllImport("user32.dll")] // 
    21         static extern int GetMenuItemID(IntPtr hMenu, int nPos);
    22 
    23         [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    24         static extern void SendMessage2(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam);
    View Code
    • GetMenu():返回程序主菜单的句柄
    • GetSubMenu():返回子菜单的句柄
    • GetSubMenu():返回菜单项的索引值。

    在该示例中选择File->Exit,并点击它。

    图3 处理菜单

    检查应用程序状态

    使用VM_GETTEXT和SendMessage()获得控件状态

    检查应用程序状态的示例代码

     1         static void Main(string[] args)
     2         {
     3                 //...
     4                 uint VM_GETTEXT = 0x000D;
     5                 byte[] buffer=new byte[256];
     6                 string text = null;
     7                 int numFetched = SendMessage3(tb, VM_GETTEXT, 256, buffer);
     8                 text = System.Text.Encoding.Unicode.GetString(buffer);
     9                 Console.WriteLine("Fetched " + numFetched + " chars");
    10                 Console.WriteLine("TextBox1 contains = " + text);
    11                 //...
    12         }
    13         [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    14         static extern int SendMessage3(IntPtr hWndControl, uint Msg, int wParam, byte[] lParam);
    View Code

    示例程序

      1 // Chapter 3 - Windows-Based UI Testing
      2 // Example Program: WindowsUITest
      3 
      4 using System;
      5 using System.Diagnostics;
      6 using System.Runtime.InteropServices;
      7 using System.Threading;
      8 
      9 namespace WindowsUITest
     10 {
     11     class Class1
     12     {
     13         [STAThread]
     14         static void Main(string[] args)
     15         {
     16             try
     17             {
     18                 Console.WriteLine("
    Launching application under test");
     19 
     20                 string path = "..\..\..\WinApp\bin\Debug\WinApp.exe";
     21                 Process p = Process.Start(path);
     22 
     23                 Console.WriteLine("
    Finding main window handle");
     24                 IntPtr mwh = FindMainWindowHandle("Form1", 100, 25);
     25                 Console.WriteLine("Main window handle = " + mwh);
     26 
     27                 Console.WriteLine("
    Finding handles to textBox1, comboBox1");
     28                 Console.WriteLine(" button1, listBox1");
     29 
     30                 // you may want to add delays here to make sure Form has rendered
     31                 IntPtr tb = FindWindowEx(mwh, IntPtr.Zero, null, "<enter color>");
     32                 IntPtr cb = FindWindowByIndex(mwh, 2);
     33                 IntPtr butt = FindWindowEx(mwh, IntPtr.Zero, null, "button1");
     34                 IntPtr lb = FindWindowByIndex(mwh, 5);
     35 
     36                 if (tb == IntPtr.Zero || cb == IntPtr.Zero ||
     37                     butt == IntPtr.Zero || lb == IntPtr.Zero)
     38                     throw new Exception("Unable to find all controls");
     39                 else
     40                     Console.WriteLine("All control handles found");
     41 
     42                 Console.WriteLine("
    Clicking button1");
     43                 ClickOn(butt);
     44 
     45                 Console.WriteLine("Clicking away Error message box");
     46                 IntPtr mb = FindMessageBox("Error");
     47                 if (mb == IntPtr.Zero)
     48                     throw new Exception("Unable to find message box");
     49                 IntPtr okButt = FindWindowEx(mb, IntPtr.Zero, null, "OK");
     50                 if (okButt == IntPtr.Zero)
     51                     throw new Exception("Unable to find OK button");
     52                 ClickOn(okButt);
     53 
     54                 Console.WriteLine("Typing 'red' and 'blue' to application");
     55                 SendChars(tb, "red");
     56 
     57                 Console.WriteLine("Check for textBox1");
     58                 uint VM_GETTEXT = 0x000D;
     59                 byte[] buffer = new byte[256];
     60                 string text = null;
     61                 int numFetched = SendMessage3(lb, VM_GETTEXT, 256, buffer);
     62                 text = System.Text.Encoding.Unicode.GetString(buffer);
     63                 Console.WriteLine("Fetched " + numFetched + " chars");
     64                 Console.WriteLine("TextBox1 contains = " + text);
     65 
     66                 ClickOn(cb);
     67                 SendChars(cb, "blue");
     68 
     69                 Console.WriteLine("Clicking on button1");
     70                 ClickOn(butt);
     71 
     72                 Console.WriteLine("
    Checking listBox1 for 'purple'");
     73 
     74                 uint LB_FINDSTRING = 0x018F;
     75                 int result = SendMessage4(lb, LB_FINDSTRING, -1, "Result is purple1");
     76                 if (result >= 0)
     77                     Console.WriteLine("
    Test scenario result = Pass");
     78                 else
     79                     Console.WriteLine("
    Test scenario result = *FAIL*");
     80                 
     81 
     82                 Console.WriteLine("
    Exiting app in 3 seconds . . . ");
     83                 //GetMenu not work
     84                 Thread.Sleep(3000);
     85                 IntPtr hMainMenu = GetMenu(mwh);
     86                 IntPtr hFile = GetSubMenu(hMainMenu, 0);
     87                 int iExit = GetMenuItemID(hFile, 2);
     88                 uint WM_COMMAND = 0x0111;
     89                 SendMessage2(mwh, WM_COMMAND, iExit, IntPtr.Zero);
     90 
     91                 Console.WriteLine("
    Done");
     92                 Console.ReadLine();
     93             }
     94             catch (Exception ex)
     95             {
     96                 Console.WriteLine("Fatal error: " + ex.Message);
     97             }
     98         } // Main()
     99 
    100         static IntPtr FindTopLevelWindow(string caption, int delay, int maxTries)
    101         {
    102             IntPtr mwh = IntPtr.Zero;
    103             bool formFound = false;
    104             int attempts = 0;
    105 
    106             do
    107             {
    108                 mwh = FindWindow(null, caption);
    109                 if (mwh == IntPtr.Zero)
    110                 {
    111                     Console.WriteLine("Form not yet found");
    112                     Thread.Sleep(delay);
    113                     ++attempts;
    114                 }
    115                 else
    116                 {
    117                     Console.WriteLine("Form has been found");
    118                     formFound = true;
    119                 }
    120             } while (!formFound && attempts < maxTries);
    121 
    122             if (mwh != IntPtr.Zero)
    123                 return mwh;
    124             else
    125                 throw new Exception("Could not find Main Window");
    126         } // FindTopLevelWindow()
    127 
    128         static IntPtr FindMainWindowHandle(string caption, int delay, int maxTries)
    129         {
    130             return FindTopLevelWindow(caption, delay, maxTries);
    131         }
    132 
    133         static IntPtr FindMessageBox(string caption)
    134         {
    135             int delay = 100;
    136             int maxTries = 25;
    137             return FindTopLevelWindow(caption, delay, maxTries);
    138         }
    139 
    140         static IntPtr FindWindowByIndex(IntPtr hwndParent, int index)
    141         {
    142             if (index == 0)
    143                 return hwndParent;
    144             else
    145             {
    146                 int ct = 0;
    147                 IntPtr result = IntPtr.Zero;
    148                 do
    149                 {
    150                     result = FindWindowEx(hwndParent, result, null, null);
    151                     if (result != IntPtr.Zero)
    152                         ++ct;
    153                 } while (ct < index && result != IntPtr.Zero);
    154 
    155                 return result;
    156             }
    157         } // FindWindowByIndex()
    158 
    159         static void ClickOn(IntPtr hControl)
    160         {
    161             uint WM_LBUTTONDOWN = 0x0201;
    162             uint WM_LBUTTONUP = 0x0202;
    163             PostMessage1(hControl, WM_LBUTTONDOWN, 0, 0);
    164             PostMessage1(hControl, WM_LBUTTONUP, 0, 0);
    165             Thread.Sleep(1000);
    166         }
    167 
    168         static void SendChar(IntPtr hControl, char c)
    169         {
    170             uint WM_CHAR = 0x0102;
    171             SendMessage1(hControl, WM_CHAR, c, 0);
    172         }
    173 
    174         static void SendChars(IntPtr hControl, string s)
    175         {
    176             foreach (char c in s)
    177             {
    178                 SendChar(hControl, c);
    179             }
    180         }
    181 
    182         // P/Invoke Aliases
    183 
    184         [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
    185         static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    186 
    187         [DllImport("user32.dll", EntryPoint = "FindWindowEx", CharSet = CharSet.Auto)]
    188         static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
    189 
    190         // for WM_CHAR message
    191         [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    192         static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);
    193 
    194         // for WM_COMMAND message
    195         [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    196         static extern void SendMessage2(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam);
    197 
    198         // for WM_LBUTTONDOWN and WM_LBUTTONUP messages
    199         [DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
    200         static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);
    201 
    202         // for WM_GETTEXT message
    203         [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    204         static extern int SendMessage3(IntPtr hWndControl, uint Msg, int wParam, byte[] lParam);
    205 
    206         // for LB_FINDSTRING message
    207         [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    208         static extern int SendMessage4(IntPtr hWnd, uint Msg, int wParam, string lParam);
    209 
    210         // Menu routines
    211         [DllImport("user32.dll")] // 
    212         static extern IntPtr GetMenu(IntPtr hWnd);
    213 
    214         [DllImport("user32.dll")] // 
    215         static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);
    216 
    217         [DllImport("user32.dll")] // 
    218         static extern int GetMenuItemID(IntPtr hMenu, int nPos);
    219 
    220     } // class
    221 } // ns
    View Code

    参考

    [1] C# DllImport的用法

    [2] C#与win32 api数据类型对照

  • 相关阅读:
    Redis的事务、锁及管理命令
    Redis消息队列
    Redis管理实战
    Redis入门部署及持久化介绍
    MySQL的存储引擎
    MHA高可用及读写分离
    jquery css hover
    SqlParameter 中 top 的使用
    Jquery 操作DropDownList 根据条件选中
    js 数值格式化函数
  • 原文地址:https://www.cnblogs.com/Ming8006/p/3993296.html
Copyright © 2011-2022 走看看