MSDN Magazin > Home > Ausgaben > 2008 > February >  Testlauf: Die Benutzeroberflächenautomatis...
Testlauf
Die Benutzeroberflächenautomatisierungs-Bibliothek von Microsoft
Dr. James McCaffrey

Codedownload verfügbar unter: TestRun2008_02.exe (178 KB)
Browse the Code Online
Es gibt mehrere Verfahren, mit denen Sie die Benutzeroberfläche einer Windows®-Anwendung testen können. Sie können zum Beispiel System.Reflection-Klassen verwenden, um Microsoft® .NET Framework-Anwendungen zu testen, oder Sie können sowohl .NET-Anwendungen als auch systemeigene Anwendungen durch Aufrufen von Win32® API-Funktionen wie FindWindow testen – entweder mit nicht verwaltetem C++ oder mit C# oder Visual Basic® unter Verwendung der P/Invoke-Methode.
Im Artikel dieses Monats wird aufgezeigt, wie Sie in die Automatisierung von Benutzeroberflächentests einsteigen können, und zwar mithilfe der neuen Benutzeroberflächenautomatisierungs-Bibliothek von Microsoft, die in .NET Framework 3.0 als Teil von Windows Presentation Foundation (WPF) enthalten ist. Es handelt sich dabei um eine Art Nachfolger der Microsoft Active Accessibility (MSAA)-Bibliothek, die ursprünglich für Eingabehilfen entworfen wurde, sich aber auch für die Benutzeroberflächenautomatisierung als nützlich erwiesen hat und entsprechend angepasst wurde. Andererseits wurde die Benutzeroberflächenautomatisierungs-Bibliothek von Anfang an sowohl für Eingabehilfen als auch für Benutzeroberflächentest-Automatisierungsaufgaben entworfen. Sie können die Benutzeroberflächenautomatisierungs-Bibliothek verwenden, um Win32-, .NET Windows Forms- und WPF-Anwendungen auf Hostcomputern zu testen, auf denen Betriebssysteme ausgeführt werden, die .NET Framework 3.0 unterstützen (z. B. Windows XP, Windows Vista®, Windows Server® 2003 und Windows Server 2008).
Um gleich zum Kern zu kommen: Ich glaube, dass die Entwicklung der Benutzeroberflächenautomatisierungs-Bibliothek einer der bislang wichtigsten Fortschritte in der Testautomatisierung ist und dass sich die Bibliothek innerhalb kurzer Zeit zum beliebtesten Verfahren für die Benutzeroberflächentest-Automatisierung für Windows-Anwendungen entwickeln wird. Verglichen mit alternativen Ansätzen zur Benutzeroberflächenautomatisierung ist die Benutzeroberflächenautomatisierungs-Bibliothek leistungsfähiger, oft leichter zu verwenden und konsistenter. In ganz ähnlicher Weise wie .NET Framework die Softwareanwendungsentwicklung verändert hat, wird aus meiner Sicht die Benutzeroberflächenautomatisierungs-Bibliothek wahrscheinlich die Benutzeroberflächentest-Automatisierung revolutionieren.
Anhand eines Bildschirmfotos können Sie am besten sehen, worauf ich hinaus will. Abbildung 1 zeigt eine einfache zu testende Windows-Anwendung. Die Anwendung heißt „StatCalc“ und berechnet das arithmetische, geometrische oder harmonische Mittel eines Satzes ganzer Zahlen. Das arithmetische Mittel ist einfach der Durchschnitt. Zum Beispiel ist 45 Zoll das arithmetische Mittel von 30 Zoll und 60 Zoll. Das geometrische Mittel wird für Verhältnisse verwendet. Zum Beispiel ist 42,4264:1 das geometrische Mittel von 30:1 und 60:1. Das harmonische Mittel wird für Raten und Ähnliches verwendet. Zum Beispiel ist 40 km/h das harmonische Mittel von 30 km/h und 60 km/h bei einer festen Entfernung.
Abbildung 1 Beispielanwendung in einem automatisierten Benutzeroberflächentest (Klicken Sie zum Vergrößern auf das Bild)
Die in Abbildung 1 gezeigte Benutzeroberflächentest-Automatisierung ist eine Konsolenanwendung, die verschiedene Aufgaben ausführt: Sie startet die zu testende Anwendung, verwendet die Benutzeroberflächenautomatisierungs-Bibliothek, um Verweise auf die Anwendung und die Benutzersteuerelemente der Anwendung zu erhalten, simuliert einen Benutzer, der „30“ und „60“ eingibt, und simuliert schließlich das Klicken auf das Schaltflächensteuerelement „Calculate“ (Berechnen). Die Testautomatisierung prüft dann den sich ergebenden Status der Anwendung durch Untersuchen des TextBox-Ergebnissteuerelements für einen erwarteten Wert, und meldet dann einen Erfolg oder ein Fehlschlagen. Das Bildschirmfoto wurde aufgezeichnet, kurz bevor die Testautomatisierung die zu testende Anwendung geschlossen hat.
Im übrigen Teil dieses Artikels wird die zu testende StatCalc-Windows-Anwendung kurz beschrieben und erläutert, wie die Anwendung im Test gestartet wird, wie mithilfe der Benutzeroberflächenautomatisierungs-Bibliothek Verweise auf die Anwendung und die Benutzersteuerelemente abgerufen werden, wie Benutzeraktionen simuliert werden und wie der Anwendungsstatus überprüft wird. Außerdem wird beschrieben, wie Sie das hier vorgestellte Testsystem erweitern und ändern können, damit es Ihre persönlichen Anforderungen erfüllt. Meiner Meinung nach werden Sie die Verwendung der neuen Benutzeroberflächenautomatisierungs-Bibliothek als wertvolle Ergänzung Ihres Testtoolsets ansehen.

