net自动化测试之道基于反射的UI自动化测试—设置控件的属性

来源:互联网 发布:java中aop是什么意思 编辑:程序博客网 时间:2024/05/22 00:37

Manipulating Control Properties

Problem

You want to set the value of a controlproperty.

 问题

如何设置控件的属性值?

Design

Obtain a reference to the control you wantto manipulate using the Form.GetType(),

Type.GetField(),and FieldInfo.GetValue()methods.Thenuse the PropertyInfo.SetValue()

method in conjunction with a methoddelegate to set the value of the target control.

设计

使用Form.GetType()、Type.GetField()、FieldInfo.GetValue()获取我们想设置的控件的引用。然后结合代理使用PropertyInfo.SetValue()方法设置目标控件的值。

 解决方案

if(theForm.InvokeRequired)

{

Delegate d=

newSetControlPropertyValueHandler(SetControlPropertyValue);

object[]o=newobject[]{theForm,"textBox1","Text","foo"};

Console.WriteLine("Setting textBox1to'foo'");

theForm.Invoke(d,o);

}

else

{

Console.WriteLine("Unexpected logicflow");

}

在某处声明代理方法:

static BindingFlagsflags=BindingFlags.Public|

BindingFlags.NonPublic|

BindingFlags.Static|

BindingFlags.Instance;

delegate voidSetControlPropertyValueHandler(Form f,

string controlName,string propertyName,objectnewValue);

static void SetControlPropertyValue(Formf,string controlName,

string propertyName,object newValue)

{

Type t1=f.GetType();

FieldInfofi=t1.GetField(controlName,flags);

object ctrl=fi.GetValue(f);

Type t2=ctrl.GetType();

PropertyInfopi=t2.GetProperty(propertyName);//?

pi.SetValue(ctrl,newValue,null);

}

注解

When writing lightweight reflection-basedUI test automation,you may need to simulate user

actions by manipulating properties ofcontrols on the application form.Examples include

setting the Text property value of aTextBox control to simulate a user typing and setting the

Checked property value of a RadioButtonListitem to true to simulate a user clicking on the

item.The key to setting the value of acontrol’s property is to use the PropertyInfo.SetValue()

method.Unfortunately,as described inSections 2.2“Manipulating Form Properties”and 2.3

“Accessing Form Properties,”there is a hidden issue—you should notcall SetValue()directly

from a thread that is not the main Formthread.If the hidden issue did not exist,you could set

the value of a control like this:

当编写基于反射的轻量级UI自动化测试的时候,我们可能需要通过设置form控件的属性值来模拟用户行为。例子中包括了设置TextBox的Text属性值,模拟用户输入,设置RadioButtonList的Checked属性值为true,模拟用户点击RadioButtonList的条目。设置控件的属性值的关键是使用PropertyInfo.SetValue()。不幸的是,同前两节一样,有一个隐藏的线程问题,如果没有这个问题,我们可以这样设置:

BindingFlagsflags=BindingFlags.Public|BindingFlags.NonPublic|

BindingFlags.Static|BindingFlags.Instance;

Console.WriteLine("Setting textBox1to'foo'");

Type t1=theForm.GetType();

FieldInfo fi=t1.GetField("textBox1",flags);

object ctrl=fi.GetValue(theForm);

Type t2=ctrl.GetType();

PropertyInfopi=t2.GetProperty("Text");

pi.SetValue(ctrl,"foo",null);

The BindingFlags object is a filter formany of the methods in the System.Reflection

namespace.In lightweight test-automationsituations,you almost always filter for Public,

NonPublic,Instance,and Staticmethods,as we’ve done in thisexample.Because this is such

a common pattern,you’ll often find itconvenient to declare a single class-scope BindingFlags

object,rather than recode a new object foreach call that requires a BindingFlags argument.

To manipulate a control,you begin bygetting a Type object from the parent Form object.This

is the first of two Type objects you’llneed.Then you use the Type object to obtain a reference to a

FieldInfo object by using the GetField()method.Withthis intermediate FieldInfo object,you

can now get a reference to the actualcontrol object by calling FieldInfo.GetValue().This is not

