C#借助API实现黑盒自动化测试工具的编写
 

2010-12-9 来源:网络

 

本文摘要:

   一个简单的例子
      1.1:EnumChildWindows介绍
      1.2:主要源码
   难点:如何获取指定的控件句柄
      2.1:使用SPY++
      2.2:获取控件位置
      2.3:获取控件ID
   一个简单的例子
   在日常编码过程中,我们常常会进行自动化测试。这里的自动化测试不是指单元测试,而是模拟人工输入来进行快速的、高并发的测试。可以使用的自动化工具有LOADRUNNER,以及目前在VS2010中的功能很强大的测试工作平台(录制操作步骤,自动生成代码)。但是,这些工具的熟练掌握也有一定的时间成本,并且,最主要的,对于一个程序员来说,那不够灵活。所以,比较高效的一个做法是,调用WINDOWS API,自己动手写编码来实现。
   下面做一个简单的演示。为了简便起见,假设存在这样一个应用程序:
      1:提供一个WINFORM窗体,上面存在一个TextBox,以及一个Button;
      2:点击Button,会弹出提示框,提示框内容为TextBox的值;
   现在,测试要求如下
      1:在300台机器上运行上面的程序;
      2:到这300台机器上去点击这个Button,看看上文中的功能2有没有实现;
   很显然,实际情况中没有这么简单的程序,实际的情况有可能是点击Button,统一下载一个文件,而测试的要求可能就变为考核服务器的负载。现在,测试部显然也没有300个人坐在客户机上验证测试的结果,这个时候,就需要我们提供一个自动化的测试工具,来完成必要的测试任务。
   测试工具,首先也是一个C#的程序,它的主要目的是:
      1:获取上文应用程序的窗口句柄,继而获取TextBox句柄及Button句柄;
      2:为TextBox随机填入一些字符;
      3:模拟点击Button;
   EnumChildWindows介绍
   在这里需要介绍下EnumChildWindows,
   EnumChildWindows可是个好东西,可以枚举一个父窗口的所有子窗口:

BOOL EnumChildWindows(
&\mr"V!az ]0 HWNDhWndParent, // handle to parent window // 父窗口句柄ITPUB个人空间fo9SZ#P
WNDENUMPROClpEnumFunc, // callback function // 回调函数的地址ITPUB个人空间4]1ul F-c2i
LPARAMlParam // application-defined value // 你自已定义的参数ITPUB个人空间^.r/s)] Ajb3V+e
);

   就这么简单,让我们再定义一个回调函数,像下面这样:
   BOOL CALLBACK EnumChildProc(ITPUB个人空间m4V/?XiKb'h~
HWNDhwnd, // handle to child window
\-@Lc4[ q}eP*L0 LPARAMlParam // application-defined value
O%^{^$W2\R)I fW"i0);
   在调用EnumChildWindows 这个函数时,直到调用到最个一个子窗口被枚举或回调函数返回一个false,否则将一直枚举下去。

简单例子的主要源码
    测试工具的主要代码如下:

privatevoidbutton1_Click(objectsender, EventArgs e)
{//获取测试程序的窗体句柄IntPtr mainWnd = FindWindow(null, "FormLogin");
List<IntPtr> listWnd =newList<IntPtr>();//获取窗体上OK按钮的句柄IntPtr hwnd_button = FindWindowEx(mainWnd,newIntPtr(0),null, "OK");//获取窗体上所有控件的句柄EnumChildWindows(mainWnd,newCallBack(delegate(IntPtr hwnd,intlParam)
{
listWnd.Add(hwnd);returntrue;
}), 0);foreach(IntPtr iteminlistWnd)
{if(item != hwnd_button)
{char[] UserChar = "luminji".ToCharArray();foreach(charchinUserChar)
{
SendChar(item, ch, 100);
}
}
}
SendMessage(hwnd_button, WM_CLICK, mainWnd, "0");
}publicvoidSendChar(IntPtr hand,charch,intSleepTime)
{
PostMessage(hand, WM_CHAR, ch, 0);
System.Threading.Thread.Sleep(SleepTime);
}publicstaticintWM_CHAR = 0x102;publicstaticintWM_CLICK = 0x00F5;

[DllImport("User32.dll", EntryPoint = "SendMessage")]publicstaticexternintSendMessage(IntPtr hWnd,intMsg, IntPtr wParam,stringlParam);

[DllImport("user32.dll")]publicstaticexternIntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,stringlpszClass,stringlpszWindow);

[DllImport("user32.dll", SetLastError =true)]publicstaticexternIntPtr FindWindow(stringlpClassName,stringlpWindowName);

[DllImport("user32.dll")]publicstaticexternintAnyPopup();

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError =true)]publicstaticexternintGetWindowText(IntPtr hWnd, StringBuilder lpString,intnMaxCount);

[DllImport("user32.dll")]publicstaticexternintEnumThreadWindows(IntPtr dwThreadId, CallBack lpfn,intlParam);

[DllImport("user32.dll")]publicstaticexternintEnumChildWindows(IntPtr hWndParent, CallBack lpfn,intlParam);