Einblick in StatCalc
Lassen Sie uns kurz einen Blick auf die zu testende Anwendung werfen, damit Sie verstehen, was das Ziel der Testautomatisierung ist. Sie werden auch sehen, warum es nützlich, aber nicht unbedingt erforderlich ist, beim Schreiben der Benutzeroberflächentest-Automatisierung auf den Quellcode der Anwendung zugreifen zu können.
Die StatCalc-Anwendung ist ein einfaches Windows-basiertes Formular. Zum Codieren der Anwendung wurde C# verwendet, aber die Benutzeroberflächenautomatisierungs-Bibliothek funktioniert auch mit Win32- und WPF-Anwendungen. Der Einfachheit halber wurden die standardmäßigen Visual Studio®-Steuerelementnamen verwendet: Form1, label1, textBox1, groupBox1, radioButton1, radioButton2, radioButton3, button1 und textBox2. Es wurden Menüelemente oberster Ebene, „File“ (Datei) und „Help“ (Hilfe), hinzugefügt, wobei das MenuStrip-Steuerelement (verfügbar seit .NET Framework 2.0) statt des älteren MainMenu-Steuerelements verwendet wurde. Die Funktionalität der StatCalc-Anwendung ist in der button1_Click-Methode enthalten (siehe Abbildung 2).
private void button1_Click(object sender, EventArgs e) {
  string[] sVals = textBox1.Text.Split(' ');
  int[] iVals = new int[sVals.Length];
  for (int i = 0; i < iVals.Length; ++i)
    iVals[i] = int.Parse(sVals[i]);

  if (radioButton1.Checked) {
    double sum = 0.0;
    foreach (int v in iVals)
      sum += v;
    double result = (double)(sum / iVals.Length);
    textBox2.Text = result.ToString("F4");
  }
  else if (radioButton2.Checked) {
    double product = 1.0;
    foreach (int v in iVals)
    product *= (double)v;
    double result = NthRoot(product, iVals.Length);
    textBox2.Text = result.ToString("F4");
  }
  else if (radioButton3.Checked) {
    double sum = 0.0;
    foreach (int v in iVals)
      sum += (1/ (double)v);
    double result = (double)(iVals.Length / sum);
    textBox2.Text = result.ToString("F4");
  }
}