entirely intuitive but the pattern isalways the same.Next,you use the control object and get its

Type by calling GetType().Thenyou can use this second Type object to get a PropertyInfo object

using the GetProperty()method.At thispoint,you have references to the control object and one of its properties.Finally,youcan manipulate the value of the control’s property by using the

SetValue()method.

The first two arguments passed toSetValue()are the control object to manipulate and

the new value for the control’sproperty.The third argument represents optional index values.

You only need this when you are dealingwith indexed properties.This value should be a null

reference for nonindexed properties,as isalmost always the case for controls.Although some

controls,such as the ListBox control,havecomponents that are indexed(the Items property,

for example),the control itself is notindexed.

As described earlier,the hidden issue isthat you should not directly call SetValue()on a

control object from a thread that does notown the control’s parent Form object.Doing so can

lead to complex thread synchronizationproblems.Because you are working from the test-

harness thread instead of the Formthread,the Form.InvokeRequired property is alwaystrue.

The recommended technique in situationslike this is to call Form.Invoke(),passing a delegate

object that is associated with a methodthat actually calls SetValue().Implementing this idea

gives you the code in this solution.

You can significantly increase themodularity of this technique by wrapping the code up

into a single method combined with adelegate object:

static BindingFlagsflags=BindingFlags.Public|

BindingFlags.NonPublic|

BindingFlags.Static|

BindingFlags.Instance;

delegate voidSetControlPropertyValueHandler(Form f,

string controlName,stringpropertyName,object newValue);

static void SetControlPropertyValue(Formf,string controlName,

string propertyName,object newValue)

{

if(f.InvokeRequired)

{

//Console.WriteLine("in invokereq.");

Delegate d=

newSetControlPropertyValueHandler(SetControlPropertyValue);

object[]o=newobject[]{f,controlName,propertyName,newValue};

f.Invoke(d,o);

}

else

{

//Console.WriteLine("in elsepart");

Type t1=f.GetType();

FieldInfofi=t1.GetField(controlName,flags);

object ctrl=fi.GetValue(f);

Type t2=ctrl.GetType();

PropertyInfo pi=t2.GetProperty(propertyName);

pi.SetValue(ctrl,newValue,null);

}

}

The method can be called like this:

SetControlPropertyValue(theForm,"textBox1","Text","paper");

SetControlPropertyValue(theForm,"comboBox1","Text","rock");

This SetControlPropertyValue()wrapperimproves the modularity of your automation

code,but is somewhat tricky because itreferences itself.When SetControlPropertyValue()is

called in the Main()method of your harness,InvokeRequiredis initially true because the calling

thread does not own the form.Executionbranches to the Form.Invoke()statement,which,in

turn,calls the SetControlPropertyValueHandler()delegatethat calls back into the associated

SetControlPropertyValue()method.But the second time through the wrapper,InvokeRequired

will be false because the call now comesfrom the originating thread.Execution transfers to the

else part of the logic,where the PropertyInfo.SetValue()changesthe control’s property.If you

remove the commented lines of code andrun,you’ll see how the path of execution works.

这个BindingFlags对象是对System.Reflection命名空间许多方法的过滤器。在轻量级自动测试场景中,我们总是过滤Public,NonPublic,Instance,和 Static 方法,正如这个例子。因为这是一种常见模式,我们将会发现在类范围内声明BindingFlags比当需要BindingFlags参数的时候再声明要方便。要设置控件属性,首先我们要从父form对象上获取Type对象。这是我们需要的两个Type对象中的第一个。然后我们在Type对象上调用GetField()方法,获取一个FieldInfo对象的引用。有了这个中间媒介FieldInfo对象,我们可以通过调用FieldInfo.GetValue()获取控件对象的引用。接着,我们通过在控件对象上调用GetType()获取它的Type。然后,我们使用第二个Type对象调用GetType()获取PropertyInfo对象。这时候,我们有了控件的引用和它的一个属性。最后使用SetValue()方法设置控件属性值。

SetValue()方法的第一个参数是需要设置的控件对象,第二个参数是控件属性的新值,第三各参数代表可选的索引值,只有在处理索引属性的时候才会用到,对于非索引属性,大部分时候像本例中的控件一样,该值为null。虽然有些控件,如ListBox由索引元件组成(如它的item属性),但是控件本身是非索引的。

