Seit Einführung des neuen JSON-Build Systems im TFS steht auch der Build Task Run Functional Tests zur Verfügung. Dieser ermöglicht die Ausführung von Selenium – und anderen funktionalen Test-Frameworks durch einen Test Agent auf einer Menge von Remote-Maschinen (d.h. auf Rechnern ohne Build Agent). Mit diesem ist es möglich nicht nur Tests auf Basis von Test Assemblies auszuführen, sondern auch Test Suiten zu verwenden. In letzterem Fall werden alle darin enthaltenen Test Cases, welche mit einer automatisierten Test Methode verknüpft sind und somit den Automation Status = Automated aufweisen, ausgeführt und die Ergebnisse der Test Cases im TFS automatisch gesetzt. Für datengetriebene Tests jedoch kann es vorkommen, dass Test Cases fälschlicherweise als Passed markiert werden. Damit verlieren die Ergebnisse der automatisierten Testläufe innerhalb der ausgeführten Test Suiten ihre Aussagekraft.
Understand the problem
Um zu verstehen was im Detail passiert und wie mit dem oben beschriebenen Bug umgegangen werden kann, gehen wir einen Schritt zurück: Was sind eigentlich datengetriebene Tests? Bei datengetriebenen Tests handelt es sich um Tests, welche in so genannten Iterationen mit unterschiedlichen Datensätzen ausgeführt werden. Die Datensätze können dabei aus verschiedenen Quellen wie z.B. XML-Files, Datenbanken, SharePoint, etc. ausgelesen werden. Ein Test Case soll also nur dann als Passed markiert werden, wenn alle ausgeführten Iterationen erfolgreich ausgeführt wurden.
Der Run Functional Test-Task jedoch beinhaltet einen Fehler: Das Ergebnis der ersten Iteration eines Test Cases wird als Gesamtergebnis gesetzt. Dies führt im Fall, dass die erste Iteration erfolgreich ausgeführt wurde Folge-Iterationen aber nicht, zu falschen Ergebnissen.
Fix it
Da es nicht möglich ist den Build Task selbst derart anzupassen, dass die richtigen Testergebnisse im TFS widergespiegelt werden, wird ein Workaround benötigt um dieses Ziel zu erreichen. Der hier vorgestellte Ansatz basiert auf der Verwendung von PowerShell und der TFS API um die Ergebnisse eines Test Runs zu überprüfen und gegebenenfalls anzupassen. Das PowerShell-Skript kann dann im Anschluss an den Run Functional Test-Task in der jeweiligen Build oder Release Definition eingehängt werden. Was ist also im Skript zu tun?
In groben Schritten muss folgender Ablauf implementiert werden:
- Identifikation des zum Build / Release zugehörigen Test Runs
- Identifikation des TRX-Test Run Attachments, welches die Ergebnisse der einzelnen Iterationen eines Test Cases enthält
- Parsen des TRX-Files um die korrekten Testergebnisse zu erhalten
- Abgleichen des aktuellen Ergebnis mit dem korrekten Ergebnis pro Test Case
- Bei Bedarf Korrektur der Ergebnisse einzelner Test Cases
Der auf diese Weise beschriebene Ablauf kann im Detail folgendermaßen umgesetzt werden:
Um den zu einem Build bzw. Release zugehörigen Testlauf zu identifizieren (1.) werden zunächst alle Tasks per REST API angefragt. Generell kann in Powershell für den Aufruf einer REST-Methode das Commandlet Invoke-RestMethod verwendet werden. Der Aufruf zum Anfragen der Tasks am Beispiel eines Releases sieht folgendermaßen aus:
GET https://{instance}/{project}/_apis/release/releases/{releaseId}/environments/{environmentId}/deployPhases/{releaseDeployPhaseId}/tasks?api-version={version}
Alle benötigten Parameter können aus den vordefinierten Release-Umgebungsvariablen ausgelesen werden. Das vollständige PowerShell-Code Snippet lautet dann wie folgt :
# Initialize variables
$instance = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI
$project = $env:SYSTEM_TEAMPROJECT
$releaseId = $env:RELEASE_RELEASEID
$environmentId = $env:RELEASE_ENVIRONMENTID
$releaseDeployPhaseId = $env:RELEASE_DEPLOYPHASEID
$tasksUrl = $instance + $project + ”/_apis/release/releases/” + $releaseId + “/environments/” + $environmentId + “/deployPhases/” + $releaseDeployPhaseId + “/tasks?api-version=3.0-preview”
$userName = “bgoeller”
$password = “superSecretPassword”
$securePassword = ConvertTo-SecureString $password –AsPlainText –Force
$credentials = New-Object System.Management.Automation.PSCredential ($username, $securePassword)
# Get Release Tasks
$tasks = Invoke-RestMethod Uri $tasksUrl –Credential $credentials –Method Get
Hinweis: Bitte beachten Sie, dass Passwörter nicht im Skript enthalten sein sondern per Skript-Parameter übergeben werden sollten. In vorhergehendem Code Snippet ist das Passwort zu Demozwecken enthalten.
Aus dem Ergebnis dieser Abfrage kann dann die Id des Test Tasks herausgesucht werden, indem auf den Task Name RunVisualStudioTestsusingTestAgent gefiltert wird. Im Anschluss kann mithilfe der Test Task Id und der REST API-Methode
GET https://{instance}/{project}/_apis/release/releases/{releaseId}/environments/{environmentId}/deployPhases/{releaseDeployPhaseId}/tasks/{taskId}/logs?api-version={version}
das Log-File des Run Functional Test-Tasks angefragt werden. Aus diesem kann dann mittels Regex die Test Run Id identifiziert werden.
Analog zu Schritt 1 können dann mittels REST API alle dem Test Run zugehörigen Attachments abgerufen, das TRX-File identifiziert und heruntergeladen werden (2.).
Die letzten drei Schritte (3., 4., und 5.) lassen sich folgendermaßen sinnvoll in einem Ablauf unterbringen:
Zunächst werden die aktuellen Ergebnisse für die im Test Run enthaltenen Test Cases per REST API abgerufen. Diese enthalten zu jedem Test Case sowohl die ID des zugehörigen Test Results, das Ergebnis selbst sowie weitere Metainformationen. Im Folgeschritt findet nun ein Technologiewechsel statt: Bisher wurden alle Anfragen an den TFS per REST API ausgeführt. Diese ist jedoch noch nicht vollständig ausgebaut. Der Zugriff auf Teile der tiefergehenden Informationen sowie schreibende Operationen auf dem TFS können bisher nicht per REST API ausgeführt werden. Darum werden alle weiteren Schritte mittels TFS Client API ausgeführt. Dazu muss zunächst eine Verbindung zum TFS und insbesondere zum TFS Test Management Service aufgebaut werden:
$tfs = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($instance)
$testManagementService = $tfs.GetService([Microsoft.TeamFoundation.TestManagementClient.ITestManagementService])
Hierbei ist zu beachten, dass die für die TFS Client API-Aufrufe benötigten Assemblies im Vorhinein geladen werden müssen um im Skript zur Verfügung zu stehen. Hat man diese Hürde genommen kann dann die Verbindung zwischen den Test Cases und den Methodennamen der automatisierten Tests, welche im TRX-File aufgeführt sind, hergestellt werden. Dazu kann pro Test Case das detaillierte Test Result abgerufen werden:
$testProject = $testManagementService.GetTeamProject($project)
$detailedTestResult = $testProject.TestResults.Find($testRunId, $TestCaseResult.Id)
An diesem Test Result findet man dann alle benötigten Informationen um einen Test Case auf die entsprechende Methode im TRX-File zu mappen. Somit kann das TRX-File geparsed werden, die Ergebnisse abgeglichen werden und gegebenenfalls die falschen Ergebnisse überschrieben werden:
if(-not (Has-TestCaseCorrectOutcome)
{
$detailedTestResult.Outcome = $correctOutcome
$detailedTestResult.ErrorMessage = “You’ve fixed it!”
$detailedTestResult.Save($true)
}
Challenges & Conclusion
Das Problem der falschen Testergebnisse ist mit oben beschriebenem Ansatz gelöst. Doch was gibt es dabei zu beachten?
Zunächst sei darauf hingewiesen, dass für die Verwendung der beschriebenen API-Aufrufe ein User angegeben werden muss, welcher über ausreichend Berechtigungen verfügt. Dies bedeutet zum einen, dass der User lesend Zugriff auf alle Teile des TFS Test Management (für das entsprechende Team Projekt) benötigt. Desweiteren benötigt der User ausreichend Berechtigungen auf der Build bzw. Release Definition, in welchem die Tests sowie das PowerShell-Skript ausgeführt werden.
Eine weitere Herausforderung ist, dass wenn das Skript zum Korrigieren der Test Ergebnisse direkt nach Ausführung der Tests ausgeführt wird, das Problem entstehen kann, dass die zum Run Functional Test-Task zugehörigen Informationen noch nicht am Build bzw. Release abrufbar sind. Diese Hürde kann gemeistert werden, indem eine Warteschleife eingebaut wird, in welcher die Task Informationen solange angefragt werden bis sie verfügbar sind.
Hat man also die beschriebenen Hürden gemeistert, so bietet der vorgestellte Ansatz mittels PowerShell und TFS API einen leichtgewichtigen Ansatz, um trotz fehlerhafter Auswertung bei datengetriebenen Tests die Vorzüge des Run Functional Test-Tasks zu nutzen.