Wenn ein Benutzer auf die Schaltfläche „Calculate“ (Berechnen) klickt, wird die Steuerung der button1_Click-Methode übergeben, die den Wert in textBox1 als einzelne Zeichenfolge abruft. Anschließend wird die String.Split-Methode verwendet, um jeden Wert in ein Zeichenfolgenarray zu analysieren. Als Nächstes konvertiert der Klickhandler das Zeichenfolgenarray in ein int-Array. Die button1_Click-Logik verzweigt sich in Abhängigkeit vom ausgewählten radioButton-Steuerelement, berechnet den entsprechenden Mittelwert als double-Typ und zeigt dann das Ergebnis im textbox2-Steuerelement als Zeichenfolge an, die auf vier Dezimalstellen formatiert ist.
Beim Berechnen des geometrischen Mittels ruft die Anwendung eine lokale Hilfsmethode namens „NthRoot“ auf, die wie folgt definiert ist:
private static double NthRoot(double x, int n) {
  return Math.Exp( Math.Log(x) / (double)n );
}
An dieser Stelle wird keine Fehlerprüfung durchgeführt, damit der Anwendungscode kurz und leicht verständlich ist.
Als allgemeine Faustregel gilt: Beim Schreiben der Testautomatisierung mithilfe der Benutzeroberflächenautomatisierungs-Bibliothek benötigen Sie keinen Zugriff auf den Quellcode des zu testenden Systems. In den meisten Fällen greifen Sie auf Schaltflächen über ihre Beschriftungen (z. B. Calculate) statt über ihre interne Name-Eigenschaft (z. B. button1) zu. Einige Steuerelemente, beispielsweise textbox-Steuerelemente, haben jedoch keine Beschriftungen.
Um auf Steuerelemente ohne Beschriftung mit der Benutzeroberflächenautomatisierung zugreifen zu können, ist es notwendig, die Reihenfolge der impliziten Indexe zu kennen. Das ist die Reihenfolge, in der Steuerelemente in das Form-Hauptsteuerelement (oder übergeordnete Steuerelement) geladen werden, wenn Form initialisiert wird. Die Reihenfolge der impliziten Indexe ist nicht unbedingt identisch mit der Reihenfolge, in der Sie dem Form-Objekt Steuerelemente zur Entwurfszeit hinzufügen, und sie ist auch nicht mit der Registerkarten-Indexreihenfolge identisch.
Sie können die Reihenfolge der impliziten Indexe von Steuerelementen ohne Beschriftung am einfachsten durch Anzeigen des Anwendungsquellcodes bestimmen. Wenn Sie zum Beispiel die Form1.Designer.cs-Datei für die StatCalc-Anwendung untersuchen und den vom Windows Forms-Designer generierten Codebereich erweitern, wird dieser Code angezeigt:
this.Controls.Add(this.button1);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.label1);
this.Controls.Add(this.textBox2);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.menuStrip1);
Dies bedeutet, dass button1 den impliziten Index 0 hat, groupBox1 den Index 1 und so weiter. Beachten Sie, dass der implizite Index des textBox2-Steuerelements niedriger ist als der des textBox1-Steuerelements (obwohl beim Entwurf der StatCalc-Anwendung textBox1 vor textBox2 im Form-Objekt platziert wurde). Auch radioButton1, radioButton2 und radioButton3 sind untergeordnete Steuerelemente des groupBox1-Steuerelements statt von Form1. Wie Sie bald sehen werden, ist die Ebenentiefe in der Steuerelementstruktur wichtig, wenn Sie Testautomatisierung mithilfe der Benutzeroberflächenautomatisierungs-Bibliothek schreiben.
Wenn Sie auf die Quelle der zu testenden Anwendung zugreifen können, können Sie deshalb leicht die Reihenfolge der impliziten Indexe für Steuerelemente bestimmen. Aber was ist, wenn Sie keinen Zugriff auf den Quellcode haben? Eine Möglichkeit, die Reihenfolge der impliziten Indexe von Steuerelementen zu bestimmen, besteht in der Verwendung des Spy++-Tools, das in Visual Studio enthalten ist. Nach dem Starten von Spy++ können Sie Strg+F5 drücken und das Suchtool der grafischen Benutzeroberfläche auf ein Steuerelement/Fenster ziehen, um ausführliche Informationen zum Steuerelement/Fenster zu erhalten. Abbildung 3 zeigt das Ergebnis, wenn Spy++ auf das textBox1-Steuerelement angewendet wird. Im Hintergrund können Sie sehen, dass textBox1 das Handle 00080820 hat. Im Vordergrund hat das nächste Steuerelement nach textBox1 die Beschriftung menuStrip1, und das Steuerelement vor textBox1 hat keine Beschriftung (es handelt sich um das textBox2-Steuerelement).
Abbildung 3 Ermitteln der Reihenfolge der impliziten Indexe mit Spy++ (Klicken Sie zum Vergrößern auf das Bild)
So besteht selbst dann, wenn Sie keinen Zugriff auf den Quellcode der zu testenden Anwendung haben, die Möglichkeit, die Reihenfolge der impliziten Indexe für die Steuerelemente der ganzen Anwendung abzuleiten, indem Sie jedes Steuerelement mittels Spy++ untersuchen.
Das manuelle Testen selbst dieser winzigen StatCalc-Anwendung über die Benutzeroberfläche wäre langweilig, fehleranfällig, zeitaufwändig und ineffizient. Sie müssten einige Eingaben vornehmen, auf das Schaltflächen-Steuerelement „Calculate“ klicken, den Ergebniswert visuell überprüfen und den Erfolg bzw. das Fehlschlagen manuell in einer Tabelle oder Datenbank festhalten. Ein viel besserer Ansatz besteht darin, die Benutzeroberflächenautomatisierungs-Bibliothek zu verwenden, um einen Benutzer zu simulieren, der die Anwendung verwendet, und dann zu bestimmen, ob die Anwendung richtig reagiert hat. Durch Automatisieren aufwändiger, monotoner Tests können Sie Zeit für interessantere und nützlichere manuelle Testfälle gewinnen, bei denen Ihre Erfahrung und Intuition eine wichtige Rolle spielen.

Der Code für die Benutzeroberflächentest-Automatisierung
Die Gesamtstruktur der Testumgebung ist in Abbildung 4 dargestellt. Nach dem Starten von Visual Studio erstellen Sie ein neues Konsolenanwendungsprogramm. Ich habe mich entschieden, C# zu verwenden, aber es sollte für Sie nicht schwer sein, meinen Testautomatisierungscode auf Wunsch in Visual Basic oder andere .Net-kompatible Sprachen zu konvertieren. Als Nächstes fügen Sie der UIAutomationClient.dll- und der UIAutomationTypes.dll-Bibliothek Projektverweise hinzu. Diese Bibliotheken sind Teil von .NET Framework 3.0 und sind in der Regel im Verzeichnis „%PROGRAMFILES%\Reference Assemblies\Microsoft\Framework\v3.0“ gespeichert.
using System;
using System.Windows.Automation;
using System.Diagnostics;
using System.Threading;

namespace TestScenario {
  class Program {
    static void Main(string[] args) {
      try {
        Console.WriteLine("\nBegin WPF UIAutomation test run\n");
        // launch StatCalc application
        // get refernce to main Form control
        // get references to user controls
        // manipulate application
        // check resulting state and determine pass/fail
        Console.WriteLine("\nEnd test run\n");
      }
      catch(Exception ex) {
        Console.WriteLine("Fatal error: " + ex.Message);
      }
    } 
  } 
} 

