VS2010测试功能之旅

——编码的UI测试系列之六:提高UI测试稳定性的8个方法

RealZhao2011511

回顾

近段时间各种忙缠身,加上自己平时比较懒,以至于博客一个月余没有更新。

最近终于忙完了,再回来博客看看,发现该系列文章还有4个章节未完成。为了实现当时构想,决定还是坚持把这个系列写完,不能让它TJ了。

对那些经常在博客园坚持写博客的园友们,我想说两个字:佩服!确实,一篇博客不是那么容易写的啊~~以前看博客觉得不腰疼,真正写的时候发现还是挺费力气的~~~

OK,转入正题,这次带来的是:提高UI测试稳定性的8个方法。

 

理想与现实

我们的测试,经常是在理想的情况下录制的。

在代码当中,我们操作了一个控件,接下来又是另一个动作。在一个稳定的情况下,自然能执行通过。假如说,您录制或编写代码时,网速优良,而且机器配置也非常威猛,这是好事,但在实际使用时,您的网速时好时坏,您的机器也可能是老爷机,这种情况下,测试代码还能执行通过吗

举一个例子:

这里有一个查询模块,我们接下来的操作是,点击查询,然后获取他的查询出的行数,从界面上可以看到将会查询出5行数据

然后这个是我们的代码

1 int userCount=0;
2 
3 WinButton uI查询Button = this.UI查询用户Window.UI查询Window.UI查询Button;
4 
5 Mouse.Click(uI查询Button, new Point(11));//Click '查询' button
6 
7 userCount = this.UI查询用户Window.UIUserGridViewWindow.UIDataGridViewTable.Rows.Count;

 

大家仔细想想?这几句代码难道就没有问题?

我们知道,其实代码是会一行一行执行的,中途没有等待时间,假如这个时候我们网速有点卡,查询出来要等5秒,但是,我们的代码可不会等待,点击“查询”后,他会立即判断这个UIDataGridViewTable有多少行,然后赋值给userCount(但这个时候行数还为0,不是查询完之后的5),于是我们就得到了错误的结果。

这个问题如何解决呢?

 

拙劣的实现

为了让我们的测试代码能够等待程序查询完了再进行统计,我们通常的做法如下所示,注意,这是一种非常拙劣的做法:

1 int userCount=0;
2 
3 Mouse.Click(uI查询Button, new Point(11));//点击 '查询' 按钮
4 
5 Playback.Wait(5000);//也可以用Thread.Sleep(5000),等待5秒
6 
7 userCount = this.UI查询用户Window.UIUserGridViewWindow.UIDataGridViewTable.Rows.Count;//统计行数

 

为什么说他拙劣?因为这样非常死板,如果等待时间不够,可能没有等上一步完成就执行下一步,造成错误,如果等待过久,又有损测试效率。

假设我们的网速进一步恶化,现在要等个10-20秒才能够查询出数据,这个时候,为了提高代码的稳定性,难道我们这样去写??

1 Playback.Wait(20000);//也可以用Thread.Sleep(20000),等待20秒

 

假设我们这个时候网速恢复了,只需要1秒就可以查询出数据,但是这个时候,我们代码中,写死了要等20秒,结果会出现19秒毫无意义等待,反而造成了网速不卡测试卡的状况。

 

大家也明白为什么说他拙劣了吧。

 

最佳实现

现在,我们开始想要一些比较智能的方法:当条件没有达到时一直等待,而当条件达到后,就自动结束等待。

有没有这样的方法呢?答案是:YES

在UI测试中,微软为我们默认提供了这8种方法,来提高测试的稳定性(注:由于MSDN官网上的中文翻译有问题,为了不让大家误解这些方法的意思,这里还是贴出英文原文,看的懂的看,看不懂可以跳过直接看本文下面的使用方法介绍)

UITestControl.WaitForControlXXX() methods

Description

WaitForControlReady

Waits for the control to be ready to accept mouse and keyboard input. The engine implicitly calls this API for all actions to wait for the control to be ready before doing any operation. However, in certain esoteric scenario, you may have to do explicit call.

WaitForControlEnabled

Waits for the control to be enabled when the wizard is doing some asynchronous validation of the input by making calls to the server. For example, you can method to wait for the Next button of the wizard to be enabled (). For an example of this method, see Walkthrough: Creating, Editing and Maintaining a Coded UI Test.

WaitForControlExist

Waits for the control to appear on the UI. For example, you are expecting an error dialog after the application has done the validation of the parameters. The time taken for validation is variable. You can use this method to wait for the error dialog box.

WaitForControlNotExist

Waits for the control to disappear from the UI. For example, you can wait for the progress bar to disappear.

