Visual Studio 2015

Bessere Software durch intelligente Komponententests

Pratap Lakshman

Die Welt der Softwareentwicklung entwickelt sich rasend hin zu immer kürzeren Releasezyklen. Die Zeiten, als Softwareentwicklungsteams die Aufgaben von Spezifikation, Implementierung und Testen noch in einem Wasserfallmodell streng sequenziell planen und erledigen konnten, sind lange vorbei. Das Entwickeln von Software in höchster Qualität ist ein hartes Geschäft in unserer hektischen Welt und verlangt nach einer Neubewertung bestehender Entwicklungsmethodologien.

Um die Anzahl der Fehler in einem Softwareprodukt zu verringern, müssen sich alle Teammitglieder darauf einigen, was das Softwaresystem leisten soll – und das ist eine der wesentlichen Herausforderungen. Spezifikation, Implementierung und Testen erfolgten normalerweise voneinander abgeschottet, ohne ein allgemeines Kommunikationsmedium. Unterschiedliche Sprachen oder Artefakte, die für die jeweilige Aufgabe verwendet wurden, erschwerten es ihnen, sich gleichzeitig weiterzuentwickeln, während die Softwareimplementierung voranschreitet – und deshalb ist dies, obwohl ein Spezifikationsdokument die Arbeit aller Teammitglieder miteinander verknüpfen sollte, in der Realität selten der Fall. Die ursprüngliche Spezifikation und die tatsächliche Implementierung können voneinander abweichen, und das Einzige, was alles zusammenhält, ist zu guter Letzt der Code, der schließlich die ultimative Spezifikation sowie die verschiedenen Designentscheidungen, die unterwegs getroffen wurden, verkörpert. Das Testen versucht, diese Abweichungen wieder auszugleichen, indem es Zuflucht zum Testen nur einiger weniger geläufiger End-to-End-Szenarios nimmt.

Diese Situation lässt sich verbessern. Ein allgemeines Medium zur Angabe des beabsichtigten Verhaltens des Softwaresystems ist erforderlich, und zwar eins, das sich über Design, Implementierung und Testen hinweg gemeinsam verwenden und zudem noch einfach weiterentwickeln lässt. Die Spezifikation muss direkt in Bezug zum Code stehen, und das Medium muss in Form einer erschöpfenden Testsammlung kodifiziert sein. Toolbasierte Methoden, die durch intelligente Komponententests ermöglicht werden, können dabei helfen, diesen Anspruch zu erfüllen.

Intelligente Komponententests

Intelligente Komponententests, eine Funktion von Visual Studio 2015 Preview (siehe Abbildung 1), ist ein intelligenter Assistent für die Softwareentwicklung, der Entwicklerteams hilft, Fehler frühzeitig zu finden und die Testwartungskosten zu verringern. Er basiert auf vorangegangenen Arbeiten von Microsoft Research namens "Pex". Das zugrunde liegende Modul verwendet Whitebox-Codeanalysen und Einschränkungs-Solver, um künstlich präzise Testeingabewerte zu erzeugen, um damit alle Codepfade im zu testenden Code zu testen, diese dann als kompakte Sammlung herkömmlicher Komponententests mit hoher Abdeckung dauerhaft zu speichern und die Testsammlung parallel zur Entwicklung des Codes automatisch weiterzuentwickeln.

Intelligente Komponententests sind vollständig integriert in Visual Studio 2015 Preview
Abbildung 1 Intelligente Komponententests sind vollständig integriert in Visual Studio 2015 Preview

Darüber hinaus, und dies wird auch nachdrücklich empfohlen, kann mithilfe von Richtigkeitseigenschaften, die im Code als Assertionen angegeben sind, die Erzeugung von Testfällen gesteuert werden.

Standardmäßig erfassen die generierten Testfälle, wenn Sie nichts mehr machen, als intelligente Komponententests mit einem Stück Code auszuführen, das beobachtete Verhalten des zu testenden Codes für jeden der künstlich erzeugten Eingabewerte. In dieser Phase gilt der Rest, mit Ausnahme von Testfällen, die Laufzeitfehler verursachen, als bestandene Tests – schließlich ist dies das beobachtete Verhalten.