Die Architektur der Benutzeroberflächenautomatisierungs-Bibliothek verwendet eine Clientserverperspektive und Benennungskonventionen. Vom Standpunkt einer Benutzeroberflächentest-Automatisierung aus betrachtet, bedeutet das, dass die zu testende Anwendung Server genannt und die Testumgebung als Client betrachtet wird – der Testumgebungsclient fordert Benutzeroberflächeninformationen von der zu testenden Anwendung (dem Server) an. Die UIAutomationClient.dll-Bibliothek ist im Grunde die von Benutzeroberflächenautomatisierungs-Clients verwendete Testautomatisierungsbibliothek. Zusätzlich enthält die UIAutomationTypes.dll-Bibliothek verschiedene Typdefinitionen, die sowohl von UIAutomationClient.dll als auch von anderen Benutzeroberflächenautomatisierungs-Serverbibliotheken verwendet werden.
Neben der UIAutomationClient.dll- und der UIAutomationTypes.dll-Bibliothek werden Sie auch die UIAutomationClientSideProvider.dll- und die UIAutomationProvider.dll-Bibliothek sehen. Die UIAutomationClientSideProvider.dll-Bibliothek enthält Code für die Arbeit mit Steuerelementen, die nicht mit Automatisierungsunterstützung erstellt wurden – dazu könnten Legacysteuerelemente und benutzerdefinierte .NET-Steuerelemente gehören. Weil die Beispielanwendung alle Standardsteuerelemente verwendet (die zur Unterstützung der Benutzeroberflächenautomatisierung erstellt wurden), ist diese Bibliothek nicht erforderlich. Die UIAutomationProvider.dll-Bibliothek ist ein Satz von Schnittstellendefinitionen, der von Entwicklern verwendet werden kann, die benutzerdefinierte Benutzeroberflächensteuerelemente erstellen und wünschen, dass die Benutzeroberflächenautomatisierungs-Bibliothek auf die Steuerelemente zugreifen kann.
Nach Hinzufügen der Projektverweise ist der System.Windows.Automation-Namespace nun für das Testumgebungsprogramm sichtbar, und Sie können dem Namespace eine using-Anweisung hinzufügen. Der Bequemlichkeit halber werden using-Anweisungen hinzugefügt, die auf den System.Diagnostics-Namespace und den System.Threading-Namespace zeigen (damit die Process-Klasse bzw. die Thread.Sleep-Methode problemlos verwendet werden kann). Wie bei Testautomatisierungen üblich, wird die Umgebung mit einem try-/catch-Block auf oberster Ebene umschlossen, um etwaige schwere Fehler zu behandeln. Testautomatisierung ist von Natur aus sehr schwierig, und Ausnahmen sind gewissermaßen die Regel statt die Ausnahme.
Die Testautomatisierungscode beginnt mit dem Start der zu testenden Anwendung:
Console.WriteLine("\nBegin WPF UIAutomation test run\n");
Console.WriteLine("Launching StatCalc application");
Process p =
    Process.Start("..\\..\\..\\StatCalc\\bin\\Debug\\StatCalc.exe");
Beachten Sie, dass die Prozessinformationen der zu testenden Anwendung aufgezeichnet werden. Als Nächstes wird ein Verweis auf das Desktopfenster des Hostcomputers als AutomationElement abgerufen:
AutomationElement aeDesktop = AutomationElement.RootElement;
Die meisten für Testautomatisierung mithilfe der Benutzeroberflächenautomatisierungs-Bibliothek interessanten Objekte sind vom Typ „AutomationElement“. Hier kann ein Verweis auf das visuelle Element oberster Ebene (den Desktop) abgerufen werden, indem Sie die statische RootElement-Eigenschaft von AutomationElement verwenden. Beim Durchführen der Benutzeroberflächentest-Automatisierung ist es nützlich, sich jede visuelle Entität (Steuerelemente, Fenster und so weiter) auf dem Hostcomputer als Teil einer hierarchischen Baumstruktur mit dem Desktopfenster als Stammelement vorzustellen. Dies ist ein nützliches Prinzip für die meisten Arten der Benutzeroberflächenautomatisierung einschließlich der Automatisierung mit der Benutzeroberflächenautomatisierungs-Bibliothek.
Als Nächstes muss ein Verweis auf das Form-Hauptobjekt abgerufen werden, das im Grunde die StatCalc-Anwendung ist. Eine einfache Möglichkeit dafür lässt sich wie folgt veranschaulichen:
// naive approach
Thread.Sleep(5000);
AutomationElement aeForm =
    AutomationElement.FromHandle(p.MainWindowHandle);
if (aeForm == null)
    Console.WriteLine(
        "Could not find the StatCalc Form");
Halten Sie für einige Sekunden an, um der zu testenden Anwendung Zeit zum Starten zu geben, und verwenden Sie dann die FromHandle-Methode, um einen Verweis auf die Anwendung abzurufen. Denken Sie daran, dass die Prozessinformationen der zu testenden Anwendung beim Starten der Anwendung gespeichert wurden.
Obwohl dieses Verfahren funktioniert, gibt es zwei kleine Probleme bei diesem Ansatz. Zum einen gibt es keine gute Möglichkeit zu erraten, wie lange vor dem Zugriff auf das Form-Objekt gewartet werden muss. Deshalb müssen Sie konservativ vorgehen und eine sehr lange Verzögerungszeit verwenden. Dadurch wird die Testautomatisierung stark verlangsamt, insbesondere wenn Sie hunderte oder tausende von Testfällen ausführen. Zum anderen wird bei diesem Ansatz angenommen, dass die Testumgebung die zu testende Anwendung startet (was Ihnen ermöglicht, die Prozessinformationen zur Anwendung zu speichern). In einigen Testsituationen möchten Sie aber, dass die Anwendung bereits ausgeführt wird. Daher haben Sie keinen sofortigen Zugriff auf die Prozessinformationen der zu testenden Anwendung (obwohl Sie diese Informationen programmgesteuert abrufen können).
In den meisten Situationen besteht der bessere Ansatz im Abrufen eines Verweises auf das Form-Objekt der zu testenden Anwendung als AutomationElement in der Verwendung von Code wie in Abbildung 5 gezeigt. Statt für einen willkürlichen Zeitraum anzuhalten, bevor blind ein Verweis auf die Anwendung angefordert wird, versuchen Sie, den Verweis innerhalb einer Schleife mit einer geringen Verzögerung (in diesem Fall 100 Millisekunden) abzurufen. Verfolgen Sie nach, wie oft Ihre Umgebung in die Warteschleife eintritt, und beenden Sie den Vorgang, wenn Sie einen Verweis ungleich null erhalten (was bedeutet, dass das Form-Objekt gefunden wurde) oder wenn die maximale Anzahl der Versuche (in diesem Fall 50) überschritten wird.
// a better approach
AutomationElement aeForm = null;
int numWaits = 0;
do 
{
    Console.WriteLine("Looking for StatCalc . . . ");
    aeForm = aeDesktop.FindFirst(TreeScope.Children,
        new PropertyCondition(
            AutomationElement.NameProperty, 
            "StatCalc"));
  
    ++numWaits;
    Thread.Sleep(100);
} 

