Eingabeerzeugung mithilfe der dynamischen symbolischen Ausführung

IntelliTest erzeugt Eingaben für parametrisierte Unittests, indem es die Branchbedingungen im Programm analysiert. Testeingaben werden danach ausgewählt, ob sie neues Branchingverhalten des Programm auslösen können. Die Analyse ist ein inkrementeller Prozess. Sie verfeinert das Prädikat q: I -> {true, false} über die formalen Eingabeparameter des Tests I. q stellt die Menge an Verhaltensweisen dar, die IntelliTest bereits beobachtet hat. Zunächst ist dies q := false, weil noch nichts beobachtet wurde.

Die Schritte der Schleife sind wie folgt:

  1. IntelliTest bestimmt Eingaben i mit einem Einschränkungs-Solver, sodass q(i)=false ist. Aufgrund der Konstruktion nimmt die Eingabe i einen neuen Ausführungspfad. Zunächst bedeutet dies, dass i jede Eingabe sein kann, weil noch kein Ausführungspfad gefunden wurde.

  2. IntelliTest führt den Test mit der ausgewählten Eingabe i aus und überwacht die Ausführung des Test sowie das getestete Programm.

  3. Während der Ausführung nimmt das Programm einen bestimmten Pfad, der von alle bedingten Branches des Programms bestimmt wird. Der Satz aller Bedingungen, die die Ausführung bestimmen, wird als Pfadbedingung bezeichnet. Diese wird über die formellen Eingabeparameter als Prädikat p: I -> {true, false} geschrieben. IntelliTest berechnet eine Repräsentation dieses Prädikats.

  4. IntelliTest legt q := (q or p) fest. Anders gesagt bedeutet dies, dass erfasst wird, dass es den Pfad gesehen hat, der mit p dargestellt wird.

  5. Gehen Sie zu Schritt 1.

Der Einschränkungs-Solver von IntelliTest kann Werte aller Typen behandeln, die in .NET-Programmen vorkommen können:

IntelliTest filtert Eingaben heraus, die gegen angegebenen Annahmen verstoßen.

Neben direkten Eingaben (Argumente an parametrisierte Unittests) kann ein Test weitere Eingabewerte aus der statischen Klasse PexChoose ziehen. Die Wahl bestimmt auch das Verhalten der parametrisierten Pseudoobjekte.

Einschränkungs-Solver

IntelliTest verwendet einen Einschränkungs-Solver, um die relevanten Eingabewerte eines Tests und des getesteten Programms zu bestimmen.

IntelliTest verwendet den Einschränkungs-Solver Z3.

Dynamische Code Coverage

Als Nebenwirkung der Laufzeitüberwachung sammelt IntelliTest Daten zu dynamischen Code Coverage. Dies wird als dynamisch bezeichnet, da IntelliTest nur von Code weiß, der bereits ausgeführt wurde. Deshalb kann es keine absoluten Werte für die Coverage angeben, wie dies andere Coveragetools machen.

Wenn IntelliTest z.B. angibt, dass die Coverage aus 5/10 Basisblöcken besteht, bedeutet dies, das fünf von zehn Blöcken abgedeckt wurden, wobei die gesamte Zahl an Blöcken in allen bis jetzt von der Analyse erreichten Methoden zehn ist (im Gegensatz zu allen Methode in der getesteten Assembly vorhanden sind). Je weiter die Analyse fortschreitet und je mehr erreichbare Methoden gefunden werden, desto höher können der Zähler (in diesem Fall 5) und der Nenner (in diesem Fall 10) sein.

Ganze Zahlen und Gleitkommas

Der Einschränkungs-Solver von IntelliTest bestimmt Testeingabewerte primitiver Typen wie z.B. byte, int, float und anderer, um verschiedene Ausführungspfade für den Test und das getestete Programm auszulösen.

Objekte

IntelliTest kann entweder Instanzen vorhandener .NET-Klassen erstellen, oder Sie können IntelliTest verwenden, um automatisch Pseudoobjekte zu erstellen, die eine spezifische Schnittstelle implementieren und sich je nach Verwendung anders verhalten.

Instanziieren vorhandener Klassen

Wo liegt das Problem?

IntelliTest überwacht die ausgeführten Anweisungen, wenn es einen Test und das getestete Programm ausführt. Insbesondere überwacht es jeden Zugriff auf Felder. Dann verwendet es einen Einschränkungs-Solver, um neue Testeingaben zu bestimmen, einschließlich Objekte und deren Feldwerte, sodass der Test und das getestete Programm sich anders verhalten.

Dies bedeutet, dass IntelliTest Objekte eines bestimmten Typs erstellen und deren Feldwerte festlegen muss. Wenn die Klasse sichtbar ist und über einen sichtbaren Konstruktor verfügt, kann IntelliTest eine Instanz der Klasse erstellen. Wenn alle Felder der Klasse sichtbar sind, kann IntelliTest die Felder automatisch festlegen.

Wenn der Typ nicht sichtbar ist oder wenn die Felder nicht sichtbar sind, benötigt IntelliTest Hilfe beim Erstellen von Objekten und dabei, diese in interessante Zustände zu versetzen, um die maximale Code Coverage zu erreichen. IntelliTest könnte Reflektion verwenden, um auf beliebige Weise Instanzen zu erstellen und zu initialisieren. Dies ist in der Regel allerdings nicht die Ideallösung, da dadurch möglicherweise das Objekt in einen Zustand versetzt wird, der bei der normalen Programmausführung nicht auftreten würde. Stattdessen verlässt sich IntelliTest auf Hinweise des Benutzers.