WaitForControlPropertyEqual(String, Object)

Waits for the specified property of the control to have the given value. For example, you wait for the status text to change to Done.

WaitForControlPropertyNotEqual(String, Object)

Waits for the specified property of the control to have the opposite of a specified value. For example, you wait for the edit box to be not read-only, that is, editable.

WaitForControlCondition(Predicate(Of UITestControl))

Waits for the specified predicate returns to be true. This can be used for complex wait operation (like OR conditions) on a given control. For example, you can wait until the status text is Succeeded or Failed as shown in the following code:

WaitForCondition(Of T)(T, Predicate(Of T))

All the previous methods are instance methods of UITestControl. This method is a static method. This method also waits for the specified predicate to be true but it can be used for complex wait operation (like OR conditions) on multiple controls. For example, you can wait until the status text is Succeeded or until an error message appears, as shown in the following code:

 

如何使用

这些方法,其实无论是测试Winform,还是测试Web,WPF,Silverlight,都可以使用,因为Winform理解起来最简单,所以这里以Winform程序为例进行说明。

 

一.WaitForControlExist()

作用:等待某一个控件,直到它出现为止。

返回值:bool类型值,控件是否在规定时间内出现

典型的使用场景:测试Web程序,当我们点击某一个链接,我们需要等待某一个页面出现。又如测试Winform程序,我们点击“提交”按钮,然后等待“提交成功!”弹出框出现。或者用于一些动态生成的控件。

举例:

Act.1输入账号密码,点击登陆

Act.2 这个时候通过网络会送您的登录信息,确认无误后弹出系统主窗口,然后我们想点击“用户管理”->“添加用户”

未优化代码:

1 UI用户名Edit.Text= "Admin";
2 
3 UI密码Edit.Text="123456";
4 
5 Mouse.Click(UI登陆Button, new Point(1 , 1));
6 
7 Mouse.Click(UI用户管理MenuItem, new Point(1 , 1));
8 
9 Mouse.Click(UI添加用户MenuItem, new Point(1 , 1));

可能造成的问题:

在Mouse.Click(UI登陆Button, new Point(1 , 1))之后就直接Mouse.Click(UI用户管理MenuItem, new point(1 , 1))了,但是这个时候,可能因为网速或服务器处理速度,信息还没返回,这个时候还在登录界面,系统主窗口还未弹出,所以“UI用户管理MenuItem”是不存在的,也就没法点击,这样,测试代码可能会在执行的时候报错。

优化过后的代码:

 1 UI用户名Edit.Text= "Admin";
 2 
 3 UI密码Edit.Text="123456";
 4 
 5 Mouse.Click(UI登陆Button, new Point(1 , 1));
 6 
 7 UI系统主窗口Window. WaitForControlExist(20000);//等待“UI系统主窗口Window”出现,最多20秒,一旦“UI系统主窗口Window”出现,立即结束等待,执行下一句代码。
 8 
 9 Mouse.Click(UI用户管理MenuItem, new Point(1 , 1));
10 
11 Mouse.Click(UI添加用户MenuItem, new Point(1 , 1));

 

二.WaitForControlNotExist()

作用:和WaitForControlExist相反,它是等待某一个控件,直到它消失为止。

返回值:bool类型值,控件是否在规定时间内消失。

典型的使用场景:点击页面或窗体上的关闭按钮,等待对应的页面或窗体关闭。或者用于执行了某个操作后,界面上的某些控件会消失。

举例:

现在想要点击“取消”按钮,退出系统登录模块,并验证是否退出成功。

未优化代码:

1 Mouse.Click(UI取消Button, new Point(11));
2 
3 bool isClosed = !(UI系统登录Window.Exists);
4 
5 Assert.isTrue(isClosed, "未成功退出该模块");

可能造成的问题:

看上去是没有问题的,但您一定要相信我们的Windows大哥随时可能会出现这样的情况:

于是就被卡在这里了,当然,卡完了他是可以关闭的,不过运行中的代码可不会等他卡完。

于是代码在执行这一句“bool isClosed = !(UI系统登录Window.Exists);”时,得到一个false,然后接下来断言就会失败。

优化过后的代码:

1 Mouse.Click(UI取消Button, new Point(11));
2 
3 bool isClosed = UI系统登录Window. WaitForControlNotExist(20000);//等待“UI系统登录Window”消失,最多等20秒,一旦消失,立即结束等待,返回True,执行下一句代码。
4 
5 Assert.isTrue(isClosed, "未成功退出该模块");

 

 

三.WaitForControlPropertyEqual()

作用:等待某个控件的属性的变化,直到它变换为指定的值。