while (aeForm == null && numWaits < 50);

if (aeForm == null)
    throw new Exception("Failed to find StatCalc");
else
    Console.WriteLine("Found it!");

Darüber hinaus verwenden Sie statt FromHandle die FindFirst-Methode, die nach dem ersten verfügbaren Verweis auf ein AutomationElement sucht, das die angegebene Bedingung erfüllt, die als ein Paar Argumente übergeben wird. Das erste Argument, TreeScope.Children, weist FindFirst an, sich nur unmittelbar untergeordnete Steuerelemente statt sämtlicher Nachfolgerelemente seines Kontexts (in diesem Fall aeDesktop) anzuschauen.
Das zweite Argument für FindFirst ist eine Bedingung:
new PropertyCondition(AutomationElement.NameProperty, "StatCalc")
Dieser Code bedeutet: „Das Automatisierungselement mit Beschriftung (Name-Eigenschaft) ist gleich StatCalc.“ Statt für das Identifizieren von Steuerelementen einen traditionellen Zeichenfolgenansatz für Bedingungen zu verwenden, z. B. Property == StatCalc, haben die Designer der Benutzeroberflächenautomatisierungs-Bibliothek entschieden, eine PropertyCondition-Klasse zu erstellen. Dieser Ansatz erscheint zunächst vielleicht ein wenig unhandlich und zu umfangreich, erweist sich aber ziemlich schnell als gut überschaubar.
Für das Identifizieren von Steuerelementen eine vollständig objektorientierte PropertyCondition-Klasse statt einfacher Zeichenfolgen zu verwenden, erfordert beim Schreiben einer Testautomatisierung im Grunde mehr Code, aber es bietet auch mehrere Vorteile. Ein PropertyCondition-Element macht die Absicht Ihres Testautomatisierungscodes wahrscheinlich sehr klar (zumindest nachdem Sie sich mit dem Paradigma vertraut gemacht haben). Da ein PropertyCondition-Objekt stärker typisiert ist als eine Zeichenfolge, ermöglicht das Verwenden von PropertyCondition einer integrierten Entwicklungsumgebung wie Visual Studio, Entwurfszeit-IntelliSense®-Hilfe zur automatischen Vervollständigung bereitzustellen. Es ermöglicht dem Compiler außerdem, eine bessere Fehlerüberprüfung durchzuführen.
Nach dem Abrufen eines Verweises auf das Form-Hauptobjekt der zu testenden Anwendung rufen Sie Verweise auf sämtliche Benutzersteuerelemente ab. Um zum Beispiel einen Verweis auf das Schaltflächen-Steuerelement „Calculate“ abzurufen, können Sie Folgendes schreiben:
Console.WriteLine("Finding all user controls");
AutomationElement aeButton = aeForm.FindFirst(TreeScope.Children,
  new PropertyCondition(AutomationElement.NameProperty, "Calculate"));
Das Muster für das Abrufen eines Benutzersteuerelements ist identisch mit dem für das Abrufen des Form-Steuerelements. Konsistenz wie diese ist ein Vorteil der Benutzeroberflächenautomatisierungs-Bibliothek im Vergleich zu vielen anderen Automatisierungsbibliotheken. Wie es bei den meisten Codierarten der Fall ist, gibt es bei der Benutzeroberflächenautomatisierung gewöhnlich mehrere Wege, ein Ziel zu erreichen. Statt des vorhergehenden Codes zum Abrufen eines Verweises auf das Schaltflächen-Steuerelement „Calculate“ können Sie zum Beispiel auch Folgendes schreiben:
AutomationElement aeButton = aeForm.FindFirst(TreeScope.Children,
  new PropertyCondition(AutomationElement.ControlTypeProperty,
    ControlType.Button));
Statt NameProperty zu verwenden, können Sie ControlTypeProperty mit dem Wert „ControlType.Button“ verwenden. Dies bedeutet: „Rufe das erste Schaltflächen-Steuerelement ab, das in Form gefunden wird.“ Dieser Ansatz funktioniert hier nur, weil nur ein Schaltflächen-Steuerelement im Form-Objekt vorliegt.
Die ControlType-Klasse ist ein entscheidendes Element der Microsoft-Benutzeroberflächenautomatisierung. Die Klasse ist sehr einfach und kann zur Identifizierung des Typs eines beliebigen Steuerelements verwendet werden. Das Abrufen von Verweisen auf textbox-Steuerelemente, die keine Beschriftungen oder Namen haben, erfordert ein anderes Verfahren:
AutomationElementCollection aeAllTextBoxes =
  aeForm.FindAll(TreeScope.Children,
    new PropertyCondition(AutomationElement.ControlTypeProperty,
      ControlType.Edit));