Außerdem, wenn Sie Assertionen unter Angabe der Richtigkeitseigenschaften des zu testenden Codes schreiben, liefern intelligente Komponententests Testeingabewerte, die zum Fehlschlagen der Assertionen führen können, wobei jeder dieser Eingabewerte einen Fehler im Code offenlegt und auf diese Weise einen fehlschlagenden Testfall erzeugt. Intelligente Komponententests können nicht von selbst solche Richtigkeitseigenschaften erzeugen, Sie würden sie auf Grundlage Ihrer Domänenkenntnisse schreiben.

Testfallerzeugung

Im Allgemeinen liegen Methoden zur Programmanalyse bei einem der folgenden zwei Extremen:

  • Statische Analysemethoden überprüfen, ob eine Eigenschaft in allen Ausführungspfaden gültig ist. Da das Ziel die Programmüberprüfung ist, sind solche Methoden in der Regel sehr konservativ und markieren mögliche Verletzungen als Fehler, was zu falsch positiven Ergebnissen führt.
  • Dynamische Analysemethoden überprüfen, ob eine Eigenschaft in einigen Ausführungspfaden gültig ist. Das Testen verwendet einen dynamischen Ansatz, der darauf abzielt, Fehler zu erkennen, der aber normalerweise nicht nachweisen kann, dass keine Fehler vorliegen. Auf diese Weise schaffen es solche Methoden häufig nicht, alle Fehler zu erkennen.

Es ist eventuell nicht möglich, Fehler präzise zu erkennen, wenn nur statische Analyseverfahren oder eine Testmethode, die die Struktur des Codes unberücksichtigt lässt, angewendet werden. Sehen Sie sich z. B. den folgenden Code an:

int Complicated(int x, int y)
{
  if (x == Obfuscate(y))
    throw new RareException();
  return 0;
}
int Obfuscate(int y)
{
  return (100 + y) * 567 % 2347;
}

Statische Analysemethoden sind eher konservativ, sodass die in Obfuscate vorhandene, nicht lineare Ganzzahlarithmetik die meisten statischen Analysemethoden dazu bringt, eine Warnung wegen eines potenziellen Fehlers in "Complicated" auszugeben. Auch Zufallstestverfahren haben nur eine sehr geringe Chance, ein Paar von X-/Y-Werten zu finden, das die Ausnahme auslöst.

Intelligente Komponententests implementieren eine Analysemethode, die irgendwo zwischen diesen beiden Extremen liegt. Ähnlich wie bei den statischen Analysemethoden weisen sie nach, dass eine Eigenschaft in den meisten möglichen Pfaden gültig ist. Ähnlich wie bei dynamischen Analysemethoden melden sie nur echte Fehler und keinen falsch positiven Ergebnisse.

Die Testfallerzeugung umfasst die folgenden Aufgaben:

  • Dynamisches Ermitteln aller Zweige (explizit und implizit) im zu testenden Code.
  • Synthetisieren präziser Testeingabewerte, die diese Zweige testen.
  • Aufzeichnen der Ausgabe des zu testenden Codes für die besagten Eingaben.
  • Dauerhaftes Speichern als kompakte Testsammlung mit hoher Abdeckung.

Abbildung 2 zeigt, wie das unter Verwendung von Laufzeitinstrumentation und Überwachung funktioniert, und im Folgenden finden Sie die daran beteiligten Schritte:

  1. Der zu testende Code wird zuerst instrumentiert, und es werden Rückrufe platziert, die es dem Testmodul erlauben, die Ausführung zu überwachen. Der Code wird dann mit dem einfachsten relevanten, konkreten Eingabewert (basierend auf dem Typ des Parameters) ausgeführt. Dies stellt den ersten Testfall dar.
  2. Das Testmodul überwacht die Ausführung, berechnet die Abdeckung für jeden Testfall und verfolgt, wie der Eingabewert durch den Code geleitet wird. Wenn alle Pfade abgedeckt sind, wird der Prozess beendet. Jedes außergewöhnliche Verhalten wird als Zweig betrachtet, ebenso wie explizite Verzweigungen im Code. Wenn noch nicht alle Pfade abgedeckt wurden, wählt das Testmodul einen Testfall aus, der einen Programmpunkt erreicht, an dem ein nicht abgedeckter Zweig beginnt, und bestimmt, wie die Verzweigungsbedingung vom Eingabewert abhängig ist.
  3. Das Modul erzeugt ein Einschränkungssystem, das die Bedingung darstellt, unter der die Steuerung bis zu dem Programmpunkt gelangt und dann entlang des zuvor nicht abgedeckten Zweigs fortfahren würde. Danach wird ein Einschränkungs-Solver abgefragt, um auf Grundlage dieser Einschränkung einen neuen konkreten Eingabewert zu synthetisieren.
  4. Wenn der Einschränkungs-Solver einen konkreten Eingabewert für die Einschränkung bestimmen kann, wird der zu testende Code mit dem neuen konkreten Eingabewert ausgeführt.
  5. Wenn die Abdeckung steigt, wird ein Testfall ausgegeben.

