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

来源:互联网 发布:wps vba编程实战教程 编辑:程序博客网 时间:2024/06/12 05:24

Manipulating Form Properties

Problem

问题

如何设置winform应用程序的属性?

Design

Get a reference to the property you want toset using the Type.GetProperty()method.Then use

the PropertyInfo.SetValue()methodin conjunction with the Form.Invoke()method and a

method delegate.

设计

使用Type.GetProperty()方法获得我们想操作的form属性的引用。然后结合Form.Invoke()方法和一个代理方法,使用PropertyInfo.SetValue()方法设置属性的值。

 解决方案

string formName="AUT.Form1";

stringpath="..\\..\\..\\AUT\\bin\\Debug\\AUT.exe";

Form theForm=LaunchApp(path,formName);//seeSection 2.1

Thread.Sleep(1500);

Console.WriteLine("\nSetting Form1Location to x=10,y=20");

System.Drawing.Point pt=newSystem.Drawing.Point(10,20);

object[]o=newobject[]{theForm,"Location",pt};

Delegate d=new SetFormPropertyValueHandler(SetFormPropertyValue);

if(theForm.InvokeRequired)

{

theForm.Invoke(d,o);

}

else

{

Console.WriteLine("Unexpected logicflow");

}

在程序的某处定义下列方法:

delegate voidSetFormPropertyValueHandler(Form f,

string propertyName,

object newValue);

static void SetFormPropertyValue(Formf,string propertyName,

object newValue)

{

Type t=f.GetType();

PropertyInfopi=t.GetProperty(propertyName);

pi.SetValue(f,newValue,null);

}

Comments

To simulate user interaction with aWindows-based form application,you may want to move

the form or resize the form.One way to dothis using a reflection-based technique is to use the

PropertyInfo.SetValue()method.Although the idea is simple in principle,the details are

tricky.You can best understand thetechnique by working backwards.The.NET Framework has

aPropertyInfo.SetValue()method that can set the value of a property of anobject.But the

SetValue()method requires a PropertyInfo objectcontext.However,a PropertyInfo object

requires a Type object context.So youstart by creating a Type object from the Form object you

want to manipulate.Then you get aPropertyInfo object from the Type object,and then you call

the SetValue()method.So,ifthere were no hidden issues you could simply write code like this:

theForm=LaunchApp(path,formName);//seeSection 2.1

Console.WriteLine("\nSetting Formlocation to x=10,y=20");

Type t=theForm.GetType();

PropertyInfopi=t.GetProperty("Location");

Point pt=new Point(10,20);

pi.SetValue(theForm,pt,null);

Unfortunately,there is a serious hiddenissue that you must deal with.Before explaining that

hidden issue,let’s examine theSetValue()method.SetValue()accepts three arguments.The

PropertyInfo object,whose SetValue()method you call,represents a property,such asa Form

object’s Location property.The firstargument to SetValue()is the object tomanipulate,which in

this case is the Form object.The secondargument is the new value of the property,which in this

example is a new Point object.The thirdargument is necessary because some properties are

indexed.When a property is not indexed,asis usually the case with form controls,you can just

pass a null value as the argument.

The hidden issue with calling thePropertyInfo.SetValue()method is that you are not

calling SetValue()from the main Form thread;youare calling SetValue()from a thread cre-

ated by the test-automation harness.Insituations like this,you should not call SetValue()

directly.A full explanation of this issueis outside the scope of this book,but the conclusion is

that you should call SetValue()indirectlyby calling the Form.Invoke()method.This is a bit

tricky because Form.Invoke()requires adelegate object that calls SetValue()and an object

that represents the arguments forSetValue().So in pseudo-code,you need to do this:

if(theForm.InvokeRequired)

theForm.Invoke(a method delegate,an objectarray);

else

Console.WriteLine("Unexpected logicflow");

The InvokeRequired property in thissituation should always be true because the Form

object was launched by a differentthread(the automation harness).If InvokeRequired is not

true,there is a logic error and you maywant to print a warning message.

So,now you need a method delegate.Beforeyou create the delegate,which you can think

of as an alias for a real method,you createthe real method that will actually do the work:

static void SetFormPropertyValue(Formf,string propertyName,

object newValue)

{

Type t=f.GetType();

PropertyInfopi=t.GetProperty(propertyName);

pi.SetValue(f,newValue,null);

}

Notice that this method is almost exactlylike the naive code if the whole InvokeRequired

hidden issue did not exist.After creatingthe real method,you create a delegate that matches

the real method:

delegate voidSetFormPropertyValueHandler(Form f,string propertyName,

object newValue);

In short,if you pass a reference todelegate SetFormPropertyValueHandler(),control is

transferred to the associatedSetFormPropertyValue()method(assuming you associate the

two in the delegate constructor).

