|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Download Source Code: Testflow.zip - 172.71 KB IntroductionIn this article I will show how to employ Windows Workflow Foundation (WF) for testing. Originally, Microsoft aimed WF on describing business processes inside an application. The idea is that we can use this technology to describe the process of testing as well. Tester designs a test using visual diagram and, what is more important, he can execute this test automatically. After reading the article you will learn:
To demonstrate all the "How-Tos" I created TestflowFramework on C# and attached it to the article. I expect you have had enough knowlege of WF and C# to say "hello world". The rest of stuff I will explain below. Using TestflowFramework tester can design tests on diagrams in Visual Studio 2008 (for brevity I named “Testflow” a diagram with testing workflow). Blocks on Testflow diagram correspond to actions of user, e.g. ‘type a text in the field’ or ‘click the button’. After diagram is done, tester can run it and all actions will be automatically simulated against the application. Solution accompanied this article contains sample application and its testflows. The sample application manages users list. It supports viewing/adding/editing/removing users. The sample testflow performs the sequence of following actions: clicks ‘Add User’, enters ‘Login’, ‘Full Name’, ‘Department’ fields and so on. In next chapters I will also tell how to reuse this test and run it with different parameters multiple times. This article concentrates on testing Windows Forms applications. Similarly, this approach can be extended on ASP.NET. One could even adopt it to Compact Framework and XNA platforms but with some additional efforts. How it worksWell, Windows Workflow Foundation is a very powerful technology and blending with Visual Studio 2008 makes it utterly explosive. Workflow Foundation has perfect extensible architecture both at runtime and design time. In runtime we need emulate user actions and apply it to Windows Form. Current version of TestflowFramework implements several activities: ClickButtonActivity, InputTextActivity, CloseFormActivity. Anyone can extend this set and add some advanced activities to manipulate grids, viewers, etc. For design time I developed a designer classes that tune style of WF components, captions and enforce some restrictions. Particularly, any testing action is allowed to be added only into a FormScope activity. FormScope activity is a container element that hosts any testing activities and supplies context for them. In other words, FormScope contains actions that user can do on given form. After putting FormScope on diagram, tester selects one form existing in application. See picture below:
The picture below shows ClickButtonAction on FormScope bound to AddUserForm. Note that clicked button is being chosen from a strongly typed list that is read from AddUserForm through Reflection.
It’s also worth to mention that the action emulation is done through Reflection. This approach has pros and cons. First of all if you change resolution, coordinates and layout of your form, tests will work well despite everything because tests are bound to programmatic names of forms and controls. And this gives a second advantage: when you rename/delete a control, you will be notified by compiler (yep, at compile time!). The drawback is that you will probably need to develop custom testing activities for some controls. But reward is a high reliability of tests. Notwithstanding, this is not sole approach. Some cases may require another one (for example if you want to test Win32 applications). Writing tests for applicationSample contains the application managing a simple user list. It has two forms:
As I has told above, our test is a sequence of user actions nested inside FormScope activity. Every user action is described by dedicated activity type. In given version of Testflowframework I included four types:
In this test button "Add User" is clicked. Then Login, Full Name and Department fields are filled with specified test data. And click on "OK" button commits it eventually. Now, if you grasp these basic principles, let's move further.Reusing TestAs soon as you create test, you want to reuse it with different parameters. Let’s say we have “AddUser” test from above. How can we create 3 users with different Login, Full Name, and Department? -- We will use technique of binding values with workflow properties to accomplish this task. See two steps below: 1. Create Test Part with parameters. Create “AddUser” activity. Open its diagram and add an InputTextActivity. For TextBox property of this activity select txtLogin and bind InputText to workflow property “Login”. See picture below:
Do same thing for FullName and Department. Finally, you have to have three activities bound to three properties. 2. Reuse test part and set its parameters.Create new workflow and drag-n-drop “AddUser” test from toolbox. Then set values of properties Login, FullName, Department. You may add this test as many times as you need to create any number of users.
Validating Test ResultsAt this point our test has created 3 users. At least it is due... How we can verify this fact and alert if results are wrong? Well, in given case we will simply validate number of users created: we will access to the list of users (listUsers control at Main Form) and check number of its items. If it is not equal to 3, we abort the test. I have developed custom activity
As you can see the reading data from application is simple. We should select Control Name located on our form, and then we set the Property Name of this control. Finally, we specify binding for Destination Value. So, the value of On next step we will validate this
Thus, we check the condition. In case of failure we can even specify the reason and it will be written into the log file. On the contrary, if all goes well form is gracefully closed after a short delay. Running TestCurrent version uses a very simple approach for test starting. To start test you need to start application with several command line parameters, specifying testing assembly containing test and test name itself. For example: WinApp.exe -test "TestflowsForWinApp.dll" AddUser_TestFlow TestLog.txt After test is done, its result is written into log: 31.12.2007 2:01 | Success | AddUsersBatch 31.12.2007 4:18 | Failure | AddUsersBatch. Reason: User Count is Wrong Log file is named TestLog.txt and stored in application folder (e.g. \WinApp\bin\Debug\ for sample application). No doubt, this simplified approach can be elaborated for particular requirements. Log files could be saved into xml file. Test launching engine can be implemented in more sophisticated manner. Notwithstanding, I leave it as simple as possible for the sake of highlighting the primary idea. Hints1. Do not use standard InvokeWorkflow activity to invoke one test from another. This activity starts specified workflow asynchronously. In most cases it is undesired behavior. 2. Put your reusable test parts into separate assembly apart from the assembly with main tests. Assembly with main tests should refer the assembly with test parts. In this case you will see test parts in Toolbox and can easily drag-n-drop them into main tests. If test parts would be in the same assembly, Visual Studio does not show them in Toolbox. 3. You may use any standard activities to enrich your tests. For example, use DelayActivity to emulate user thinking. Extending Workflow Foundation and Creating Custom Test ActivityThus, you know how to create a test using TestflowFramework. It is time to learn how design own activity type and enrich your tests with new user actions. Below we will go through the proccess of creation InputTextActivity that allows enter test text into TextBox control: 1. Create Activity class and inherit it from WindowsControlActivity. public partial class InputTextActivity : WindowsControlActivity
{
}
We could inherit from most general Activity class but WindowsControlActivity provides us with some basic functionality required for testing activity.
2. Declare TextBox property. public static readonly DependencyProperty TextBoxProperty =
DependencyProperty.Register(
"TextBox",
typeof(string),
typeof(InputTextActivity),
new PropertyMetadata(
"",
DependencyPropertyOptions.Metadata,
new Attribute[]
{ new ValidationOptionAttribute(ValidationOption.Required) }
)
);
[DefaultValue(""),
Description("TextBox where the text will be typed"),
RefreshProperties(RefreshProperties.All),
MergableProperty(false),
TypeConverter(typeof(ControlTypeConverter)),
Category("Testing")]
public string TextBox
{
get
{
return (base.GetValue(TextBoxProperty) as string);
}
set
{
base.SetValue(TextBoxProperty, value);
}
}
Every property should be accompanied by static field of DependencyProperty type. This field plays role of metadata container and helps Windows Workflow do many useful services for us. You could also note 3. Implement type converter. protected class ControlTypeConverter : TypeConverter
{
// Methods
public ControlTypeConverter()
{ }
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
IControlFieldProvider provider = null;
object[] instance = context.Instance as object[];
if ((instance != null) && (instance.Length > 0))
{
provider = instance[0] as IControlFieldProvider;
}
else
{
provider = context.Instance as IControlFieldProvider;
}
ICollection values = new object[0];
if (provider != null)
{
values = provider.GetPropertyValues(context);
}
return new TypeConverter.StandardValuesCollection(values);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{ return true; }
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{ return true; }
}
Interface IControlFieldProvider used in type converter is implemented in WindowsControlActivity class. It reads all form's fields through Reflection. Then it put field name into the value list if it has type of Control: protected Type filterType = typeof(Control);
ICollection IControlFieldProvider.GetPropertyValues(ITypeDescriptorContext context)
{
StringCollection strings = new StringCollection();
if (this.FormType != null)
{
BindingFlags flags =
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.GetField;
foreach (FieldInfo info in this.FormType.GetFields(flags))
{
if (!info.IsSpecialName)
{
if (info.FieldType == filterType || info.FieldType.IsSubclassOf(filterType))
strings.Add(info.Name);
}
}
}
return strings;
}
Sticking to this pattern you can declare any property with any predefined values. 4. Declare the InputText property. public static readonly DependencyProperty ParametersProperty =
DependencyProperty.Register(
"Parameters",
typeof(WorkflowParameterBindingCollection),
typeof(InputTextActivity),
new PropertyMetadata(
DependencyPropertyOptions.Metadata |
DependencyPropertyOptions.ReadOnly
)
);
public WorkflowParameterBindingCollection Parameters
{
get
{
return ((WorkflowParameterBindingCollection)(base.GetValue(ParametersProperty)));
}
}
... and initialize it in constructor with following line: base.SetReadOnlyPropertyValue(ParametersProperty, new WorkflowParameterBindingCollection(this));
Binding infrastructure is ready. Declare the property:
public static DependencyProperty InputTextProperty =
DependencyProperty.Register(
"InputText",
typeof(string),
typeof(InputTextActivity)
);
[Description("Text entered into the text box")]
[Category("Testing")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string InputText
{
get
{
return ((string)(base.GetValue(InputTextProperty)));
}
set
{
base.SetValue(InputTextProperty, value);
}
}
5. Implement runtime behavior. protected override void DoControlActivity(Form form)
{
TextBox box = GetControl(form, TextBox) as TextBox;
box.Select();
box.Text = InputText;
}
Our testing activity selects specified TextBox and "types" text there. BottomlineCongrats! We have extended Workflow Foundation and make our test activity work. We declated a class inherited from WindowsControlActivity (or Activity). Then we declared two properties: first one had a predefined list of controls and second one was bindable string property. All declarations was made by a specific pattern required by WF. Finally, we defined runtime behavior through overriding DoControlActivity method (or Execute method if we need to do something general). History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||