Einblick in die Hintergründe der Funktionsweise der Testfallerzeugung
Abbildung 2 Einblick in die Hintergründe der Funktionsweise der Testfallerzeugung

Die Schritte 2 bis 5 werden wiederholt, bis alle Zweige abgedeckt sind, oder bis vorkonfigurierte Explorationsgrenzen überschritten werden.

Dieser Vorgang wird als "Exploration" bezeichnet. Innerhalb einer Exploration kann der zu testende Code mehrmals "durchlaufen" werden. Einige dieser Läufe erhöhen die Abdeckung, und nur die Läufe, die die Abdeckung erhöhen, geben Testfälle aus. Somit testen alle Tests, die erzeugt werden, mögliche Pfade.

Begrenzte Exploration

Wenn der zu testende Code keine Schleifen oder unbegrenzten Rekursionen enthält, endet die Exploration normalerweise schnell, weil es nur eine (kleine) begrenzte Anzahl zu analysierender Ausführungspfade gibt. Die interessantesten Programme enthalten jedoch Schleifen oder unbegrenzte Rekursionen. In solchen Fällen ist die Anzahl von Ausführungspfaden (praktisch) unbegrenzt, und es kann generell nicht entschieden werden, ob eine Anweisung erreicht werden kann. Mit anderen Worten würde eine Exploration ewig brauchen, um alle Ausführungspfade des Programms zu analysieren. Da die Testerzeugung das tatsächliche Ausführen des zu testende Codes umfasst, stellt sich die Frage, wie Sie sich vor solchen unkontrollierten Explorationen schützen können? An diesem Punkt spielt die begrenzte Exploration eine wesentliche Rolle. Sie stellt sicher, dass Explorationen nach einer vernünftigen Dauer beendet werden. Es gibt mehrere in Ebenen gegliederte, konfigurierbare Explorationsgrenzen, die zum Einsatz kommen:

  • Einschränkungs-Solvergrenzen begrenzen die zeitliche Dauer und Speichermenge, die der Solver bei der Suche nach dem nächsten konkreten Eingabewert verwenden kann.
  • Explorationspfadgrenzen begrenzen die Komplexität des zu analysierenden Ausführungspfads hinsichtlich der Anzahl der zu verfolgenden Zweige, der Anzahl der Bedingungen für die Eingaben, die überprüft werden müssen, und der Tiefe des Ausführungspfads hinsichtlich der Stapelrahmen.
  • Explorationsgrenzen begrenzen die Anzahl der "Läufe", die keinen Testfall liefern, die Gesamtzahl der zugelassenen Läufe und die Gesamtzeit, nach der eine Exploration beendet wird.

Ein wichtiger Aspekt jedes toolbasierten Testansatzes, der effektiv sein soll, ist schnelles Feedback, und alle diese Grenzen wurden so vorkonfiguriert, dass sie eine schnelle, interaktive Verwendung ermöglichen.

Darüber hinaus verwendet das Testmodul Heuristiken, um schnelle eine hohe Codeabdeckung zu erzielen, indem die Lösung schwieriger Einschränkungssysteme verschoben wird. Sie können das Modul schnell einige Tests für Code erzeugen lassen, an dem Sie arbeiten. Um aber die verbleibenden schwierigen Probleme der Testeingabeerzeugung zu beheben, können Sie die Schwellenwerte erhöhen, damit das Testmodul weiter an den komplizierten Einschränkungssystemen arbeiten kann.

Parametrisierte Komponententests

Alle Programmanalysemethoden versuchen, bestimmte Eigenschaften eines bestimmten Programms zu validieren oder zu widerlegen. Es gibt verschiedene Verfahren zum Angeben von Programmeigenschaften:

  • API-Verträge geben das Verhalten einzelner API-Aktionen aus Sicht der Implementierung an. Ihr Ziel ist es, in dem Sinne Robustheit zu garantieren, dass Vorgänge nicht abstürzen und Dateninvarianten erhalten bleiben. Ein häufiges Problem von API-Verträgen ist ihre eingeschränkte Sicht auf einzelne API-Aktionen, was es erschwert, systemweite Protokolle zu beschreiben.
  • Komponententests beinhalten beispielhafte Nutzungsszenarios aus der Sicht eines Clients der API. Deren Ziel ist es, ordnungsgemäßes Funktionieren in dem Sinne zu garantieren, dass sich das Zusammenspiel mehrerer Vorgänge wie beabsichtigt verhält. Ein häufiges Problem von Komponententests ist, dass sie von den Details der API-Implementierung getrennt sind.

