Blog Home  Home Feed your aggregator (RSS 2.0)  
artiso Blog - Thursday, October 09, 2008
Neues rund um's Thema .Net
 
 Friday, October 10, 2008

Advanced Developers Conference, Windows Development, Windwos Entwicklung, ADC, LINQ, LINQ (DB), Testing, Deployment, Visual Studio Team System, VSTS-Versionkontrolle, Dynamische Sprachen, C++, C#, Debugging, Architektur, WCF, WF, WPF, Parallel LINQ, OpenMP, MPI, C++/CLI, C++-Interop, MFC, TR1, Vista-API’s, Debugging, VS-Extensions, Database Data Mining, Analysis, Reporting, VS-DB-Edition, visual studio, .net, vista, Anwendungsentwicklung .NET, managed code, unmanaged code

Nächste Woche Dienstag bin ich mit zwei Vorträgen auf der ADC08 vertreten.

Testing Practices mit VSTS und TFS
VSTS bringt eine ganze Reihe von Testmethoden. Insbesondere die Edition für Tester bietet verschiedene Testarten. Die Implementierung der unterschiedlichen Testarten ist inzwischen hinlänglich beschrieben. Wie diese Tests in Real-World-Projekte eingesetzt werden, um tatsächlich die Qualität zu verbessern, beschreibt der Vortrag ebenso wie die Lösung von Real-World-Problemen. Dabei werden Themen wie die Bereitstellung von Testumgebungen, Testplanung und Testdurchführung sowie Testdokumentation ebenso beleuchtet wie die Frage, in welchen Fällen eine Edition für Tester notwendig ist, wo es mögliche Alternativen gibt und wie verschiedene Akteure innerhalb des Projekts wie z.B. Fachabteilungen eingebunden werden.

UI-Testing mit UI Automation
Unit-Testing und Web-Testing verbreiten sich mit VSTS und anderen Testing-Tools immer mehr. Was diese Testmethoden jedoch nicht abdecken, ist quasi die „letzte Meile“, die Oberfläche der Anwendungen. Um diese in Tests einzubinden sind entweder teure Tools erforderlich, oder man nutzt das weitgehend unbekannte UI Automation Framework, das Bestandteil des .NET 3.0 Frameworks ist. Der Vortrag erläutert zunächst, wie man mit Hilfe des UI Automation Frameworks Windows- und Web-Anwendungen kontrollieren kann. Anschließend werden diese Methoden in Unit-Tests integriert, um damit automatisierte UI-Tests aufzubauen. Als krönender Abschluss wird ein UI-Test-Rekorder implementiert, mit dem UI-Aktionen aufgezeichnet und automatisiert in Unit-Test überführt werden können. Der Vortrag beschreibt neben den Möglichkeiten auch Grenzen des UI Automation Frameworks und mögliche Lösungen.

Friday, October 10, 2008 12:05:40 AM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    | 
 Thursday, October 09, 2008

MSDN Webcasts

Seit ein paar Tagen ist ein neuer Web-Cast online in dem ich zusammen mit Christian Binder zusammen erörtere welche Vorteile die hierarchische Organisation von Workitems bietet. Ohne schon zuviel vom Inhalt verraten zu wolle, es geht um Requirementmanagement, Traceability, Impace-Analyse und einiges mehr. Im Webcast wird ein kostenloses Tool vorgestellt mit dem bereits mit TFS 2008 hierarchische Workitems realisiert werden können.

Thursday, October 09, 2008 11:56:33 PM (Mitteleuropäische Zeit, UTC+01:00)  #    Comments [0]    |   | 

Mit Hilfe des UI Automation Frameworks (UIA) können Oberflächen aus einer Testanwendung heraus "ferngesteuert" werden. Damit lassen sich z.B. UI-Tests bauen. Die Grundlagen hierzu habe ich in zwei Blogposts bzw. Webcasts beschrieben.

MSDN WebCast zum UI-Recording
MSDN WebCast zum UI-Testing mit dem UI Automation Framework

Wenn man sich mit dieser Technologie etwas intensiver beschäftigt wird man früher oder später auf ein paar Probleme stoßen. So veröffentlichen beispielsweise nicht alle Third-Party-Controls alle erforderlichen Funktionalitäten über die UIAutomation Patterns. Und leider gibt es auch bei Standard-Winforms-Controls noch ein paar Lücken. Ein Beispiel hierzu ist das MenuStrip-Control. Dieses Control bietet leider nicht die erforderlichen Events um die Auswahl eines Menüeintrags aufzuzeichnen. Und für die Auswahl eines Menüeintrages gibt es auch kein geeignetes Invoke-Pattern o.ä.

