首先解释下什么是TA,TA也是test automation, 即自动测试,也是用机器来模拟人操作软件,然后测试操作结果是否按预想的流程进行。 公司产品的自动测试覆盖率一直上不去,很大的原因的是一些技术问题,因为自动测试工具是一个独立的进程,他很难知道被测试程序当时的UI情况。对于一些系统标准控件,可以通过控件窗口句柄和系统API来查询和操作,但是我们没法知道自绘控件上的文字信息,还有没法知道弹出菜单的内容, 还有弹出Tooltip的内容等。

  那么我们能不能找到一个通用的方法来获取所有窗口上的文字信息呢,不管它是自绘控件还是系统控?这时我们想到了API Hook,因为窗口上的文字信息,终肯定都是通过Windows的API画上去的,我们只要HooK了这些API,能得到所有画在窗口上的文字信息(包括位置,颜色,字体等)。其实词霸的屏幕取词是使用这种技术。

  现在考虑如何用API Hook来实现这种屏幕取词技术。这里我们使用<<Windows核心编程>>里remoteThread技术将我们的Detector.dll注入到目标进程,为了方便TA工具调用,我们同时封装一个TAUtil.dll,总的框架如下:

  从图上可以看到我们测试工具进程可以同时和多个目标进程通讯,也是可以获取任意进程窗口的文字信息。

  这里涉及到进程间通讯技术,一般来说高效的是用内存映射文件,但是这里我们为了简单,采用WM_COPYDATA消息,用WM_COPYDATA涉及到窗口,显然,我们这里的通讯窗口也应该采用一对多的形式,TAUtil.dll内有一个隐藏的主通讯窗口,另外每个目标进程的Detector.dll内有一个隐藏的辅助通讯窗口,主通讯窗口和辅助通讯窗口之间通过WM_COPYDATA通讯。

  下面说下大概的流程:

  1、TA Tool加载TAUtil.dll, TAUtil.dll创建主通讯窗口

  2、TAUtil.dll在目标进程中创建RemoteThread, 目标进程加载Detector.dll, Detector.dll在Attatch时创建辅助通讯窗口

  3、TAUtil.dll发消息给Detector.dll请求获取目标窗口文字信息,然后等待

  4、Detector.dll Hook 绘画文字的API,然后请求目标窗口重画窗口,重画结束后卸载Hook,然后Detector.dll再将截获到的文字发回给TAUtil.dll

  5、TAUtil.dll收到文字信息后继续执行.

  这里有几点要注意,一是主通讯窗口和辅助通讯窗口都要单独的线程中运行,不然会阻塞主线程; 二是如何判断文字是画在目标窗口上的,我们可以通过WindowFromDC来判断,但是对于内存DC,调用这个API时他会返回NULL, 这时我们要跟踪所有DC拷贝的API,这样才能判断终文字是不是画到了我们的目标窗口上. 如果我们要知道文字的绘画位置,还要跟踪DC拷贝的相对位置。windows绘画文字的API包括DrawTextA, DrawTextW, DrawTextExA, DrawTextExW, ExtTextOutA, ExtTextOutW, TabbedTextOutA, TabbedTextOutW, PolyTextOutA, PolyTextOutW, TextOutA, TextOutW, DC拷贝的API包括BitBlt,TransparentBlt,PatBlt,StretchBlt等.

  上面是屏幕取词的实现原理, 接下来我们考虑如何操作目标进程菜单?

  在考虑这个问题之前,我们先要知道Windows内部的对象类型, Windows的内部的对象类型分为GDI Object, User Object, Kernel Object, GDI Object包括Bitmap, Brush, DC, Pen 等,这些都只在该进程内有效; User Object包括HWND,HMENU等,这些对象是跨进程的,任何进程只要知道这个句柄值可以操作它; Kernel Object 是系统共享的,包括进程句柄,线程句柄,Mutex,Event等,进程只要有权限,进程内核对象表中有该项,可以访问。

  显然,对于菜单,因为他是属于用户对象,外部进程只要得到它的HMENU可以通过菜单相关的API来操作了。那么接下来我们的问题是如何得到菜单句柄了. 方法同样是API Hook ,我们只要Hook 系统API TrackPopupMenu和TrackPopupMenuEx可以了, 大概流程是:

  1、安装菜单相关的API Hook

  2、模拟鼠标点击淡出菜单

  3、得到Hook 到的菜单句柄,卸载Hook

  4、通过菜单相关API查询菜单内容,操作菜单

  综上,API Hook技术可以在自动化测试时可以实现我们平时测试时做不到的事情,通过目标进程的窗口层次和该技术结合,基本上可以将自动测试覆盖率达到85%以上, 不能达到是因为有一部分UI信息是通过图片来表现,这个涉及到图像识别了。