AutomationElement aeTextBox1 = aeAllTextBoxes[1];
AutomationElement aeTextBox2 = aeAllTextBoxes[0];
Hier verwenden Sie die FindAll-Methode, um alle textbox-Steuerelemente abzurufen, die direkte untergeordnete Elemente des Form-Steuerelements sind. Beachten Sie, dass ControlType.Edit verwendet wird, um, anders als Sie vielleicht erwartet haben, textbox-Steuerelemente statt etwas wie ControlType.TextBox anzugeben. Speichern Sie alle textbox-Steuerelemente in einem AutomationElementCollection-Element, auf das Sie dann über den Index zugreifen können.
Erinnern Sie sich an den vorherigen Abschnitt: Das textBox2-Steuerelement hat einen niedrigeren impliziten Index als das textBox1-Steuerelement. Deshalb befindet sich textBox2 an der Stelle [0] in der Sammlung und textBox1 an der Stelle [1]. Es gibt einige weitere Ansätze, die Sie verwenden können, um Verweise auf Steuerelemente ohne Beschriftung abzurufen, aber die FindAll-Methode ist oft eine gute Wahl.
So rufen Sie einen Verweis auf das radioButton1-Steuerelement ab:
AutomationElement aeRadioButton1 =
  aeForm.FindFirst(TreeScope.Descendants,
    new PropertyCondition(AutomationElement.NameProperty,
      "Arithmetic Mean"));
Beachten Sie, dass TreeScope.Descendants statt TreeScope.Children verwendet wird, weil der aktuelle Kontext das Form-Objekt ist. Die radioButton-Steuerelemente sind untergeordnete Elemente des groupBox1-Steuerelements, das ein untergeordnetes Element des Form1-Steuerelements ist. Deshalb sind die radioButton-Steuerelemente Nachfolgerelemente von Form1, aber keine untergeordneten Elemente. Alternativ könnten Sie zuerst einen Verweis auf das groupBox1-Steuerelement (als untergeordnetes Element von Form1) und dann einen Verweis auf radioButton1 (als untergeordnetes Element des groupBox1-Steuerelements) abrufen.
Nachdem Sie Verweise auf radioButton2 (geometrisches Mittel) und radioButton3 (harmonisches Mittel) auf genau die gleiche Weise wie bei radioButton1 abgerufen haben, können Sie mit der Ausführung der zu testenden Anwendung beginnen. So simulieren Sie einen Benutzer, der etwas in das textBox1-Steuerelement eingibt:
Console.WriteLine("\nSetting input to '30 60'");
ValuePattern vpTextBox1 =
  (ValuePattern)aeTextBox1.GetCurrentPattern(ValuePattern.Pattern);
vpTextBox1.SetValue("30 60");
Die Verwendung der SetValue-Methode ist wahrscheinlich nicht überraschend, aber beachten Sie, dass auf SetValue nicht direkt über das aeTextBox1-Objekt zugegriffen wird. Stattdessen wird ein ValuePattern-Mittlerobjekt verwendet. Das Konzept der AutomationPattern-Objekte wie ValuePattern ist wahrscheinlich der größte konzeptionelle Stolperstein für Programmierer, für die die Benutzeroberflächenautomatisierungs-Bibliothek noch neu ist. Sie können sich Musterobjekte als eine abstrakte Möglichkeit vorstellen, die Steuerelementfunktionalität anzuzeigen, die vom Typ oder dem Erscheinungsbild des Steuerelements unabhängig ist. Anders ausgedrückt: Sie können spezifische AutomationPattern-Instanzen wie ValuePattern verwenden, um spezifische Steuerelementfunktionalität zu ermöglichen.
Vereinfachen Sie die Dinge weiter, indem Sie sich vorstellen, dass das ControlType-Element eines Steuerelements die Art des Steuerelements anzeigt und dass das Pattern-Element eines Steuerelements anzeigt, wozu das Steuerelement in der Lage ist. An dieser Stelle sollte unbedingt angemerkt werden, dass ein Steuerelement mehrere Muster unterstützen kann. So verwenden Sie ein AutomationPattern-Element, um das radioButton2-Steuerelement auszuwählen:
Console.WriteLine("Selecting 'Geometric Mean' ");
SelectionItemPattern ipSelectRadioButton2 =
  (SelectionItemPattern)aeRadioButton2.GetCurrentPattern(
    SelectionItemPattern.Pattern );
ipSelectRadioButton2.Select();
Dieses Mal wird SelectionItemPattern verwendet, um eine Auswahl zu ermöglichen. Der Name der GetCurrentPattern-Methode ist für Benutzer, für die die Benutzeroberflächenautomatisierungs-Bibliothek noch neu ist, oft verwirrend. Von einem Testautomatisierungsstandpunkt aus dient die Methode dem Festlegen, nicht dem Abrufen eines angegeben AutomationPattern-Elements. Aber aus der Perspektive eines Clientservers ruft der Automatisierungsclientcode eine bestimmte Eigenschaft aus dem Servercode der zu testenden Anwendung ab. Der Code, der zum Simulieren eines Klicks auf das Schaltflächen-Steuerelement „Calculate“ verwendet wird, sollte dies veranschaulichen:
  Console.WriteLine("Clicking on Calculate button");
  InvokePattern ipClickButton1 =
    (InvokePattern)aeButton.GetCurrentPattern(
    InvokePattern.Pattern);
  ipClickButton1.Invoke();
  Thread.Sleep(1500);