[DllImport("user32.dll", CharSet = CharSet.Ansi)]publicstaticexternIntPtr PostMessage(IntPtr hwnd,intwMsg,intwParam,intlParam);

[DllImport("user32.dll", CharSet = CharSet.Ansi)]publicstaticexternIntPtr SendMessage(IntPtr hwnd,intwMsg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]publicstaticexternIntPtr SendMessageA(IntPtr hwnd,intwMsg,intwParam,intlParam);

[DllImport("user32.dll", CharSet = CharSet.Auto)]staticexternintGetClassName(IntPtr hWnd, StringBuilder lpClassName,intnMaxCount);

[DllImport("user32.dll", SetLastError =true, CharSet = CharSet.Auto)]publicstaticexternintGetWindowTextLength(IntPtr hWnd);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError =false)]publicstaticexternIntPtr GetParent(IntPtr hWnd);publicdelegateboolCallBack(IntPtr hwnd,intlParam);

运行效果:

难点:如何获取指定的控件句柄
细心的人可能已经发现,上文中,给文本框赋值的地方,使用了如下代码:

foreach(IntPtr iteminlistWnd)
{if(item != hwnd_button)
{char[] UserChar = "luminji".ToCharArray();foreach(charchinUserChar)
{
SendChar(item, ch, 100);
}
}
} 假设我们的窗体上有多个文本框,那么事实上,这段代码会给所有的文本框输入"luminji”字样。这在多数应用程序中都是不允许的,我们需要精确定位需要控制的控件。

   我们在得到OK按钮的句柄的时候,使用了函数:
   IntPtr hwnd_button = FindWindowEx(mainWnd,newIntPtr(0),null, "OK"); 而想要获取文本框句柄的时候,这个函数却不能使用,因为,所有文本框都是没有标题的,也就是类似"OK"这个值。有人说,那就使用控件ID吧。且看:

获取控件ID
   非.NET程序,一旦程序被生成,控件ID就是固定的,所以这一招,用在非.NET程序中,那是再好也不过了。

   根据ID来得到控件句柄的函数声明如下:
   [DllImport("user32.dll", EntryPoint = "GetDlgItem")]publicstaticexternIntPtr GetDlgItem( IntPtr hParent,intnIDParentItem); 其中,第一个参数就是窗体的句柄,第二个参数就是控件ID。

但是,显然,这种方法不适用于我们的.NET程序,因为我们会发现,我们的.NET程序没运行一次,这个ID是变化的。

获取控件位置
   所以,最终的一个方案是:根据控件位置,人工比对后得到我们想要的控件句柄。该函数的声明如下:

好了,现在的关键就是怎么取得这个控件的位置。我们在VS中查看,某个控件有X坐标和Y坐标,以上面程序的这个TextBox来说,其在VS中显示的位置是“70,83”,但是而VS中显示的,是不包含标题和边框的坐标值。但是这个坐标值可以作为我们人工比对的参考。更精确的坐标值,我们写代码来实现,如下:EnumChildWindows(mainWnd,newCallBack(delegate(IntPtr hwnd,intlParam)
{
listWnd.Add(hwnd);
StringBuilder className =newStringBuilder(126);
StringBuilder title =newStringBuilder(200);
GetWindowText(hwnd, title, 200);
RECT clientRect;
GetClientRect(hwnd,outclientRect);intcontrolWidth = clientRect.Width;intcontrolHeight = clientRect.Height;intx = 0, y = 0;
IntPtr parerntHandle = GetParent(hwnd);if(parerntHandle != IntPtr.Zero)
{
GetWindowRect(hwnd,outclientRect);
RECT rect;
GetWindowRect(parerntHandle,outrect);
x = clientRect.X - rect.X;
y = clientRect.Y - rect.Y;
Debug.Print(x.ToString());
Debug.Print(y.ToString());
}returntrue;
}), 0); 注意,上面代码中的X和Y就是某个控件的精确的X和Y值,记录下来,比对一下,我们就能得到精确的坐标值了。在上文的例子中,我们的文本框的坐标最终得到为“78,113”。
有了这个坐标值,我们便知道这个控件的句柄,也就是hwnd是属于哪个控件的了。

   根据EnumChildWindows枚举次序得到句柄
   如果你不想这么麻烦,还有一种简单的方案,那就是利用EnumChildWindows的枚举顺序。要知道,在不同的机器上,EnumChildWindows枚举一个窗体上子控件的顺序是相同的,也就是说,如果有两个文本框,它们在这台机器上被枚举的顺序一个是2,一个是3,那么,它们在其它机器上被枚举的顺序,也是这个固定次序。通过比对,我们也能得到它们各自的句柄。当然,如果我们有了这些句柄,还有什么是不能做到的呢?

使用SPY++
   SPY++是微软的一个工具,用户获取窗体上的ID或者类型或者句柄等信息。因为在我们的这个例子里,ID和句柄在每台机器上都是不变的,所以这个工具对于我们来说,没有多大的用处。但是,当你HACK别人的程序的时候,它会发挥一定作用。



LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   


性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术


某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...   
 
 
 
 
 
 
 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号