返回值:bool类型值,是否在规定时间内属性是否变换为指定值。

典型的使用场景:执行某个操作之后,窗体或页面上的某一个控件的属性会发生变化,而且这个变化是可以预期的。

举例:

Act.1 为了修改用户头像,现在我们要上传一个文件,假设这个时候已经选择了要上传的文件,点击开始上传

Act.2 上传完成后,点击确认,提交信息

未优化代码:

1 Mouse.Click(UI开始上传Button, new Point(11));
2 
3 Playback.Wait(10000);//也可以用Thread.Sleep(10000);
4 
5 Mouse.Click(UI确认Button, new Point(11));

可能造成的问题:

一看就可以发现,中间等待10秒。

10秒难道就够了?万一刚好是11秒呢?或者可能要等个30秒呢?等待时间过短,可能还未上传结束就开始点击“确认”按钮了。

有的人可能干脆会用“Playback.Wait(30000);” ,但是这会出现我们之前提到的问题:万一1秒就上传成功了呢?那不是还要等29秒?测试不就卡在那里了。

其实大家换个思路,就可以发现其实在上传前后,上传状态是不一样的。

上传前是

上传后是

所以只要上传状态这个Label变为“上传完成”,我们就可以断定是上传完毕了。

优化过后的代码:

1 Mouse.Click(UI开始上传Button, new Point(11));
2 
3 UI上传状态Text.WaitForControlPropertyEqual("DisplayText","上传完成",30000);// 等待“UI上传状态Text”的文本变为"上传完成",最多等30秒,一旦变为"上传完成",立即结束等待,返回True,执行下一句代码。
4 
5 Mouse.Click(UI确认Button, new Point(11));

 

 

四.WaitForControlPropertyNotEqual()

作用:等待某个控件的属性的变化,直到它变换为不同于指定的值。

返回值:bool类型值,是否在规定时间内属性是否变换为不同于指定的值。

典型的使用场景:执行某个操作之后,窗体或页面上的某一个控件的属性会发生变化,而且这个变化是只可估计范围,无法估计具体值。(和WaitForControlPropertyEqual相反,PropertyEqual是在可估计出变化后的具体值时使用)。

举例:

本章开头的那个例子大家还记得吧

这里有一个查询模块,我们接下来的操作是,点击查询,然后获取他的查询出的行数,从界面上可以看到将会查询出5行数据

未优化代码:

1 int userCount=0;
2 
3 Mouse.Click(uI查询Button, new Point(11)); 
4 
5 Playback.Wait(5000);//也可以用Thread.Sleep(5000) 
6 
7 userCount = this.UI查询用户Window.UIUserGridViewWindow.UIDataGridViewTable.Rows.Count; 

可能造成的问题:

我们知道,其实代码是会一行一行执行的,中途没有等待时间,假如这个时候我们网速有点卡,查询出来要等7,8秒,但是,我们的代码可不会等待,或者说等待时间较少(例如这里的5秒),点击“查询”后,他会立即判断这个UIDataGridViewTable有多少行,然后赋值给userCount(但这个时候行数还为0,不是查询完之后的5),于是我们就得到了错误的结果。

如果使用Playback.Wait(30000)来粗糙地解决,就会涉及到我们之前提到的测试效率问题。

但如果我们仔细观察,就可以发现,UIDataGridViewTable在查询前后,Rows属性是有发生变化的(查询前没有行,查询后有行),如何利用这个变化编写相应的代码,提高测试的稳定性呢?

优化过后的代码:

1 int userCount=0;
2 
3 Mouse.Click(uI查询Button, new Point(11)); 
4 
5 this.UI查询用户Window.UIUserGridViewWindow.UIDataGridViewTable.WaitForControlPropertyNotEqual("Rows",Null,30000)// 等待“UIDataGridViewTable”的属性Rows不为空(在没有查询出数据前,Rows属性是等于Null的),最多等30秒,一旦变为非空,立即结束等待,返回True,执行下一句代码。
6 
7 userCount = this.UI查询用户Window.UIUserGridViewWindow.UIDataGridViewTable.Rows.Count; 

 

 

五.WaitForControlReady ()

作用:等待某个控件,直到它解除了线程阻塞,并允许接受外界输入(例如鼠标,键盘操作)。

返回值:bool类型值,是否在规定时间内接触线程阻塞并可以接受输入。

典型的使用场景:执行某个操作之后,窗体或页面上进行相关数据的加载,导致线程阻塞,控件(例如文本框,下拉框)无法接收输入。但加载完之后,页面上的各控件属性没有变化(或者变化了但读取不到)。(因为属性没有变化或者读取不到,也就没法用WaitPropertyEqual和NotEqual,只好被迫用WaitForControlReady)