Hier wird InvokePattern verwendet, um einen Schaltflächenklick zu ermöglichen und dann den Klick mithilfe der Invoke-Methode auszuführen. Beachten Sie, dass für 1,5 Sekunden angehalten wird, um der Anwendung Zeit zum Antworten zu geben. Sie könnten auch in eine Verzögerungsschleife eintreten und mithilfe eines Verfahrens, das gleich vorgeführt wird, regelmäßig prüfen, ob das textBox2-Ergebnisfeld leer ist oder nicht. Es gibt 18 AutomationPattern-Typen, die in Abbildung 6 aufgeführt sind. Um zu wissen, wie Ihre zu testende Anwendung mithilfe der Microsoft-Benutzeroberflächenautomatisierungs-Bibliothek ausgeführt werden kann, ist es erforderlich, die AutomationPattern-Klassen zu verstehen.

DockPattern
ExpandCollapsePattern
GridPattern
GridItemPattern
InvokePattern
MultipleViewPattern
RangeValuePattern
ScrollPattern
ScrollItemPattern
SelectionPattern
SelectionItemPattern
TablePattern
TableItemPattern
TextPattern
TogglePattern
TransformPattern
ValuePattern
WindowPattern
An diesem Punkt im Testautomatisierungscode wurde die zu testende Anwendung gestartet, „30 60“ in das Eingabesteuerelement (textBox1) eingegeben, das Steuerelement für das geometrische Mittel (radioButton2) ausgewählt und auf das Calculate-Steuerelement (button1) geklickt. Untersuchen Sie jetzt das textBox2-Steuerelement, um zu sehen, ob Sie einen erwarteten Wert erhalten haben:
Console.WriteLine("\nChecking textBox2 for '42.4264'");
TextPattern tpTextBox2 =
  (TextPattern)aeTextBox2.GetCurrentPattern(TextPattern.Pattern);
string result = tpTextBox2.DocumentRange.GetText(-1);
Es wird wieder ein AutomationPattern-Element verwendet, dieses Mal TextPattern, um einen Aufruf an die GetText-Methode vorzubereiten. Beachten Sie, dass GetText indirekt über eine DocumentRange-Eigenschaft aufgerufen wird, die einen Textbereich zurückgibt, der den Haupttext eines „Dokuments“ einschließt, das in diesem Fall ein textbox-Element ist. Das Argument für GetText soll anzeigen, dass keine Beschränkung für die Länge der Rückgabezeichenfolge festgelegt wird.
Eine alternative Möglichkeit für das Lesen des Inhalts des textBox2-Steuerelements besteht darin, die GetCurrentPropertyValue-Methode wie folgt zu verwenden:
string result =
  (string)aeTextBox2.GetCurrentPropertyValue(
    ValuePattern.ValueProperty);
Die GetCurrentPropertyValue-Methode gibt die aktuelle Eigenschaft für ein AutomationElement-Steuerelement zurück, wenn eine Eigenschaft festgelegt wurde, oder gibt den Wert der Standardeigenschaft für das Steuerelement zurück, wenn die Eigenschaft des Kontextsteuerelements nicht festgelegt wurde. In diesem Fall wurde keine Eigenschaft für das textBox2-Steuerelement festgelegt. Deshalb gibt GetCurrentPropertyValue mit dem Argument „ValueProperty“ den Wert der Standardeigenschaft für das textBox2-Steuerelement zurück, das die Text-Eigenschaft ist. Das Verwenden von GetCurrentProperty spart Zeit beim Codieren, kann aber riskant sein, weil sich die aktuelle Eigenschaft eines Steuerelements zur Laufzeit ändern kann.
Vergleichen Sie das Ergebnis der Berechnung mit einem erwarteten Wert, um den Erfolg des Testszenarios zu bestimmen:
if (result == "42.4264") {
  Console.WriteLine("Found it");
  Console.WriteLine("\nTest scenario: Pass");
}
else {
  Console.WriteLine("Did not find it");
  Console.WriteLine("\nTest scenario: *FAIL*");
}
Jetzt können Sie die zu testende Anwendung schließen:
Console.WriteLine("\nClosing application in 5 seconds");
Thread.Sleep(5000);
WindowPattern wpCloseForm =
  (WindowPattern)aeForm.GetCurrentPattern(WindowPattern.Pattern);
wpCloseForm.Close();
Hier wird die Close-Methode der WindowPattern-Klasse verwendet, was einen thematischen Ansatz für die Benutzeroberflächenautomatisierungs-Bibliothek darstellt.
Als Alternative für das Schließen der zu testenden Anwendung können Sie das Menüelement „File“ (Datei) | „Exit“ (Beenden) ändern, und zwar angefangen mit Folgendem:
AutomationElement aeMenu =
  aeForm.FindFirst(TreeScope.Children,
    new PropertyCondition(AutomationElement.ControlTypeProperty,
      ControlType.Menu));
Der ControlType.Menu-Typ ist jedoch das .Net Framework 1.1-MainMenu-Steuerelement und funktioniert nicht mit dem neueren MenuStrip-Steuerelement. Da die zu testende Anwendung MenuStrip verwendet, kann der ControlType.Menu-Typ nicht verwendet werden. Dies ist eine seltene Ausnahme. Die Benutzeroberflächenautomatisierungs-Bibliothek unterstützt fast alle Benutzersteuerelemente und bietet auch Automatisierungsunterstützung für benutzerdefinierte Steuerelemente durch Funktionalität, die in der UIAutomationProvider.dll-Bibliothek enthalten ist. Jetzt ist die Testautomatisierung vollständig, und Sie können das Testfallergebnis aufzeichnen und ein anderes Testszenario starten.