Now that we’ve dealt with the delegateparameter to the Form.Invoke()method,we have

to deal with the object arrayparameter.This parameter represents arguments that are passed

to the delegate and then,in turn,are passedto the associated real method.In this case,the

delegate requires a Form object,a propertyname as a string,and a location as a Point object:

System.Drawing.Point pt=newSystem.Drawing.Point(10,20);

object[]o=newobject[]{theForm,"Location",pt};

Putting these ideas and code together,youcan write

delegate voidSetFormPropertyValueHandler(Form f,

string propertyName,

object newValue);

static void SetFormPropertyValue(Formf,string propertyName,

object newValue)

{

Type t=f.GetType();

PropertyInfopi=t.GetProperty(propertyName);

pi.SetValue(f,newValue,null);

}

static void Main(string[]args)

{

Form theForm=null;

string formName="AUT.Form1";

stringpath="..\\..\\..\\AUT\\bin\\Debug\\AUT.exe";

theForm=LaunchApp(path,formName);//seeSection 2.1

Console.WriteLine("\nSetting Form1Location to 10,20");

System.Drawing.Point pt=newSystem.Drawing.Point(10,20);

object[]o=newobject[]{theForm,"Location",pt};

Delegate d=newSetFormPropertyValueHandler(SetFormPropertyValue);

if(theForm.InvokeRequired)

theForm.Invoke(d,o);

else

Console.WriteLine("Unexpected logicflow");

//etc.

}

And now manipulating the properties of theapplication form is very easy.For example,

suppose you want to change the size of theform.Here’s how:

Console.WriteLine("\nSetting Form1Size to 300x400");

System.Drawing.Size size=newSystem.Drawing.Size(300,400);

object[]o=new object[]{theForm,"Size",size};

Delegate d=newSetFormPropertyValueHandler(SetFormPropertyValue);

if(theForm.InvokeRequired)

{

theForm.Invoke(d,o);

}

else

Console.WriteLine("Unexpected logicflow");

Console.WriteLine("\n And now settingForm1 Size to 200x500");

Thread.Sleep(1500);

size=new System.Drawing.Size(200,500);

o=newobject[]{theForm,"Size",size};

d=newSetFormPropertyValueHandler(SetFormPropertyValue);

if(theForm.InvokeRequired)

{

theForm.Invoke(d,o);

}

else

Console.WriteLine("Unexpected logicflow");

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

into a single method combined with adelegate:

delegate voidSetFormPropertyValueHandler(Form f,

string propertyName,object newValue);

static void SetFormPropertyValue(Formf,string propertyName,

object newValue)

{

if(f.InvokeRequired)

{

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

Delegate d=

newSetFormPropertyValueHandler(SetFormPropertyValue);

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

f.Invoke(d,o);

return;

}

else

{

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

Type t=f.GetType();

PropertyInfopi=t.GetProperty(propertyName);

pi.SetValue(f,newValue,null);

}

}

With this helper method,you can make cleancalls in your test harness such as

Form theForm=null;

string formName="AUT.Form1";

stringpath="..\\..\\..\\AUT\\bin\\Debug\\AUT.exe";

theForm=LaunchApp(path,formName);//seeSection 2.1

System.Drawing.Point pt=newSystem.Drawing.Point(10,10);

SetFormPropertyValue(theForm,"Location",pt);

Thread.Sleep(1500);

pt=new System.Drawing.Point(200,300);

SetFormPropertyValue(theForm,"Location",pt);

Thread.Sleep(1500);

This SetFormPropertyValue()wrapper isslightly tricky because it is self-referential.(A

recursive method calls itself directly;aself-referential method calls itself indirectly.)When

called in the Main()method of yourharness,InvokeRequired is initially true because the call-

ing automation thread does not own theform.Execution branches to the Form.Invoke()

statement,which,in turn,calls theSetFormPropertyValueHandler()delegate that calls back

into the associatedSetFormPropertyValue()method.But the second time through the wrap-

per,InvokeRequired will be false,becausethe call comes from the originating thread.Control

transfers to the else part of thelogic,where the PropertyInfo.SetValue()changes the Form

property.If you remove the commented linesof code and run,you’ll see how the path of exe-

cution works.

Because placing Thread.Sleep()delays is socommon in UI test automation,you may

want to add a delay parameter to all thewrapper methods in this chapter:

static void SetFormPropertyValue(Formf,string propertyName,

object newValue,int delay)

{

Thread.Sleep(delay);

//other code as before

}

So,if you wanted to delay 1,500milliseconds(1.5 seconds),you can call

SetFormPropertyValue()like this:

Point point=new Point(50,75);

SetFormPropertyValue(theForm,"Location",point,1500);

