复制粘贴不了是怎么回事?(有时候复制粘贴不了怎么回事)
878
2022-05-30
Windows的图形界面架构
window图像渲染的基本流程
window桌面程序UI自动化测试技术
Win32程序
WPF程序
UIAutomation
Windows的图形界面架构
window图像渲染的基本流程
window桌面程序UI自动化测试技术
Win32程序
WPF程序
UIAutomation
桌面程序图像渲染性能测试实践
普通PC桌面WPF应用
大屏幕可视化WPF应用
Windows的图形界面架构
从Windows Vista之后,Desktop composition的部分就由Desktop Window Manager完成了(当然是启用Aero的情况下,Windows 8起DWM是必须开启的
如上图,应用程序画完了界面,告诉DWM把它放到桌面上去
DWM本身是基于Direct3D的,D3D下面是WDDM驱动
至于应用程序,绝大多数win桌面应用都是基于GDI的,很老的图形库 (从某个版本起GDI也是跑在D3D之上了,于是显卡厂家就不用写GDI驱动了),D3D(比如基于WPF的应用,今天主要介绍的应用),OpenGL(现在的Windows的图形架构是以DirectX为主,OpenGL支持需要OpenGL installable client driver)
window图像渲染的基本流程
从程序中提交一个Draw,数据需要经过:
A
p
p
−
>
D
X
r
u
n
t
i
m
e
−
>
U
s
e
r
m
o
d
e
d
r
i
v
e
r
−
>
d
x
g
k
r
n
l
−
>
K
e
r
n
e
l
m
o
d
e
d
r
i
v
e
r
−
>
G
P
U
App->DX runtime->User mode driver->dxgkrnl->Kernel mode driver->GPU
App−>DXruntime−>Usermodedriver−>dxgkrnl−>Kernelmodedriver−>GPU
在到达GPU之前,全都是在CPU上执行的,所以从程序本身是无法获取渲染结果
到这里就为我们做window桌面程序图像渲染性能测试带来两个问题:
怎么检查图像渲染的质量?
怎么获取图像渲染的响应时间?
由于需要桌面UI自动化测试的技术,所以下面我们介绍window桌面程序UI自动化测试技术
window桌面程序UI自动化测试技术
Win32程序
使用 Win32 API 来创建的程序成为Win32程序。
提供 Win32 API的dll被加载到应用程序的进程中,应用程序通过这些API来创建线程、窗口和控件。
Win32程序中,所有窗口和控件都是一个窗口类的实例,都拥有一个窗口句柄,窗口对象属于内核对象,由Windows子系统来维护。
Windows子系统为标准控件定义了窗口类,并使用GDI来绘制这些标准控件。
Win32程序采用消息循环机制:
WPF程序
WPF的控件不再是通过Win32 API来创建窗口,使用Win32 API并不能查找和操作WPF控件
WPF所有控件和动画都是使用DirectX 绘制
WPF控件不直接支持MSAA,而是通过 UIA 用桥转换技术来支持MSAA
WPF用AutomationPeer类支持自动化,每一种控件都有对应的 AutomationPeer类。AutomationPeer不直接暴露给测试客户端,而是通过UIA来使用。UIA向应用程序窗口发送WM_GetObject消息,获得由AutomationPeer实现的UIA Server端Provider。AutomationPeer由控件创建(OnCreateAutomationPeer)
UIAutomation
UIAutomation是微软从Windows Vista开始推出的一套全新UI自动化测试技术, 简称UIA。
UIA定义了全新的、针对UI自动化的接口和模式。测试程序可以通过这些接口来查找和操作控件。
遍历和条件化查询:TreeWalker/FindAll
UI元素属性的UIA Property, 包括Name、 ID、Type、ClassName、Location、 Visibility等等。
UIA Pattern(控件的行为模式), 比如Select、Expand、Resize、 Check、Value等等。
UIA的两种实现方法:
Server-Side Provider:
由被测程序实现UIA定义的接口,返回给测试程序。WPF程序通过这种方式来支持UIA。
Client-Side Provider:
测试程序没有实现UIA定义的接口。由UIA Runtime或测试程序自己来实现。比如Win32和WinForm程序,UIA Runtime通过MSAA来实现UIA定义的接口。UIA定义了全新的、针对UI自动化的接口和模式。测试程序可以通过这些接口来查找和操作控件。
遍历和条件化查询:TreeWalker/FindAll
UI元素属性的UIA Property, 包括Name、 ID、Type、ClassName、Location、 Visibility等等。
UIA Pattern(控件的行为模式), 比如Select、Expand、Resize、 Check、Value等等。
UIA驱动计算器示例:
using System.Windows.Automation; PropertyCondition conditionName = new PropertyCondition(AutomationElement.NameProperty, "计算器"); AutomationElement calcWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, conditionName); //Button 1 PropertyCondition conditionBtn1 = new PropertyCondition(AutomationElement.AutomationIdProperty, "131"); AutomationElement button1 = calcWindow.FindFirst(TreeScope.Descendants, conditionBtn1); //点击Button1 InvokePattern invokePatternBtn1 = (InvokePattern)button1.GetCurrentPattern(InvokePattern.Pattern); invokePatternBtn1.Invoke();
桌面程序图像渲染性能测试实践
因为我们的性能测试是基于部分UI自动化测试技术落地的,在此介绍一下我们的UI自动化测试解决方案
测试解决方案应至少包括5个项目,其中前两个是和其他测试解决方案共享的。5个项目均为类库,不能直接执行。
AI.Robot为UI驱动框架。 AI.Utilities项目里是一些辅助类,如数据库读写、图片对比等(性能测试需用到)。
AI.App.UIObjects项目里放置UI对象。把UI对象集中放置到此项目中是为了减少界面更改带来的维护工作量。
AI.App.BusinessLogic项目里放置可重复用到的界面元素操作的集合,通常是为了完成一项特定的业务的步骤的集合。
AI.App.TestCases里放置测试用例。并按照MSTest单元测试框架组织测试类和测试方法。包含测试类和测试方法的.net类库称为测试程序集。
今天讨论的桌面程序图像渲染性能测试主要应用于以下两种应用:
普通PC桌面WPF应用(分辨率<2K)
大屏幕可视化WPF应用(分辨率>8K)
普通PC桌面WPF应用
首先,回到之前的两个问题:
怎么检查图像渲染的质量?
怎么获取图像渲染的响应时间?
首先将正常渲染完的控件输出成图片
// 将控件uiElement输出到图片aa.bmp uiElement.CaptureBitmap(@"D:\aa.bmp");
使用测试工具驱动启动被测应用并开始计时,在渲染过程中快速截图,实时比较两幅图片是否完全相等,如果相等并结束计时并写入响应时间。
// 比较两幅图片是否完全相同(所有像素点都相同) bool isEqual = ImageHelper.IsEqual(@"D:\image1.bmp", @"D:\image2.bmp");
判断两幅图是否完全相同
///
影响图片输出的因素:
1.显卡,不同显卡输出文字和渐变色的时候有细微的差别,所以不同机器上显示的控件和输出的图片通常不完全相同,特别是当控件上有文字的时候。
2.DPI设置,将机器的DPI设置为120%时,100x100大小的控件将显示为120x120像素
3.当在远程桌面上运行测试时,远程连接的选项“字体平滑”会影响控件显示和输出的图片
大屏幕可视化WPF应用
由于大屏幕的分辨率8K起步,也就不适应上面的截图判断方法了,为什么呢?
我们简单来计算8K图片的大小吧
分辨率:
7680
×
4320
=
33177600
像素
≈
95
M
B
分辨率:7680×4320=33177600像素≈95MB
分辨率:7680×4320=33177600像素≈95MB
我们常见显示器用256种状态标识屏幕上某种颜色的灰度,而屏幕采用三基色红绿蓝(RGB),不压缩的情况下一个像素需要占用24bit(3字节),这个就是常说的24位真彩色。
近100MB的图片实时截图并进行判断,本身两个动作就会对机器的计算资源消耗巨大,会严重影响性能测试准确性。
这里我们折中使用实时判断标志位RGB像素点的方法来判断图片渲染的结果
首先,我们会使用取色器采样几个最后图像渲染完成的坐标像素点RGB值
原理其实很简单,只需要两步:
鼠标移动的时候获取鼠标光标的位置
鼠标单击获取当前鼠标光标的位置的RGB颜色值到粘贴板
涉及HookManager技术
namespace GetColor { public partial class Form1 : Form { //显示设备上下文环境的句柄 private IntPtr hdc = IntPtr.Zero; private int maxY, maxX; public Form1() { InitializeComponent(); this.BackColor = Color.Brown; this.TransparencyKey = Color.Brown; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; this.WindowState = FormWindowState.Maximized; this.TopMost = true; this.Cursor = Cursors.Cross; HookManager.MouseMove += OnMouseMove; this.KeyPress += OnKeyPress; this.MouseClick += OnMouseDown; Size screenSize = Screen.PrimaryScreen.Bounds.Size; this.maxX = screenSize.Width - this.label1.Width; this.maxY = screenSize.Height - this.label1.Height; this.SetLabel(Control.MousePosition.X, Control.MousePosition.Y); } //鼠标移动,实时获取鼠标位置 private void OnMouseMove(object sender, MouseEventArgs e) { this.SetLabel(Control.MousePosition.X, Control.MousePosition.Y); } private void SetLabel(int x, int y) { if(this.hdc == IntPtr.Zero) { this.hdc = GetDC(IntPtr.Zero); this.label1.Location = new Point(Math.Min(this.maxX, x + 10), Math.Min(this.maxY, y + 10)); int color = GetPixel(this.hdc, x, y); this.label1.Text = string.Format("X={0},Y={1}\r\nColor:#{2}\r\n{3}", x, y, Convert.ToString(color, 16).PadLeft(6, '0'), color); this.Update(); ReleaseDC(IntPtr.Zero, this.hdc); DeleteDC(this.hdc); this.hdc = IntPtr.Zero; } } private void OnKeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Escape) { UnHook(); Application.Exit(); } } private void OnMouseDown(object sender, MouseEventArgs e) { //检索一指定窗口的客户区或整个屏幕的显示设备上下文环境的句柄 this.hdc = GetDC(IntPtr.Zero); //指定坐标点的像素的RGB颜色值。 int color = GetPixel(this.hdc, e.X, e.Y); //鼠标单击拷贝值 if (e.Button == MouseButtons.Left) { Clipboard.SetText(string.Format("
小程序截图:
把图像渲染结果采样点填入测试工具的XML配置文件后,我们使用测试工具启动程序开始计时并实判断采样标志位像素点的RGB值,如果全部通过结束计时并写入渲染响应时间
public void ValidateStage(Screen screen) { bool allReady = false; foreach (var stage in screen.ValidationStages) { stage.IsReady = false; } DateTime now = DateTime.Now; while (!allReady && (DateTime.Now - now).TotalSeconds < this.Config.Timeout) { allReady = true; foreach (var stage in screen.ValidationStages) { if(!stage.IsReady) { stage.IsReady = this.ValidatePointsOneTime(stage.ValidatePoints); if (stage.IsReady) { TimeSpan cost = DateTime.Now - now; this.logger.LogInfo(string.Format("{0}耗时{1}", stage.Name, cost.TotalSeconds)); this.logData += string.Format(",{0}", cost.TotalSeconds); } else { allReady = false; } } } Thread.Sleep(this.Config.TryInterval); } foreach (var stage in screen.ValidationStages) { if(!stage.IsReady) { foreach (ValidatePoint point in stage.ValidatePoints) { int color = Root.GetPointColor(point.X, point.Y); this.logger.LogInfo(string.Format("点({0}, {1})的颜色为{2}", point.X, point.Y, color)); } this.logger.LogInfo(string.Format("{0}秒内{1}未绘制完", this.Config.Timeout.ToString(), stage.Name)); this.logData += string.Format(",>{0}", this.Config.Timeout); } } } public void ValidateStage(Screen screen, ValidationStage stage) { DateTime now = DateTime.Now; bool screenReady = this.ValidatePoints(stage.ValidatePoints); if (screenReady) { TimeSpan cost = DateTime.Now - now; this.logger.LogInfo(string.Format("{0}耗时{1}", stage.Name, cost.TotalSeconds)); this.logData += string.Format(",{0}", cost.TotalSeconds); } else { this.logger.LogInfo(string.Format("{0}秒内{1}未绘制完", this.Config.Timeout.ToString(), stage.Name)); this.logData += string.Format(",>{0}", this.Config.Timeout); } } public bool ValidatePointsOneTime(List
实际效果:
参考资料:
[1]:Windows Display Driver Model (WDDM) Architecture (Windows Drivers)
[2]:The Desktop Window Manager (Windows)
API GUI Windows 云性能测试服务 CPTS 渲染
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。