基於反射的ui測試自動化程序,要完成的6項任務:ide
AUT是一個剪刀、石頭、布的猜拳軟件,當點擊button1時,會在listbox中顯示誰是勝者。測試
圖1 待測程序GUIui
AUT代碼以下:this
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace AUTForm { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { string tb = textBox1.Text; string cb = comboBox1.Text; if (tb == cb) { listBox1.Items.Add("Result is a tie"); } else if (tb == "paper" && cb == "rock" || tb == "rock" && cb == "scissors" || tb == "scissors" && cb == "paper") { listBox1.Items.Add("The TextBox wins"); } else { listBox1.Items.Add("The ComboBox wins"); } } private void menuItem2_Click(object sender, EventArgs e) { Application.Exit(); } } }
要使用反射技術經過UI來測試Windows窗體,必需要在測試套件所在的進程內建立一個單獨的線程來運行被測程序。這樣,測試程序和被測程序就會在運行在同一進程裏面,從而能夠相互進行通訊。spa
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Reflection; using System.Threading; using System.Diagnostics; using System.Drawing; namespace AUTFormTest { class Program { [STAThread] static void Main(string[] args) { try { Console.WriteLine("\nStarting test scenario"); Console.WriteLine("\nLaunching Form1"); Form theForm = null; string formName = "AUTForm.Form1"; string path = @"E:\wicresoft\Management\Knowledge\Learned\TestFramework\TestSampleLearn\AUTForm\bin\Debug\AUTForm.exe"; theForm = LaunchApp(path, formName); Console.WriteLine("\nMoving Form1"); Point pt = new Point(320, 100); Thread.Sleep(3000); SetFormProperty.SetFormPropertyValue(theForm, "Location", pt); Console.WriteLine("\nSetting textBox1 to 'rock'"); Thread.Sleep(3000); SetControlProperty.SetControlPropertyValue(theForm, "textBox1", "Text", "rock"); Console.WriteLine("Setting comboBox1 to 'scissors'"); Thread.Sleep(3000); SetControlProperty.SetControlPropertyValue(theForm, "comboBox1", "Text", "scissors"); Console.WriteLine("\nClicking button1"); object[] parms = new object[] { null, EventArgs.Empty }; Thread.Sleep(3000); InvokeClickMethod.InvokeMethod(theForm, "button1_Click", parms); bool pass = true; Console.WriteLine("\nChecking listBox1 for 'TextBox wins'"); Thread.Sleep(3000); ListBox.ObjectCollection oc = (ListBox.ObjectCollection) GetControlProperty.GetControlPropertyValue(theForm, "listBox1", "Items"); string s = oc[0].ToString(); if (s.IndexOf("TextBox wins") == -1) pass = false; if (pass) Console.WriteLine("\n-- Scenario result = Pass --"); else Console.WriteLine("\n-- Scenario result = *FAIL* --"); Console.WriteLine("\nClicking File->Exit in 3 seconds"); Thread.Sleep(3000); InvokeClickMethod.InvokeMethod(theForm, "menuItem2_Click", parms); Console.WriteLine("\nEnd test scenario"); } catch (Exception ex) { Console.WriteLine("Fatal error: " + ex.Message); } } /// <summary> /// Lunch App /// </summary> /// <param name="path">The Application path</param> /// <param name="formName">The Form name</param> /// <returns>Form Instance</returns> static Form LaunchApp(string path, string formName) { //1. Load assmebly //2. Get the define type //3. Create type instance //經過assembly讀取程序,而後程序獲取窗體類型,經過程序建立窗體的實例. Form result = null; Assembly a = Assembly.LoadFrom(path); Type t = a.GetType(formName); result = (Form)a.CreateInstance(t.FullName); AppState aps = new AppState(result); ThreadStart ts = new ThreadStart(aps.RunApp); Thread thread = new Thread(ts); //single thread thread.SetApartmentState(ApartmentState.STA); thread.IsBackground = true; thread.Start(); return result; } } public class AppState { public readonly Form formToRun; public AppState(Form f) { this.formToRun = f; } public void RunApp() { Application.Run(formToRun); } } }
public static class SetFormProperty { delegate void SetFormPropertyValueHandler(Form f, string propertyName, object newValue); public static void SetFormPropertyValue(Form f, string propertyName, object newValue) { if (f.InvokeRequired) { Delegate d = new SetFormPropertyValueHandler(SetFormPropertyValue); object[] o = new object[] { f, propertyName, newValue }; f.Invoke(d, o); return; } else { Type t = f.GetType(); PropertyInfo pi = t.GetProperty(propertyName); pi.SetValue(f, newValue, null); } } }
問題1:若是在測試程序中直接調用PropertyInfo.SetValue()會拋錯:"Exception has been thrown by the target of an invocation."。這是由於,不是在窗體的主線程裏調用,而是在自動化測試程序所建立的一個線程裏調用。所以,咱們用Form.Invoke()方法以間接的方式調用SetValue。間接的方式調用,就是用delegate對象調用SetValue()。線程
public static class GetFormProperty { delegate object GetFormPropertyValueHandler(Form f, string propertyName); public static object GetFormPropertyValue(Form f, string propertyName) { if (f.InvokeRequired) { Delegate d = new GetFormPropertyValueHandler(GetFormPropertyValue); object[] o = new object[] { f, propertyName }; object iresult = f.Invoke(d, o); return iresult; } else { Type t = f.GetType(); PropertyInfo pi = t.GetProperty(propertyName); object result = pi.GetValue(f, null); return result; } } }
public static class SetControlProperty { delegate void SetControlPropertyValueHandler(Form f, string controlName, string propertyName, object newValue); public static void SetControlPropertyValue(Form f, string controlName, string PropertyName, object newValue) { if (f.InvokeRequired) { Delegate d = new SetControlPropertyValueHandler(SetControlPropertyValue); object[] o = new object[] { f, controlName, PropertyName, newValue }; f.Invoke(d, o); } else { Type t1 = f.GetType(); FieldInfo fi = t1.GetField(controlName, BindingFlagsList.Flags); object ctr1 = fi.GetValue(f); Type t2 = ctr1.GetType(); PropertyInfo pi = t2.GetProperty(PropertyName); pi.SetValue(ctr1, newValue, null); } } }
BingFlags對象是用來過濾System.Reflection命名空間裏許多不一樣類型的方法的。定義以下:code
public static class BindingFlagsList { public static BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; }
public static class GetControlProperty { delegate object GetControlPropertyValueHandler(Form f, string controlName, string propertyName); public static object GetControlPropertyValue(Form f, string controlName, string propertyName) { if (f.InvokeRequired) { Delegate d = new GetControlPropertyValueHandler(GetControlPropertyValue); object[] o = new object[] { f, controlName, propertyName }; object iResult = f.Invoke(d, o); return iResult; } else { Type t1 = f.GetType(); FieldInfo fi = t1.GetField(controlName, BindingFlagsList.Flags); object ctr1 = fi.GetValue(f); Type t2 = ctr1.GetType(); PropertyInfo pi = t2.GetProperty(propertyName); object gResult = pi.GetValue(ctr1, null); return gResult; } } }
public static class InvokeClickMethod { static AutoResetEvent are = new AutoResetEvent(false); delegate void InvokeMethodHandler(Form f, string methodName, params object[] parms); public static void InvokeMethod(Form f, string methodName, params object[] parms) { if (f.InvokeRequired) { Delegate d = new InvokeMethodHandler(InvokeMethod); f.Invoke(d, new object[] { f, methodName, parms }); are.WaitOne(); } else { Type t = f.GetType(); MethodInfo mi = t.GetMethod(methodName, BindingFlagsList.Flags); mi.Invoke(f, parms); are.Set(); } } }
問題2:假如測試套件觸發了待測程序的某個方法,而這個方法直接或間接建立一個新的線程去執行。若是須要等新線程執行結束之後才能在測試套間裏繼續下一步操做:orm
http://pan.baidu.com/s/1cCSqE對象