Intelligente Komponententests ermöglichen parametrisierte Komponententests, die beide Methoden vereinen. Unterstützt von einem Modul für die Testeingabeerzeugung kombiniert diese Methodologie die Sichtweise des Clients mit der der Implementierung. Die funktionalen Richtigkeitseigenschaften (parametrisierte Komponententests) werden bei den meisten Fällen der Implementierung überprüft (Testeingabeerzeugung).

Ein parametrisierter Komponententest (Parametric Unit Test, PUT) ist die unmittelbare Verallgemeinerung eines Komponententests durch die Verwendung von Parametern. Ein PUT trifft Aussagen über das Verhalten des Codes für eine ganze Gruppe möglicher Eingabewerte statt nur über einen einzigen, beispielhaften Eingabewert. Er formuliert Annahmen zu Testeingaben, führt eine Abfolge von Aktionen aus und bestätigt Eigenschaften, die im endgültigen Zustand noch gültig sein sollten, d. h., er dient also als Spezifikation. Eine solche Spezifikation erfordert weder eine neue Sprache oder Artefakte, noch führt sie welche ein. Sie wird auf Ebene der vom Softwareprodukt tatsächlich implementierten APIs geschrieben und zwar in der Programmiersprache des Softwareprodukts. Designer können sie verwenden, um beabsichtigtes Verhalten der Software-APIs zu formulieren, Entwickler können damit automatisierte Entwicklertests betreiben, und Tester können sie für tiefgehende automatische Testerzeugung nutzen. Der folgende PUT bestätigt beispielsweise, dass nach dem Hinzufügen eines Elements zu einer Liste, die nicht null ist, das Element tatsächlich in der Liste enthalten ist:

void TestAdd(ArrayList list, object element)
{
  PexAssume.IsNotNull(list);
  list.Add(element);
  PexAssert.IsTrue(list.Contains(element));
}

PUTs trennen die beiden folgenden Anliegen voneinander:

  1. Die Spezifikation der Richtigkeitseigenschaften des zu testenden Codes für alle möglichen Testargumente.
  2. Die tatsächlichen, "geschlossenen" Testfälle mit den konkreten Argumenten.

Das Modul gibt Stubs für das erste Anliegen aus, und Sie können diese gerne auf Grundlage Ihrer Domänenkenntnisse mit Leben füllen. Nachfolgende Aufrufe von intelligenten Komponententests erzeugen und aktualisieren automatisch einzelne, geschlossene Testfälle.

Anwendung

Softwareentwicklungsteams sind möglicherweise bereits in verschiedenen Methodologien fest verwurzelt, und es ist unrealistisch, von ihnen zu erwarten, dass sie von heute auf morgen eine neue übernehmen. Tatsächlich sind intelligente Komponententests nicht als Ersatz für irgendeine Testpraxis gedacht, die ein Team möglicherweise ausübt. Stattdessen sollen sie jede bestehende Praxis erweitern. Die Übernahme beginnt wahrscheinlich mit einer teilweisen Akzeptanz, bei der Teams zuerst die automatischen Standardtesterzeugungs- und -wartungsfunktionen nutzen und dann dazu übergehen, die Spezifikationen in Code zu schreiben.

