Automatiska tester av gränssnitt i WPF

Läste på planet till Oslo imorse en riktigt intressant artikel i MSDN Magazine som tar upp UI-automation som används för att automatisera enklare tester av gränssnitt i applikationer byggda med WPF. Artikeln går i korthet ut på att med hjälp av en textbaserad applikation automatisera WPF-applikationen och undersöka om den uppträder på förväntat sätt. Jag var dock inte helt komfortabel med att det var en textbaserad applikation som skötte automatiseringen utan ville istället se om jag kunde integrera tekniken i Visual Studio Team System med hjälp av exempelvis Unit-Test metoderna.

Sagt och gjort, det var inte speciellt svårt i slutändan och resultatet blev ganska trevligt tycker jag. För att inte gå in på detaljerna över det som artikeln redan beskriver (så lätt kommer du inte undan) så tänker jag här bara ta upp hur mitt resultat blev.

Om du vill slippa läsa även min artikel så kan du hämta koden som jag slängde ihop här!

Jag skapade ett testprojekt i Visual Studio och la till referenser till UIAutomationClient.dll och UIAutomationTypes.dll. Sedan skapade jag ett nytt enhetstest (Unit Test). Mitt slutresultat är tre stycken metoder i testklassen som 1) initaliserar testerna, 2) testar specifika användarinteraktioner, och 3) städar upp efter varje test.

Testklassen har två stycken privata fält enligt följande:

[TestClass]
public class UIAutomationTest
{
    private Process _process = null;
    private AutomationElement aeApplication = null;
    ...
}

Initialiseringen ser ut så här:

[TestInitialize()]
public void MyTestInitialize() {
    // Start application
    _process = Process.Start(@"..\..\..\CryptoCalc\bin\Debug\CryptoCalc.exe");

    int counter = 0;
    do
    {
        counter++;
        Thread.Sleep(100);
    } while (_process == null && counter < 50); 
 
    // Get user desktop
    AutomationElement aeDesktop = AutomationElement.RootElement;
    // Get CryptoCalc window
    counter = 0;
    do
    {
        aeApplication = aeDesktop.FindFirst(
            TreeScope.Children,
            new PropertyCondition(AutomationElement.NameProperty, "CryptoCalc"));
        counter++;
        Thread.Sleep(100);
    } while (aeApplication == null && counter < 50); 
}

Metoden startar processen som ska testas och skapar en referens (aeApplication) till det fönster som är applikationens “MainWindow”. Det går nog att göra på lite andra sätt och initalisera flera saker direkt, men här lät jag initialiseringen vara klar och gick istället över till att testa logiken med unika testmetoder:

[TestMethod]
public void TestMD5()
{
    // Find txtInput TextBox
    var aeTextInput = aeApplication.FindFirst(
        TreeScope.Descendants,
        new PropertyCondition(AutomationElement.AutomationIdProperty, "txtInput"));

    // Set txtInput value to "Johan Lindfors"
    ValuePattern vpTextBoxInput =
        (ValuePattern)aeTextInput.GetCurrentPattern(ValuePattern.Pattern);
    vpTextBoxInput.SetValue("Johan Lindfors");
 
    // Find btnMD5 Button
    AutomationElement aeRadioButton = aeApplication.FindFirst(
        TreeScope.Descendants,
        new PropertyCondition(AutomationElement.AutomationIdProperty, "btnMD5"));           

    // Select button
    SelectionItemPattern spRadioButton =
        (SelectionItemPattern)aeRadioButton.GetCurrentPattern(SelectionItemPattern.Pattern);
    spRadioButton.Select();

    // Find btnHash Button
    AutomationElement aeButton = aeApplication.FindFirst(
        TreeScope.Descendants,
        new PropertyCondition(AutomationElement.AutomationIdProperty, "btnHash"));

    // "Press" button and let application do its work
    InvokePattern ipButton = (InvokePattern)aeButton.GetCurrentPattern(InvokePattern.Pattern);
    ipButton.Invoke(); 

    Thread.Sleep(1000); 

    // Find txtResult TextBox
    var aeTextResult = aeApplication.FindFirst(
        TreeScope.Descendants,
        new PropertyCondition(AutomationElement.AutomationIdProperty, "txtResult"));
   
    // Get the produced result and assert it agains known value
    string result = (string)aeTextResult.GetCurrentPropertyValue(ValuePattern.ValueProperty); 
    Assert.AreEqual(result, "6B-16-9E-3D-AE-FA-6D-1D-B7-66-66-5E-C5-C3-E3-73");
}

Efter det att testet är klart så vill jag också städa upp efter mig och stänga ned applikationen:

[TestCleanup()]
public void MyTestCleanup() {
    if (aeApplication != null)
        aeApplication = null;

    if (_process != null && !_process.HasExited)
        _process.CloseMainWindow();
}

Det var allt som behövdes göra för att kunna automatisera testerna med hjälp av Visual Studio Team System. Nu är jag dock lite fundersam om det är användbart, jag kan tänka mig en del scenarios när det här kan passa bra, men vad tycker du?