前面说过,这个隐藏的问题是我们不能直接在控件线程中调用SetValue()。这将导致复杂的线程同步问题。因为我们是在测试套件的线程中工作的,而不是Form线程。Form.InvokeRequired属性总是为true。这种情况下,推荐的技巧是调用Form.Invoke(),给它传递一个代理对象,该代理对象与实际调用SetValue()的方法关联。解决方案中展现了实现该想法的代码。

我们可以通过封装代码到一个方法中增加模块性:

static BindingFlagsflags=BindingFlags.Public|

BindingFlags.NonPublic|

BindingFlags.Static|

BindingFlags.Instance;

delegate voidSetControlPropertyValueHandler(Form f,

string controlName,stringpropertyName,object newValue);

static voidSetControlPropertyValue(Form f,string controlName,

string propertyName,objectnewValue)

{

if(f.InvokeRequired)

{

//Console.WriteLine("ininvoke req.");

Delegate d=

newSetControlPropertyValueHandler(SetControlPropertyValue);

object[]o=newobject[]{f,controlName,propertyName,newValue};

f.Invoke(d,o);

}

else

{

//Console.WriteLine("inelse part");

Type t1=f.GetType();

FieldInfofi=t1.GetField(controlName,flags);

object ctrl=fi.GetValue(f);

Type t2=ctrl.GetType();

PropertyInfopi=t2.GetProperty(propertyName);

pi.SetValue(ctrl,newValue,null);

}

}

然后这样调用:

SetControlPropertyValue(theForm,"textBox1","Text","paper");

SetControlPropertyValue(theForm,"comboBox1","Text","rock");

封装的SetControlPropertyValue()方法能够增加自动化测试套件的模块性,但是由于存在自我引用,因此有点难理解。当SetControlPropertyValue()在套件的Main方法调用的时候,InvokeRequired初始化为true。因为调用的线程不拥有这个form。程序进入if分支,执行Form.Invoke()语句,激发代理方法SetControlPropertyValueHandler()的调用,从而调用与之关联的SetControlPropertyValue()方法。但是第二次进入该方法的时候,由于调用来自同一线程,InvokeRequired为false,程序执行进入else分支,执行PropertyInfo.SetValue(),改变控件的属性的值。如果去掉注释,可以看到执行路径。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 孩子练字就是记不住怎么办 孩子不愿意和小朋友玩怎么办 丈夫去世了婆婆不喜欢儿媳妇怎么办 高考看不下去书怎么办 在东莞读书读不成高中怎么办 嗓子哑了怎么办土方法 小孩舌头太长太大讲话不清楚怎么办 一周岁不喝奶粉怎么办 一岁的宝宝不吃奶粉怎么办 小孩不爱吃饭怎么办吃什么药 小孩这几天不爱吃饭怎么办 宝宝这几天不爱吃饭怎么办 想看书看不进去怎么办 宝宝3岁不爱看书怎么办 4岁宝宝不爱看书怎么办 1岁宝宝不爱看书怎么办 孩子一看书就哭怎么办 我不想读大专了怎么办 一年级的小孩不爱学习怎么办 高中的孩子不爱学习怎么办 9个月宝宝肠胃不好怎么办 孩子高烧过后干呕不爱吃饭怎么办 母乳涨奶发烧了怎么办 涨奶发烧了怎么办啊 2岁宝贝不吃饭怎么办 4个月的婴儿厌食怎么办 2个月婴儿厌食怎么办 3个月宝宝厌奶怎么办 没胃口吃不下饭怎么办 小孩吃多了积食怎么办 孩子吃撑了难受怎么办 卵巢早衰月经量少怎么办 7岁孩吃饭少消瘦怎么办 宝宝3岁不吃饭怎么办 小孩吃多了吐了怎么办 6岁儿童越来越瘦怎么办 7岁儿童不吃饭怎么办 天热宝宝不好好吃饭怎么办 天热宝宝不爱吃饭怎么办 天热宝宝不想吃饭怎么办 夏天天热宝宝不爱吃饭怎么办