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

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(),改变控件的属性的值。如果去掉注释,可以看到执行路径。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值