Das schöne am UI Automation Framework ist jedoch, dass man diese Funktionalitäten selber nachrüsten kann. Dies wollen wir nun am Beispiel des MenuStrips mal durchspielen. Zum Einsatz kommen sog. Serverside Provider. Wir gehen zunächst her und erstellen uns ein eigenes Control das wir von MenuStrip und entsprechenden Interfaces aus dem UIA ableiten.

public class ExtendedMenuStrip : MenuStrip, IRawElementProviderSimple, IValueProvider

 

Über das Interface IValueProvider geben wir an, dass das Control das ValuePattern implementieren soll. Wir verwenden hier das ValuePattern statt des InvokePatterns da wir ja angeben müssen, welches Menüelement aufgerufen werden soll.

Zunächst werden wir das Interface IRawElementProviderSimple implementieren.

   1: #region IRawElementProviderSimple Members
   2: public object GetPatternProvider(int patternId)
   3: {
   4:     if (patternId == ValuePatternIdentifiers.Pattern.Id)
   5:     {
   6:         return this;
   7:     }
   8:     else
   9:     {
  10:         return null;
  11:     }
  12: }
  13:  
  14: /// <summary>
  15: /// Get the value of properties
  16: /// </summary>
  17: /// <param name="propertyId"></param>
  18: /// <returns></returns>
  19: public object GetPropertyValue(int propertyId)
  20: {
  21:     if (propertyId == AutomationElementIdentifiers.ClassNameProperty.Id)
  22:     {
  23:         return "ExtendedMenuStrip";
  24:     }
  25:     else if (propertyId == AutomationElementIdentifiers.ControlTypeProperty.Id)
  26:     {
  27:         return ControlType.MenuBar.Id;
  28:     }
  29:  
  30:     if (propertyId == AutomationElementIdentifiers.HelpTextProperty.Id)
  31:     {
  32:         return "Help for ExtendedMenuStrip";
  33:     }
  34:  
  35:     if (propertyId == AutomationElementIdentifiers.AutomationIdProperty.Id)
  36:     {
  37:         return this.Name;
  38:     }
  39:  
  40:     if (propertyId == AutomationElementIdentifiers.IsEnabledProperty.Id)
  41:     {
  42:         return true;
  43:     }
  44:  
  45:     if (propertyId == AutomationElementIdentifiers.ItemStatusProperty.Id)
  46:     {
  47:         return SelectedItemID;
  48:     }
  49:  
  50:     else
  51:     {
  52:         return null;
  53:     }
  54: }
  55:  
  56: /// <summary>
  57: /// Get the host rawelement provider
  58: /// </summary>
  59: public IRawElementProviderSimple HostRawElementProvider
  60: {
  61:     get
  62:     {
  63:         return AutomationInteropProvider.HostProviderFromHandle(Handle);
  64:     }
  65: }
  66:     
  67: /// <summary>
  68: /// Get the provider options
  69: /// </summary>
  70: public ProviderOptions ProviderOptions
  71: {
  72:     get
  73:     {
  74:         return ProviderOptions.ServerSideProvider;
  75:     }
  76: }
  77:  
  78: #endregion

 

Die Methode GetPatternProvider gibt das Objekt selbst zurück, wenn ein Provider für ein ValuePattern angefordert wird. Da unser Control nur dieses Pattern implementiert, reagiert die Funktion nur auf dieses Pattern. Wir können das Control selbst zurückgeben, da dieses ja das ValuePattern implementiert. Altzernativ könnte man natürlich auch einen expliziten Provider definieren und hier zurückgeben. Die Methode GetPropertyValue gibt je nach übergebenen PropertyID einen entsprechenden Wert zurück. Hier wird z.B. die ID des Controls als AutomationID zurückgegeben. Die beiden Properties HostRawElementProvider und ProviderOptions sind readonly und geben einen Hostprovider bzw. den Typ der Providers zurück.