Erweitern der Testumgebung
Der Code, der im Artikel dieses Monats vorgestellt wird, gibt Ihnen eine solide Grundlage für die ersten Schritte beim Erstellen Ihrer eigenen Testautomatisierung mithilfe der Benutzeroberflächenautomatisierungs-Bibliothek. Wie Sie gesehen haben, ist die Bibliothek sehr umfangreich, und ihr rein objektorientiertes Programmiermodell unterscheidet sie wahrscheinlich ein wenig von anderen Bibliotheken, die Sie bislang verwendet haben. Ich bin mir jedoch sicher, dass Sie schnell lernen werden, wie Sie mithilfe der Bibliothek eine leistungsfähige Benutzeroberflächentest-Automatisierung schreiben können. Drei Hauptkonzepte, auf die es sich zu konzentrieren lohnt, sind die ControlType-Klasse für das Identifizieren des Typs eines Benutzeroberflächensteuerelements, die AutomationPattern-Klasse für das Identifizieren der Funktionalität eines Benutzeroberflächensteuerelements sowie die PropertyCondition-Klasse für das Angeben eines bestimmten Benutzeroberflächensteuerelements.
Es gibt äußerst viele Möglichkeiten, wie Sie die Ideen der von mir vorgestellten einführenden Testautomatisierungsumgebung erweitern können. Eine offensichtliche Erweiterung besteht darin, die Umgebung zu parametrisieren. Statt Eingaben und erwartete Werte hartzucodieren, können Sie diese Informationen mithilfe der Befehlszeile an Ihre Testumgebung übergeben. Noch besser wäre es, wenn Sie einen Testfalldatenspeicher als eine Textdatei, Datenbanktabelle oder XML-Datei erstellen und jeden Testfall durchlaufen, wobei Sie Testfalldaten extrahieren und in die Testumgebung einführen. Derartige Entwurfsmuster wurden ausführlich in meinem Artikel vom August 2005 unter msdn.microsoft.com/msdnmag/issues/05/08/TestRun diskutiert.
Der Code in diesem Artikel führt keine Ergebnisprotokollierung durch, und in den meisten Fällen sollten Sie Testfallergebnisse in einem Datenspeicher aufzeichnen, statt lediglich eine Erfolgs- bzw. Fehlschlagsmeldung in der Befehlshell anzuzeigen.
Ich bin mir ziemlich sicher, dass sich die Benutzeroberflächenautomatisierungs-Bibliothek innerhalb kurzer Zeit zum am meisten verwendeten Framework für die Benutzeroberflächentest-Automatisierung entwickeln wird. Das Wichtigste ist: Es gibt jetzt eine einzelne, standardmäßige, umfassende, konsistente und gut getestete Benutzeroberflächenautomatisierungs-Bibliothek, die für Softwareentwickler frei verfügbar ist. Dies legt bereits den Grundstein für eine Community mit öffentlichen Foren, Blogs, Beispielcode und so weiter.
Dies soll nicht heißen, dass die Benutzeroberflächenautomatisierungs-Bibliothek meiner Meinung nach Alternativen wie kommerzielle Benutzeroberflächentest-Automatisierungsframeworks und benutzerdefinierte Benutzeroberflächen-Automatisierungsframeworks vollständig ersetzen wird. Die Benutzeroberflächenautomatisierungs-Bibliothek bietet jedoch bedeutende Vorteile gegenüber diesen Alternativen. Verglichen mit kommerziellen Benutzeroberflächentest-Automatisierungsframeworks ist die Benutzeroberflächenautomatisierungs-Bibliothek preiswerter, und, da sie in .NET Framework 3.0 enthalten ist, stellt sie bereits einen allgemeinen Standard dar.
Im Vergleich zu benutzerdefinierten Bibliotheken für die Benutzeroberflächentest-Automatisierung, die von ein oder zwei Softwareentwicklern geschrieben wurden, enthält die Benutzeroberflächenautomatisierungs-Bibliothek viel mehr Funktionen, und Sie müssen sich nur mit einem API-Satz statt einer neuen API für jede benutzerdefinierte Automatisierungsbibliothek vertraut machen. Kommerzielle Benutzeroberflächentest-Automatisierungsframeworks sind jedoch insofern von Vorteil, als sie oft eng in Tools wie Testfallmanager und Softwaretools für die Fehlerberichterstattung integriert sind. Einfache benutzerdefinierte Bibliotheken für die Benutzeroberflächentest-Automatisierung sind von Vorteil in der Hinsicht, dass sie gewöhnlich leichter für eine schnelle, oberflächliche Testautomatisierung verwendet und an besondere Testsituationen angepasst werden können. Da jedoch die Verbreitung von .NET Framework 3.0 durch den Übergang zu Windows Vista zunimmt, können Sie davon ausgehen, dass auch die Verwendung der Benutzeroberflächenautomatisierungs-Bibliothek ansteigen wird.

Senden Sie Fragen und Kommentare für James McCaffrey in englischer Sprache an testrun@microsoft.com.


Dr. James McCaffrey arbeitet für Volt Information Sciences Inc. und organisiert technische Schulungen für Softwareentwickler auf dem Campus von Microsoft in Redmond, Washington. Er hat an verschiedenen Microsoft-Produkten mitgearbeitet, unter anderem an Internet Explorer und MSN Search. James ist Autor von „.NET Test Automation Recipes“ (Apress, 2006) und kann unter jmccaffrey@volt.com oder v-jammc@microsoft.com erreicht werden.

Page view tracker