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

简介: C#借助API实现黑盒自动化测试工具的编写 本文代码下载(VS2010开发):http://download.csdn.net/source/2796362 本文摘要: 1:一个简单的例子        1.1:EnumChildWindows介绍    1.2:主要源码 2:难点:如何获取指定的控件句柄    2.1:使用SPY++    2.2:获取控件位置    2.3:获取控件ID 1:一个简单的例子        在日常编码过程中,我们常常会进行自动化测试。

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

本文代码下载(VS2010开发):http://download.csdn.net/source/2796362

本文摘要:

1:一个简单的例子    

   1.1:EnumChildWindows介绍

   1.2:主要源码

2:难点:如何获取指定的控件句柄

   2.1:使用SPY++

   2.2:获取控件位置

   2.3:获取控件ID

1:一个简单的例子  

     在日常编码过程中,我们常常会进行自动化测试。这里的自动化测试不是指单元测试,而是模拟人工输入来进行快速的、高并发的测试。可以使用的自动化工具有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;

1.1:EnumChildWindows介绍

   在这里需要介绍下EnumChildWindows,

EnumChildWindows可是个好东西,可以枚举一个父窗口的所有子窗口:

BOOL EnumChildWindows(
  HWND hWndParent,         // handle to parent window // 父窗口句柄
  WNDENUMPROC lpEnumFunc,  // callback function // 回调函数的地址
  LPARAM lParam            // application-defined value // 你自已定义的参数
);

    就这么简单,让我们再定义一个回调函数,像下面这样:

BOOL CALLBACK EnumChildProc(
  HWND hwnd,      // handle to child window
  LPARAM lParam   // application-defined value
);

    在调用EnumChildWindows 这个函数时,直到调用到最个一个子窗口被枚举或回调函数返回一个false,否则将一直枚举下去。

1.2:简单例子的主要源码

    测试工具的主要代码如下:

        private void button1_Click(object sender, EventArgs e)
        {
            //获取测试程序的窗体句柄
            IntPtr mainWnd = FindWindow(null, "FormLogin");
            List<IntPtr> listWnd = new List<IntPtr>();
            //获取窗体上OK按钮的句柄 
            IntPtr hwnd_button = FindWindowEx(mainWnd, new IntPtr(0), null, "OK");
            //获取窗体上所有控件的句柄
            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 FindWindowEx(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);

运行效果:

image

2:难点:如何获取指定的控件句柄

   细心的人可能已经发现,上文中,给文本框赋值的地方,使用了如下代码:

            foreach (IntPtr item in listWnd)
            {
                if (item != hwnd_button)
                {
                    char[] UserChar = "luminji".ToCharArray();
                    foreach (char ch in UserChar)
                    {
                        SendChar(item, ch, 100);
                    }
                }
            }

   假设我们的窗体上有多个文本框,那么事实上,这段代码会给所有的文本框输入"luminji”字样。这在多数应用程序中都是不允许的,我们需要精确定位需要控制的控件。

   我们在得到OK按钮的句柄的时候,使用了函数:

IntPtr hwnd_button = FindWindowEx(mainWnd, new IntPtr(0), null, "OK");

   而想要获取文本框句柄的时候,这个函数却不能使用,因为,所有文本框都是没有标题的,也就是类似"OK"这个值。有人说,那就使用控件ID吧。且看:

2.1:获取控件ID

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

   image

   根据ID来得到控件句柄的函数声明如下:

        [DllImport("user32.dll ", EntryPoint = "GetDlgItem")]
        public static extern IntPtr GetDlgItem( IntPtr hParent, int nIDParentItem); 

   其中,第一个参数就是窗体的句柄,第二个参数就是控件ID。

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

2.2:获取控件位置

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

   好了,现在的关键就是怎么取得这个控件的位置。我们在VS中查看,某个控件有X坐标和Y坐标,以上面程序的这个TextBox来说,其在VS中显示的位置是“70,83”,但是而VS中显示的,是不包含标题和边框的坐标值。但是这个坐标值可以作为我们人工比对的参考。 

   更精确的坐标值,我们写代码来实现,如下:

            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);
    注意,上面代码中的X和Y就是某个控件的精确的X和Y值,记录下来,比对一下,我们就能得到精确的坐标值了。在上文的例子中,我们的文本框的坐标最终得到为“78,113”。
    有了这个坐标值,我们便知道这个控件的句柄,也就是hwnd是属于哪个控件的了。
 

2.3:根据EnumChildWindows枚举次序得到句柄

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

2.4:使用SPY++

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

    image

微信扫一扫,关注最课程(www.zuikc.com),获取更多我的文章,获取软件开发每日一练

参考:

1:http://book.21www.cn/info/vb/api/4076.html

2:http://dev.firnow.com/course/3_program/cshapo/csharpjs/20100714/441439.html

Creative Commons License本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 http://www.cnblogs.com/luminji(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
目录
相关文章
|
2月前
|
自然语言处理 C# Windows
C#开源免费的Windows右键菜单管理工具
C#开源免费的Windows右键菜单管理工具
|
2月前
|
JSON 关系型数据库 测试技术
Eolink神技之五、API自动化——定时任务
Eolink神技之五、API自动化——定时任务
44 0
|
18天前
|
监控 测试技术 API
api管理工具的新发现
该内容介绍了两款强大的API管理工具——Apifox和Eolink。Apifox集成了Postman、Swagger、Mock和JMeter的功能,提供一体化的API协作平台,提高开发、测试效率。Eolink则包括API设计、自动化测试和团队协作的apikit,以及集成第三方API的apispace。两者都提供便捷的官网和丰富的资源,值得一试。
27 0
|
1月前
|
安全 测试技术 API
|
1月前
|
JSON 测试技术 API
Postman Newman 实现 API 自动化测试的快速指南
Newman 是一款专为 Postman 打造的命令行工具,旨在通过自动运行 Postman 集合和环境,实现 API 测试的自动化。它使得开发者无需打开 Postman 图形界面,即可直接在命令行中执行测试用例。
|
1月前
|
缓存 负载均衡 Java
Python实现API接口并发测试
Python实现API接口并发测试
57 0
|
1月前
|
架构师 前端开发 测试技术
|
2月前
|
存储 关系型数据库 API
Python 任务自动化工具:nox 的配置与 API
Python 任务自动化工具:nox 的配置与 API
22 0
|
2月前
|
JSON JavaScript 前端开发
提升 API 测试效率:Postman Tests 详解
Postman 不仅是一个强大的 API 开发工具,它还提供了创建自动化测试脚本的能力,这些脚本可以用于检验API请求得到的响应是否符合预期。这些测试脚本被称为 “Tests”,支持使用 JavaScript 编程语言进行编写,并且 Postman 提供了一系列的断言库来帮助你检查包括但不限于状态码、响应内容以及响应时间在内的响应数据。
|
2月前
|
弹性计算 前端开发 Java
通义千问API:让大模型写代码和跑代码
基于前面三章的铺垫,本章我们将展示大模型Agent的强大能力。我们不仅要实现让大模型同时使用多种查询工具,还要实现让大模型能查询天气情况,最后让大模型自己写代码来查询天气情况。
59636 448
通义千问API:让大模型写代码和跑代码