Die Implementierung des IValueProvider Interface ist ebenfalls recht einfach:

   1: #region IValueProvider Members       
   2: /// <summary>
   3: /// Get readonly as false
   4: /// </summary>
   5: public bool IsReadOnly
   6: {
   7:     get
   8:     {
   9:         return false;
  10:     }
  11: }
  12:  
  13: /// <summary>
  14: /// Set value: Invoke the click event of the item
  15: /// </summary>
  16: /// <param name="value"></param>
  17: public void SetValue(string value)
  18: {
  19:     object[] param = new object[1];
  20:     param[0] = null;
  21:     ToolStripMenuItem toolStripMenuItem = getToolStripMenuItemByName(this.Items, value);
  22:     if (toolStripMenuItem != null)
  23:         toolStripMenuItem.GetType().GetMethod("OnClick", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(toolStripMenuItem, param);
  24: }
  25:  
  26: /// <summary>
  27: /// Get the ID of the selected item
  28: /// </summary>
  29: public string Value
  30: {
  31:     get
  32:     {
  33:         return SelectedItemID;
  34:     }
  35: }
  36: #endregion

 

Entscheidend ist hier die Methode SetValue. Hier ermitteln wir das entsprechende Element innerhalb des MenuStrips (ToolStripMenuItem) und rufen hier einen Click-Event auf. Da die OnClick Methode nicht public ist, müssen wir hier Reflection verwenden um diese aufrufen zu können (siehe auch Events von WinForms Controls von Außen aufrufen). Das Aufrufen des Events ist an dieser Stelle notwendig, da ja mehrere Eventhandler registriert sein können und die wollen wir alle aufrufen. Durch den Click-Event verhält sich das Control am ähnlichsten zum Anklicken in der UI.

Nun brauchen wir noch einen Event der ausgelöst wird wenn ein Menüeintrag ausgewählt wird.

   1: /// <summary>
   2: /// Set eventhandlers
   3: /// </summary>
   4: /// <param name="items"></param>
   5: public void SetExtendedMenuStripEventHandlers(ToolStripItemCollection items)
   6: {
   7:     for (int i = 0; i <= items.Count - 1; i++)
   8:     {
   9:         ToolStripMenuItem toolStripMenuItem = items[i] as ToolStripMenuItem;
  10:         if(toolStripMenuItem != null)
  11:         {
  12:             toolStripMenuItem.Click += new EventHandler(ItemRaiseAutomationEvent);
  13:             SetExtendedMenuStripEventHandlers(toolStripMenuItem.DropDownItems);
  14:         }
  15:     }
  16: }
  17:  
  18: /// <summary>
  19: /// Raise automation event
  20: /// </summary>
  21: /// <param name="sender"></param>
  22: /// <param name="e"></param>
  23: private void ItemRaiseAutomationEvent(object sender, EventArgs e)
  24: {
  25:     if ((AutomationInteropProvider.ClientsAreListening))
  26:     {
  27:         AutomationEventArgs args = new AutomationEventArgs(InvokePatternIdentifiers.InvokedEvent);
  28:         this.SelectedItemID =((ToolStripMenuItem)sender).Name;
  29:         AutomationInteropProvider.RaiseAutomationEvent(InvokePatternIdentifiers.InvokedEvent, this, args);
  30:     }
  31: }

 

Die Methode SetExtendedMenuStripEventHandlers registriert auf jedem ToolStripMenuItem einen EventHandler. Dieser löst dann den AutomationEvent aus. Hier übergeben wir im Feld SelectedItemID den Name des ToolStripMenuItems das angeklickt wurde.

Nun müssen wir noch die Methode wndProc überschreiben damit diese bei WM_GETOBJECT den entsprechenden AutomationProvider zurückgibt.

   1: /// <summary>
   2: /// Process Windows-based messages
   3: /// </summary>
   4: /// <param name="m"></param>
   5: [PermissionSetAttribute(SecurityAction.Demand, Unrestricted = true)]
   6: protected override void WndProc(ref Message m)
   7: {
   8:     // 0x3D == WM_GETOBJECT
   9:     Int32 param = 0;
  10:     if (Int32.TryParse(m.LParam.ToString(), out param))
  11:     {
  12:         if ((m.Msg == 0x3D) && (param == AutomationInteropProvider.RootObjectId))
  13:         {
  14:             m.Result = AutomationInteropProvider.ReturnRawElementProvider(
  15:                 Handle, m.WParam, m.LParam, (IRawElementProviderSimple)this);
  16:             return;
  17:         }
  18:     }