Sichtbarkeit

.NET hat ein aufwendiges Sichtbarkeitsmodell: Typen, Methoden, Felder und andere Member können privat, öffentlich, intern usw. sein.

Wenn IntelliTest Tests erzeugt, versucht es, nur Aktionen durchzuführen, die gemäß den Sichtbarkeitsregeln von .NET Framework innerhalb des Kontexts der erzeugten Tests zulässig sind (wie das Aufrufen von Konstruktoren, Methoden und das Festlegen von Feldern).

Die folgenden Regeln gelten:

  • Sichtbarkeit von internen Membern

    • IntelliTest geht davon aus, dass erzeugte Tests Zugriff auf interne Member haben, die für die einschließende Klasse PexClass sichtbar sind. .NET verfügt über das InternalsVisibleToAttribute, um die Sichtbarkeit interner Member auf andere Assemblys auszuweiten.
  • Sichtbarkeit privater Member und Member der Familie (in C# geschützt) der Klasse PexClass

    • IntelliTest platziert die erzeugten Tests immer direkt in die Klasse PexClass oder in eine Unterklasse. Deshalb geht IntelliTest davon aus, dass es alle sichtbaren Member der Familie (in C# geschützt) verwenden darf.
    • Wenn die erzeugten Tests direkt in die Klasse PexClass platziert werden (üblicherweise durch das Verwenden partieller Klassen), geht IntelliTest davon aus, dass es auch alle privaten Member der Klasse PexClass verwenden darf.
  • Sichtbarkeit öffentlicher Member

    • IntelliTest geht davon aus, dass es alle exportierten Member, die im Kontext der Klasse PexClass sichtbar sind, verwenden darf.

Parametrisierte Pseudoobjekte

Wie teste ich eine Methode, die einen Parameter eines Schnittstellentyps hat? Oder einer nicht versiegelten Klasse? IntelliTest weiß nicht, welche Implementierungen später verwendet werden, wenn diese Methode aufgerufen wird. Möglicherweise ist zur Zeit des Tests noch nicht mal eine Implementierung verfügbar.

Die konventionelle Antwort ist das Verwenden von Pseudoobjekten mit explizitem Verhalten.

Ein Pseudoobjekt implementiert eine Schnittstelle (oder weitet eine nicht versiegelte Klasse aus). Dies stellt keine tatsächliche Implementierung dar, sondern ist nur eine Verknüpfung, die die Ausführung von Tests mit dem Pseudoobjekt ermöglicht. Sein Verhalten wird manuell im Rahmen jedes Testfalls definiert, in dem es zum Einsatz kommt. Es gibt viele Tools, die das Definieren eines Pseusoobjekts und dessen erwarteten Verhaltens erleichtern. Dennoch muss dieses Verhalten manuell definiert werden.

Statt hart codierter Werte in Pseudoobjekten kann IntelliTest die Werte erzeugen. Neben parametrisierten Unittests ermöglicht IntelliTest auch parametrisierte Pseudoobjekte.

Parametrisierte Pseudoobjekte haben zwei verschiedene Ausführungsmodi:

  • choosing (auswählen): Beim Durchsuchen von Code sind Pseudoobjekte eine Quelle zusätzlicher Testeingaben, und IntelliTest versucht, interessante Werte auszuwählen.
  • replay (wiedergeben): Beim Ausführen eines bereits erzeugten Test verhalten sich Pseudoobjekte genauso wie Stubs mit Verhalten (also mit vordefiniertem Verhalten).

Verwenden Sie PexChoose, um Werte für parametrisierte Pseudoobjekte abzurufen.

Strukturen

Der Ansatzpunkt von IntelliTest zu struct-Werten ist ähnlich zu seiner Vorgehensweise bei Objekten.

Arrays und Zeichenfolgen

IntelliTest überwacht die ausgeführten Anweisungen, wenn es einen Test und das getestete Programm ausführt. Es beobachtet insbesondere, wann das Programm von einer bestimmten Länge der Zeichenfolge oder eines Arrays abhängt (und die unteren Grenzen und Längen eines mehrdimensionalen Arrays). Außerdem beobachtet es, wie das Programm die unterschiedlichen Elemente einer Zeichenfolge oder eines Arrays verwendet. Anschließend verwendet es einen Einschränkungs-Solver, um zu bestimmen, welche Längen und Elementwerte möglicherweise dazu führen, dass sich der Test und das getestete Programm interessant verhalten.

IntelliTest versucht, die Größe der Arrays und der Zeichenfolgen zu senken, die benötigt werden, um interessantes Programmverhalten auszulösen.

Abrufen zusätzlicher Eingaben

Die statische Klasse PexChoose kann verwendet werden, um zusätzliche Eingaben für einen Test abzurufen. Zudem kann sie verwendet werden, um parametrisierte Pseudoobjekte zu implementieren.

Sie haben Fragen oder Anmerkungen?

Posten Sie Ihre Ideen und Featureanfragen in der Entwicklercommunity.