In a lightweight test-automationsituation,the most common form properties you will

manipulate are Size andLocation.However,the techniques in the section allow you to set any

form property.For example,suppose you wantto manipulate the form title bar.You can do

this by passing"Text"as theproperty name argument and a string for the new title:

Form theForm=null;

string formName="AUT.Form1";

string path="..\\..\\..\\AUT\\bin\\Debug\\AUT.exe";

theForm=LaunchApp(path,formName);//seeSection 2.1

SetFormPropertyValue(theForm,"Text","SomeNewTitle");

Thread.Sleep(1500);

注解

为了模拟与winform应用程序交互,我们可能想移动form或者改变form的大小。采用反射技术的一种方式是使用PropertyInfo.SetValue()。虽然理论上这种方式很简单,但是细节却有点复杂。下面我们会详细讲解。.NET环境有一个PropertyInfo.SetValue()方法,使用该方法我们可以设置一个对象的属性值。但是SetValue()需要通过一个PropertyInfo对象调用,而PropertyInfo对象要通过Type对象获得。因此,我们首先要通过Form对象创建一个Type对象,注意这个Form对象是我们需要操作的那个。然后,我们就能通过这个Type对象获得PropertyInfo对象,最后在该PropertyInfo对象上调用SetValue()方法。如果没有隐藏的问题,代码可以简单写成如下这样:

theForm=LaunchApp(path,formName);//seeSection 2.2

Console.WriteLine("\nSetting Formlocation to x=10,y=20");

Type t=theForm.GetType();

PropertyInfopi=t.GetProperty("Location");

Point pt=new Point(10,20);

pi.SetValue(theForm,pt,null);

不幸的是,这样写会有一个严重的隐藏问题需要处理。在解释之前,我们先说明一下SetValue()方法,该方法接收三个参数。我们是在PropertyInfo对象上调用SetValue方法的,该对象代表一个属性,如一个Form对象的Location属性。SetValue方法第一个参数是需要操作的对象,在这个例子中是Form对象,第二个参数是属性的新值,在这个例子中是一个新的Point对象。第三个参数是必要的,因为有些属性是被变址的。如果一个属性不是变址的,通常像本例子中form控件的一样,我们可以只传递一个null值。

这个隐藏的问题是,我们不是在Form线程中调用SetValue()方法,而是在测试套件的线程中调用的。像这种情况,我们不能直接调用SetValue()方法。至于原因的完整解释超出了本书的范围。但是,结论是我们必须通过调用Form.Invoke()方法来间接调用SetValue()。这稍微有点小复杂,因为Form.Invoke()需要一个代理对象来调用SetValue(),并且要一个代表SetValue()方法参数的对象。因此,用伪代码表示如下:

if(theForm.InvokeRequired)

theForm.Invoke(a method delegate,an objectarray);

else

Console.WriteLine("Unexpected logicflow");

在这种场景中,InvokeRequired属性总是为true的,因为这个Form对象与测试套件运行在不同的线程中。如果该属性不为true,那么就是一个逻辑错误,我们可能想将警告信息打印出来。

现在我们需要一个代理方法,我们简单的将代理方法认为是真正方法的别名。在创建代理方法之前,我们先来创建实际工作的真正方法:

static void SetFormPropertyValue(Formf,string propertyName,

object newValue)

{

Type t=f.GetType();

PropertyInfopi=t.GetProperty(propertyName);

pi.SetValue(f,newValue,null);

}

之后,我们创建一个与该方法匹配的代理方法:

delegate void SetFormPropertyValueHandler(Formf,string propertyName,

object newValue);

简言之,如果我们将一个引用传递给代理SetFormPropertyValueHandler(),假设它与SetFormPropertyValue()关联,则控制将转移到SetFormPropertyValue()方法。我们已经处理了Form.Invoke()方法第一个参数,即代理方法,那么现在来处理第二个参数,即object的数组。这个参数代表传递给代理的参数,然后传递给与代理方法关联的真正工作的方法。在这个例子中,代理方法需要一个Form对象,一个string类型的属性的名称,一个Point类型的对象:

System.Drawing.Point pt=newSystem.Drawing.Point(10,20);

object[]o=newobject[]{theForm,"Location",pt};

注意用到Form类的时候要添加System.Windows.Forms的应用,用到Drawing类要添加System.Drawing的引用。PropertyInfo类是在System.Reflection的命名空间下

将这些想法和代码整合在一起,就可以写成:

delegate voidSetFormPropertyValueHandler(Form f,

string propertyName,

object newValue);

static void SetFormPropertyValue(Formf,string propertyName,

object newValue)

{

Type t=f.GetType();

PropertyInfopi=t.GetProperty(propertyName);

pi.SetValue(f,newValue,null);

}