举例:

Act.1 假设有一个程序,他要为图片添加水印,点击“添加水印”按钮

Act.2 程序卡了一阵之后,水印添加完毕,但是这个水印是是通过GDI+在UI上直接绘制的,您根本没法读取到相应的属性,所以水印是否添加完毕,您无从判断

Act.3 水印添加完毕后,点击保存。

未优化代码:

1 Mouse.Click(UI添加水印Button, new Point(11));
2 
3 Playback.Wait(5000);//或者Thread.Sleep(5000);
4 
5 Mouse.Click(UI保存Button, new Point(11));

可能造成的问题:

如果等待时间过短,则可能水印没有添加成功就保存了,如果等待时间过程,又会引发之前提到的测试效率问题。

需要注意的是,一般程序在这个时候都是单线程处理,也就是说,当我们点击“添加水印”的时候,程序将进行渲染,同时,线程发生阻塞,无法接受外界输入(就像是机器卡的时候会出现“未响应”的状态),而一旦渲染完毕,线程阻塞将终止,可以接受外界输入。

基于这一点,我们可以使用WaitForControlReady来代替Playback.wait

优化过后的代码:

1 Mouse.Click(UI添加水印Button, new Point(11));
2 
3 UI保存Button.WaitForControlReady(30000);// 等待“UI保存Button”直到它能够接受外界输入,最多等30秒,一旦可以接受,立即结束等待,返回True,执行下一句代码。
4 
5 Mouse.Click(UI保存Button, new Point(11));

 

 

六.WaitForControlEnabled()

作用:等待某个控件,直到它启用。等同于WaitForPropertyEqual("Enabled", true )

返回值:bool类型值,是否在规定时间内启用。

典型的使用场景:执行某个操作之后,窗体或页面上某个控件会设置为启用状态

举例:

Act.1 假设有一个模块,实现导入报表功能,如下所示,在点击“文件导入”并导入完成前,“保存”按钮一直禁用。选择一个文件,然后点击“文件导入”

Act.2 文件导入完成后,点击“保存”

未优化代码:

1 Mouse.Click(UI文件导入Button, new Point(11));
2 
3 Playback.Wait(10000);//或Thread.Sleep(3000);
4 
5 Mouse.Click(UI保存Button, new Point(11));

可能造成的问题:

等待时间少了,保存按钮没有启用就去点击了,等待时间长了,又可能有损测试效率。

优化过后的代码:

1 Mouse.Click(UI文件导入Button, new Point(11));
2 
3 UI保存Button .WaitForControlEnabled(30000);// 等待“UI保存Button”直到它启用,最多等30秒,一旦启用,立即结束等待,返回True,执行下一句代码。
4 
5 Mouse.Click(UI保存Button, new Point(11));

 

 

七.写得太累了,WaitForControlCondition和WaitForCondition,大家可以自己研究先,今下午7:00的时候我再贴出相应的使用示例

 

 

总结

本章介绍了提高UI测试稳定性的8种方法(目前暂时介绍6种,下午7:00再贴出最后两个)。相信大家有这样的经历,我们在运行UI测试时,很多测试不通过的情况,其实大多是由于测试代码不够稳定而造成的,而不是被测试程序本身的错误。因此,如何解决这些稳定性问题,一直是UI测试成败的关键之一。

编写测试代码的时候,使用这些方法,无疑可以大大提高测试的稳定性,建议以后代码中凡是出现需要等待一段时间再操作的情况,都是用WaitForControlXXX方法,而不是使用不太给力的Playback.wait或者Thread.Sleep。

 

附:发布计划

编码的UI测试系列之一:入门篇 一个简单的示例(Released)

编码的UI测试系列之二:入门篇 操作动作的录制原理(上)(Released)

编码的UI测试系列之二:入门篇 操作动作的录制原理(下)(Released)

编码的UI测试系列之三:入门篇 操作对象的识别原理 (Released)

编码的UI测试系列之四:进阶篇 通过编写测试代码的方式建立UI测试(上)(Released)

编码的UI测试系列之四:进阶篇 通过编写测试代码的方式建立UI测试(下)(Released)

编码的UI测试系列之:进阶篇 常用测试要点和函数(1(Released)

编码的UI测试系列之六:进阶篇 常用测试要点和函数(2)(Released)

编码的UI测试系列之七:进阶篇 UI测试之间的互相引用和测试组织

编码的UI测试系列之八:高级篇 远程调用其他机器进行测试

编码的UI测试系列之九:高级篇 使用MS测试管理器组织测试

作者: RealDigit 发表于 2011-05-11 10:43 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"