Testen von beobachtetem Verhalten Stellen Sie sich vor, Sie müssen Änderungen an einer Codebasis vornehmen – ohne Testabdeckung. Bevor Sie beginnen, möchten Sie ihr Verhalten vielleicht als Sammlung von Komponententests festhalten, doch das ist leichter gesagt, als getan:

  • Der Code (Produktcode) bietet sich möglicherweise nicht von alleine als komponententestbar an. Er kann enge Abhängigkeiten von der externen Umgebung beinhalten, die isoliert werden müssten, und wenn Sie sie nicht auffinden können, wissen Sie nicht einmal, wo Sie beginnen sollen.
  • Die Qualität der Tests kann ebenfalls ein Problem sein, und es gibt viele Qualitätsmaße. Da ist z. B. das Maß der Abdeckung: Wie viele Zweige oder Codepfade oder andere Programmartefakte erfassen die Tests im Produktcode? Es gibt das Maß der Assertionen, das ausdrückt, ob der Code das Richtige macht. Keins dieser Maße ist jedoch an sich ausreichend. Stattdessen wäre es nett, wenn eine hohe Dichte von Assertionen bei hoher Codeabdeckung validiert würde. Aber es ist nicht einfach, beim Schreiben der Tests diese Art von Qualitätsanalysen im Kopf zu vollziehen, und als Konsequenz hieraus kommen am Ende eventuell Tests heraus, die wiederholt dieselben Codepfade testen, vielleicht sogar nur den "glücklichen Pfad", und Sie werden nie erfahren, ob der Produktcode auch nur mit all diesen Sonderfällen umgehen kann.
  • Und frustrierenderweise wissen Sie eventuell nicht einmal, welche Assertionen Sie aufnehmen sollten. Stellen Sie sich vor, Sie werden aufgefordert, Änderungen an einer unbekannten Codebasis vorzunehmen.

In dieser Situation ist die automatische Testerzeugungsfunktion der intelligenten Komponententests besonders nützlich. Sie können das aktuelle beobachtete Verhalten Ihres Codes in Form einer Sammlung von Tests als Grundlage zur Verwendung als Regressionssammlung nehmen.

Spezifikationsbasiertes Testen Softwareteams können mithilfe von PUTs als Spezifikation in erschöpfendem Umfang Testfälle erzeugen, um Verletzungen der Testassertionen aufzudecken. Dadurch, dass den Teams ein großer Teil der manuellen Arbeit abgenommen wird, die das Schreiben von Testfällen zur Erzielung einer hohen Codeabdeckung mit sich bringt, können sie sich nun auf Aufgaben konzentrieren, die sich nicht durch intelligente Komponententests automatisierten lassen, z. B. das Schreiben von interessanteren Szenarios als PUTs oder das Entwickeln von Integrationstests, die über den Umfang von PUTs hinausgehen.

Automatisches Auffinden von Fehlern Assertionen, die Richtigkeitseigenschaften ausdrücken, lassen sich auf vielfache Weise formulieren: als Assert-Anweisungen, als Codeverträge und mehr. Angenehm daran ist, dass diese alle in Verzweigungen kompiliert werden: eine If-Anweisung mit einer Then-Verzweigung und einer Else-Verzweigung, die das Ergebnis des zu bestätigenden Prädikats darstellt. Da intelligente Komponententests Eingaben berechnen, die alle Zweige testen, werden sie dadurch auch zu einem effektiven Tool zum Auffinden von Fehlern – jede erzeugte Eingabe, die die Else-Verzweigung auslöst, stellt einen Fehler im zu testenden Code dar. Somit sind alle gemeldeten Fehler auch tatsächlich Fehler.

Verringerte Testfallwartung Bei Verwendung von PUTs muss nur noch eine wesentlich geringere Anzahl von Testfällen gepflegt werden. In einer Welt, in der noch einzelne, geschlossene Testfälle manuell geschrieben wurden, was wäre passiert, wenn sich der zu testende Code weiterentwickelt hätte? Sie hätten den Code aller Tests einzeln anpassen müssen, was ein signifikanter Kostenfaktor sein kann. Indem Sie aber stattdessen PUTs schreiben, müssen nur die PUTs gepflegt werden. Dann können intelligente Komponententests die einzelnen Testfälle automatisch neu generieren.

Herausforderungen