static void Main(string[]args)

{

Form theForm=null;

string formName="AUT.Form1";

stringpath="..\\..\\..\\AUT\\bin\\Debug\\AUT.exe";

theForm=LaunchApp(path,formName);//seeSection 2.1

Console.WriteLine("\nSetting Form1Location to 10,20");

System.Drawing.Point pt=newSystem.Drawing.Point(10,20);

object[]o=newobject[]{theForm,"Location",pt};

Delegate d=newSetFormPropertyValueHandler(SetFormPropertyValue);

if(theForm.InvokeRequired)

theForm.Invoke(d,o);

else

Console.WriteLine("Unexpected logicflow");

//etc.

}

现在,我们要操作Form的属性就很容易了,例如,假设我们想改变Form的大小,代码如下:

Console.WriteLine("\nSetting Form1Size to 300x400");

System.Drawing.Size size=newSystem.Drawing.Size(300,400);

object[]o=newobject[]{theForm,"Size",size};

Delegate d=newSetFormPropertyValueHandler(SetFormPropertyValue);

if(theForm.InvokeRequired)

{

theForm.Invoke(d,o);

}

else

Console.WriteLine("Unexpected logicflow");

Console.WriteLine("\n And now settingForm1 Size to 200x500");

Thread.Sleep(1500);

size=new System.Drawing.Size(200,500);

o=newobject[]{theForm,"Size",size};

d=newSetFormPropertyValueHandler(SetFormPropertyValue);

if(theForm.InvokeRequired)

{

theForm.Invoke(d,o);

}

else

Console.WriteLine("Unexpected logicflow");

我们可以结合代理通过封装代码到一个简单的方法来增加模块性:

delegate voidSetFormPropertyValueHandler(Form f,

string propertyName,object newValue);

static void SetFormPropertyValue(Formf,string propertyName,

object newValue)

{

if(f.InvokeRequired)

{

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

Delegate d=

newSetFormPropertyValueHandler(SetFormPropertyValue);

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

f.Invoke(d,o);

return;

}

else

{

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

Type t=f.GetType();

PropertyInfopi=t.GetProperty(propertyName);

pi.SetValue(f,newValue,null);

}

}

有了这个辅助方法,我们在测试套件中调用,则代码更简洁,如:

Form theForm=null;

string formName="AUT.Form1";

stringpath="..\\..\\..\\AUT\\bin\\Debug\\AUT.exe";

theForm=LaunchApp(path,formName);//seeSection 2.1

System.Drawing.Point pt=newSystem.Drawing.Point(10,10);

SetFormPropertyValue(theForm,"Location",pt);

Thread.Sleep(1500);

pt=new System.Drawing.Point(200,300);

SetFormPropertyValue(theForm,"Location",pt);

Thread.Sleep(1500);

这个封装的SetFormPropertyValue()有点难理解,因为它存在自我引用(递归方法直接调用自身,自我引用方法是间接调用自身)。当该方法在测试套件的Main()函数中被调用,InvokeRequired被初始化为true,因为调用自动化套件的线程不拥有这个form。分支到达Form.Invoke()语句,然后调用代理方法SetFormPropertyValueHandler(),进而回调与之关联的SetFormPropertyValue()方法。但是,第二次经过wrapper的时候,InvokeRequired将会变为false,因为调用是来自原来的线程。这样控制将进入else分支,在这个分支中,PropertyInfo.SetValue()语句将改变Form的属性。如果我们把Console.WriteLine("inthe else part");前面的注释去掉,将会看到执行路径。

由于在自动化UI测试中,放置Thread.Sleep()很常见,我们可能会想将延迟时间作为一个参数传给被封装的方法:

static void SetFormPropertyValue(Formf,string propertyName,

object newValue,int delay)

{

Thread.Sleep(delay);

//other code as before,注意object数组元素和代理方法的参数要与这个方法参数匹配

}

因此,如果我们想延迟1500微秒(1.5秒),就这样调用SetFormPropertyValue()方法:

Point point=new Point(50,75);

SetFormPropertyValue(theForm,"Location",point,1500);

在轻量级自动化测试场景中,我们最常操作的Form属性是Size和Location。但是,本节介绍的技术可以让我们设置form的任何属性。例如,假设我们想操作Form的标题栏,可以将“Text”作为属性名称,一个字符串作为新的标题名称传给方法SetFormPropertyValue():

Form theForm=null;

string formName="AUT.Form1";

stringpath="..\\..\\..\\AUT\\bin\\Debug\\AUT.exe";

theForm=LaunchApp(path,formName);//seeSection 2.1

SetFormPropertyValue(theForm,"Text","SomeNewTitle");

Thread.Sleep(1500);

原创粉丝点击