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?
Comments
Anonymous
April 16, 2009
det verkar ju helt klart intressant. hur fungerar det med code coverage? räknas metoderna som täckta så man kann se vilka rader som används för att ge det önskade beteendet hos vyn?Anonymous
April 25, 2009
Jag skulle kunna tänka mig ha användning för detta i den applikation jag är med och utvecklar.Anonymous
April 27, 2009
b90ana: Hör av dig till mig om du vill ha mer information. dagk at microsoft.com. /dag