Grenzen der Tools Die Methode der Verwendung von Whitebox-Codeanalysen in Kombination mit Einschränkungs-Solvern funktioniert bei Code auf Komponentenebene, der gut isoliert ist, sehr gut. Dem Testmodul sind jedoch einige Grenzen gesetzt:

  • Sprache: Grundsätzlich kann das Testmodul beliebige .NET-Programme analysieren, die in einer beliebigen .NET-Sprache geschrieben sind. Der Testcode wird aber nur in C# generiert.
  • Nichtdeterminismus: Das Testmodul setzt voraus, dass der zu testende Code deterministisch ist. Sollte dies nicht der Fall sein, bereinigt es nicht deterministische Ausführungspfade oder könnte bis zum Erreichen seiner Explorationsgrenzen zyklisch weiterverfahren.
  • Parallelität: Das Testmodul kann keine Multithreadprogramme verarbeiten.
  • Nicht instrumentierter systemeigener Code oder .NET-Code: Das Testmodul versteht keinen systemeigenen Code, d. h. x86-Anweisungen, die über die Funktion für Plattformaufrufe (P/Invoke) des Microsoft .NET Frameworks aufgerufen werden. Das Testmodul weiß nicht, wie solche Aufrufe in Einschränkungen zu übersetzen sind, die von einem Einschränkungs-Solver aufgelöst werden können. Und selbst bei .NET-Code kann das Modul nur Code analysieren, den es instrumentiert.
  • Gleitkommaarithmetik: Das Testmodul verwendet einen automatischen Einschränkungs-Solver, um zu bestimmen, welche Werte für den Testfall und den zu testenden Code relevant sind. Die Möglichkeiten des Einschränkungs-Solvers sind jedoch begrenzt. Insbesondere kann er nicht exakt mit Gleitkommaarithmetik umgehen.

In diesen Fällen benachrichtigt das Testmodul den Entwickler, indem es eine Warnung ausgibt. Das Verhalten des Moduls kann angesichts solcher Einschränkungen durch die Verwendung benutzerdefinierter Attribute kontrolliert werden.

Schreiben von guten, parametrisierten Komponententests Das Schreiben guter PUTs kann eine Herausforderung darstellen. Es gibt zwei wesentliche Fragen, die beantwortet werden müssen:

  • Abdeckung: Was sind gute Szenarios (Abfolgen von Methodenaufrufen), um den zu testenden Code zu testen?
  • Überprüfung: Was sind gute Assertionen, die problemlos formuliert werden können, ohne den Algorithmus neu implementieren zu müssen?

Ein PUT ist nur nützlich, wenn er Antworten auf beide Fragen gibt.

  • Ohne ausreichende Abdeckung, also wenn das Szenario zu eng gefasst ist, um den gesamten zu testenden Code zu erreichen, ist der Umfang des PUT begrenzt.
  • Ohne ausreichende Überprüfung berechneter Ergebnisse, also wenn der PUT nicht genügend Assertionen enthält, kann er nicht überprüfen, ob der Code richtig funktioniert. Alles, was der PUT dann macht, ist, zu überprüfen, ob der zu testende Code nicht abstürzt bzw. keine Laufzeitfehler hat.

Bei herkömmlichen Komponententests umfasst der Satz von Fragen noch eine weitere: Was sind relevante Testeingaben? Bei PUTs erledigen die Tools diese Frage. Das Problem, gute Assertionen zu finden, gestaltet sich jedoch leichter als bei konventionellen Komponententests: Die Assertionen sind tendenziell einfacher, weil sie für bestimmte Testeingaben geschrieben werden.

Zusammenfassung

Mithilfe der Visual Studio 2015 Preview-Funktion für intelligente Komponententests das beabsichtigte Verhalten von Software im Quellcode vorgeben können und wie intelligente Komponententests automatisierte Whitebox-Codeanalyse zusammen mit einem Einschränkungs-Solver verwenden, um eine kompakte Sammlung relevanter Tests mit hoher Abdeckung für Ihren .NET-Code zu generieren und zu pflegen. Die Vorteile betreffen Funktionen: Designer können sie verwenden, um das beabsichtigte Verhalten von Software-APIs anzugeben, Entwickler können damit automatisierte Entwicklertests betreiben, und Tester können sie für tiefgehende automatische Testerzeugung nutzen.

Sich ständig verkürzende Releasezyklen in der Softwareentwicklung sind Antrieb für viele der Aktivitäten die ständig hinsichtlich Planung, Spezifikation, Implementierung und Tests vollzogen werden. Diese hektische Welt fordert uns heraus, bestehende Praktiken im Zusammenhang mit diesen Aktivitäten neu zu bewerten. Kurze, schnelle, iterative Releasezyklen erfordern, dass wir die Zusammenarbeit zwischen diesen Funktionen auf ein neues Niveau heben. Funktionen wie intelligente Komponententests können Softwareentwicklungsteams dabei helfen, solche Niveaus einfacher zu erreichen.


Pratap Lakshman arbeitet in der Entwicklerabteilung bei Microsoft, wo er zurzeit Senior Program Manager im Visual Studio-Team ist und sich mit Testtools beschäftigt.

Unser Dank gilt dem folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Nikolai Tillmann