# Unittest - Benutzte Objekte/Variablen aufzeichnen



## _Andi91 (18. Jul 2018)

Hallo,

ich bin auf der Suche nach einem Framework/Tool das folgendes leistet:

Es gibt z.B. ein Objekt Kunde mit Name, Vorname, Strasse etc.
Es wird zur Laufzeit eine Methode entferneVornameWennNameIstMax(Kunde k) die nur den Vornamen auf null setzt wenn Name = "Max" aufgerufen.
-> Es wird erkannt, dass nur die Variablen Name und Vorname des Objekts Kunde k relevant sind und es wird ein Objekt Kunde mit setName("Max") + setVorname(...) generiert.


Dieses generierte Objekt kann dann vom Entwickler als Basis genutzt werden um einen Unittest zu schreiben und man spart sich das manuelle initialisieren von Testobjekten.
In dem Fall wäre der Aufwand natürlich noch überschaubar aber in der Praxis hat man doch öfter mal Objekte die sehr viel größer sind und es werden wesentlich mehr Variablen genutzt als 2,3.

Ist so ein Tool technisch überhaupt möglich?
Ich denke aber schon.
CodeCoverage Tools etc. arbeiten ja ähnlich.


----------



## mrBrown (18. Jul 2018)

_möglich_ ist sicherlich alles, aber ob es praktikabel ist?

Üblicherweise lassen sich die wenigstens Objekte so einfach initialisieren (und wenn man DI nutzt, dann bietet das Framework das ja schon?).
Dann lassen sich in den wenigsten Fällen die Werte so einfach setzen (könnte man lösen, indem man alle Setter-Aurufe auf dem Objekt trackt oder direkt die Felder setzte - ersteres dürfte zu viel Unsinn in den Objekten führen und letzteres ist nicht wirklich sauber)
Dann hat man vermutlich nur in wenigen Fällen primitive Objekte als Abhängigkeit, man muss also wieder den gesamten Abhängigkeitsgraph tracken.


In den Tests würde ich dann üblicherweise auch die Verändernden Methoden testen - wenn die Objekte autogeneriert auf wer weiß was für einem Weg gefüllt werden, dann fällt das schon mal weg.
Sobald man Abhängigkeiten zu anderen Objekten hat, würde man üblicherweise mocken, fällt dann auch weg.

Dann muss die Methode ja schon mit einem Test ausgeführt werden, damit getrackt werden kann, welche Objekte relevant sind - man braucht also das Objekte schon einmal fertig, damit man es danach autogenerieren kann.
Wenn ich es aber schon fertig habe, warum sollte man es dann noch generieren?

Außerdem kollidiert das ganze mit TDD, die Methode muss fertig sein, damit Abhängigkeiten getrackt werden können - ich kann also nicht mehr Testgetrieben entwickeln.
Zusätzlich, angenommen die Methode hat noch einen Fehler, würden fehlerhafte Abhängigkeitsgraphen und damit auch fehlerhafte Testobjekte generiert werden, sodass der Bug quasi nicht findbar ist.



_Andi91 hat gesagt.:


> In dem Fall wäre der Aufwand natürlich noch überschaubar aber in der Praxis hat man doch öfter mal Objekte die sehr viel größer sind und es werden wesentlich mehr Variablen genutzt als 2,3.


Das würde ich in dem Fall als Hinweis auf ein verbesserungswürdiges Design sehen


----------



## httpdigest (18. Jul 2018)

Da aus deinen Ausführungen nicht genau hervorgeht, was jetzt eigentlich vom Programmierer explizit definiert/aufgerufen wird, und was von einer möglichen Library automatisch generiert/aufgerufen werden soll, habe ich es folgendermaßen verstanden:

1. der Programmierer erstellt explizit eine Klasse Kunde mit den expliziten Attributen Name, Vorname und Strasse
2. der Programmierer schreibt manuell eine Methode entferneVornameWennNameIstMax, welche eine Bedingung enthält, die einen Equals-Vergleich auf Kunde.getName() mit "Max" macht und dann Kunde.setVorname(null) aufruft
3. Du erwartest jetzt, dass es eine Library gibt, die in der Lage ist, automatisch aus dem Code der entferneVornameWennNameIstMax() Methode folgert, dass nur bestimmte Testszenarien mit bestimmten Wertekonstellationen Sinn machen, weil ja die Methode ja nur getName() und setVorname() verwendet.
4. Aus den ermittelten Wertekonstellationen, die in diesem Fall nur name="Max" und vorname=(irgendwas außer null) sind, soll automatisch ein Kunde-Objekt mit diesen Werten erzeugt werden, welches dann zum Testen der Methode entferneVornameWennNameIstMax verwendet werden könnte.

Richtig?

Meines Wissens gibt es so ein Tool nicht. Es ist zwar rein theoretisch (und auch praktisch) möglich, per Control Flow und Data Flow Analysis herauszufinden, welche Methoden alle auf einem bestimmten Objekt ausgeführt werden, das hat allerdings aber Grenzen. Und ich glaube, bisher gibt es noch keine Testing-Library, welche diesen mühsamen Weg geht.

CodeCoverage-Tools funktionieren anders. Hierbei wird lediglich jede Zeile instrumentiert, es wird also Code hinzugefügt, der, wenn der Code ausgeführt wird, dem Testtreiber sagt, dass diese Zeile ausgeführt wurde. Zusätzlich werden dann noch Sprunganweisungen vor und nach dem Sprung bei bedingten Anweisungen und Schleifen instrumentiert, um sagen zu können, dass diese if-Anweisung in z.B. 2 von 4 möglichen Fällen zu true ausgewertet wurde.

Das was du möchtest ist aber, dass aus dem Code statisch (und nicht dynamisch, also zur Laufzeit) ermittelt wird, welche Wertekonstellationen für ein bestimmtes Outcome sorgen. Und sowas klingt schon fast nach dem Halteproblem.


----------



## Xyz1 (18. Jul 2018)

httpdigest hat gesagt.:


> Und sowas klingt schon fast nach dem Halteproblem.


Aber nur fast....  Er sucht nach einen zusätzlichen Compilierungsschritt(*).
Die vom Compiler ausgeführten, semantischen Ersetzungen (in der semantic analysis or context sensitive analysis) sind aber stark begrenzt.
(*) Diesen gibts aber nicht.

C.C.-Tools besitzen keine Logik, ändern den Programmcode und greifen erst zur Laufzeit....

Aber bevor ich weiter-schreibe nochmal ne Verständnisfrage denn es ist noch nicht eindeutig was der TE will: Alle möglichen ungültigen Eingaben sollen vorher ausgeschlossen werden? (... Annotationen ja auch dynamisch....)


----------



## _Andi91 (18. Jul 2018)

OK, ich sehe schon es wird eher schwer.

Hier nochmal kurz zusammengefasst was ich will:

- Es soll ermittelt werden welche Attribute (inkl. Attribute in Unterobjekten, ...) eines Objekts innerhalb einer Methode (inkl. aufgerufene Methoden, ...) genutzt werden und anschließend eine Initialisierung für das Objekt generiert werden, dass alle genutzten Attribute setzt wie sie zum Zeitpunkt des Methodenaufrufes gesetzt waren.
- Ergebnisse die daraus resultieren o.ä. sind egal es geht nur darum eine Initialisierung des Objekts mit den relevanten Werten für diese Methode zu generieren. Die Adresse des Kunden, seine Verträge etc. z.B. sind hier nicht relevant sondern nur Name+Vorname.

Ich habe ein Tool gefunden, dass (soweit ichs verstanden habe) die Anforderung zT erfüllt (https://github.com/almondtools/testrecorder) allerdings wird hier eine komplette Initalisierung für das Objekt erstellt und nicht nur reduziert auf die tatsächlich benutzten Attribute.

Was ich bräuchte ist tatsächlich (bzw. könnte tatsächlich so umgesetzt sein) ein Mix aus statischer und dynamischer Analyse.
Statisch -> Welche Attribute sind relevant
Dynamisch -> Zur Laufzeit Objekt Initialisierung generieren für relevante Attribute.


----------



## mrBrown (18. Jul 2018)

_Andi91 hat gesagt.:


> Statisch -> Welche Attribute sind relevant


Das dürfte nur in Ausnahmefällen klappen, schon mit der ersten `List<?>` bricht das


----------



## Xyz1 (18. Jul 2018)

_Andi91 hat gesagt.:


> Statisch -> Welche Attribute sind relevant


Nur dass das keine statische Analyse mehr ist.


----------



## Xyz1 (18. Jul 2018)

mrBrown hat gesagt.:


> schon mit der ersten List<?> bricht das


Glaube das hat mit List nix zu tun. Auch nicht mit variablem Typ.


----------



## AndiE (18. Jul 2018)

So richtig erschließt sich mir der Sinn noch nicht. Ich würde doch davon ausgehen, dass ich so eine Funktion "Lösche Vorname, wenn Vorname="MAX" " mehrmals testen würde, weil ich in die Arbeit der Funktion verschiedene Erwartungen habe. Einmal, wenn ein gültiger Eintrag ist, der "Max" ist, dann ein gültiger Eintrag, der nicht "Max" ist, und dann ein gelöschter (ungültiger) Eintrag. Im Endeffekt brauche ich für alle drei Tests ein grünes Ergebnis. Ich habe gelernt, dass sich Testklassen an die Codeklassen "anhängen" sollen(Kunde und TestKunde), und man Abhängigkeiten mit verschiedenen Dingen erzeugen kann, wie "Dummies" oder  Mocks.


----------



## httpdigest (18. Jul 2018)

Ab einem gewissen Punkt der Testgenerierung-/Testautomation wird aus dem ganzen natürlich eine Tautologie: Wir ermitteln uns Testkonfigurationen/-werte aus den Methoden, die wir testen wollen. Damit testen wir aber nicht mehr, dass die Methode das tut, was sie soll, sondern nur, dass sie das tut, was sie tut. Wir testen also gegen die Implementierung und nicht gegen die Spezifikation.
Es gibt nunmal kein automatisierbares Verfahren, das testet, ob Software korrekt ist, weil die Spezifikation ja nicht in der Software enthalten ist, nur die (hoffentlich korrekt) Implementierung der Spezifikation.
Was man aber durchaus machen kann, ist, aus einer Implementierung, von der man weiß, dass sie korrekt ist, eine Spezifikation in Form von Testfällen abzuleiten. Das ist dann aber nur für Regression hilfreich.


----------



## _Andi91 (20. Jul 2018)

mrBrown hat gesagt.:


> Das dürfte nur in Ausnahmefällen klappen, schon mit der ersten `List<?>` bricht das



Sehe ich nicht unbedingt so.
Als Mensch kann ich ja auch erkennen wann welche Attribute und welche Listeneinträge (wenn z.B. Kunde aus Liste<Kunde> Name = "Meier") genutzt werden.

Bitte auch nicht so auf diese Methode entferneVornameWennNameIstMax versteifen.
Das war nur ein minimales technisches Beispiel weil mir gerade nichts besseres eingefallen ist.
Wie gesagt ist es auch komplett irrelevant was die Methode tut mir geht es nur darum den Zustand der Objekte VOR Methodenaufruf zu persistieren aber eben nur den relevanten Teil für die Methode...

Ich muss dazu vielleicht auch sagen mir geht es konkret um ein sehr großes Projekt das mehrere Jahre auf dem Buckel hat und die Unittest Abdeckung sagen wir mal gering ist 
Bei neuen System tut man sich vielleicht leichter wenn man von vornherein mehr Wert auf Testbarkeit etc. legt.


----------



## mrBrown (20. Jul 2018)

_Andi91 hat gesagt.:


> Sehe ich nicht unbedingt so.
> Als Mensch kann ich ja auch erkennen wann welche Attribute und welche Listeneinträge (wenn z.B. Kunde aus Liste<Kunde> Name = "Meier") genutzt werden.


List steht da stellvertretend für ein beliebiges Interface.

Sobald irgendwo gegen das Interface programmiert wird, kannst du statisch nicht mehr die zur Laufzeit genutzte Implementierung bestimmen, und weißt damit nicht mal, ob die überhaupt Variablen hat.


----------



## _Andi91 (20. Jul 2018)

mrBrown hat gesagt.:


> List steht da stellvertretend für ein beliebiges Interface.
> 
> Sobald irgendwo gegen das Interface programmiert wird, kannst du statisch nicht mehr die zur Laufzeit genutzte Implementierung bestimmen, und weißt damit nicht mal, ob die überhaupt Variablen hat.



Ja das stimmt, wenn ich z.B. ein Interface IKunde habe und eine Methode istRelevant() und in der Impl Klasse auf einen Boolean geprüft würde könnte ich das erst zur Laufzeit ermitteln, dass der Boolean relevant ist und der Rest der Impl Klasse nicht...

Aber theoretisch sollte das trotzdem alles möglich sein denke ich... irgendwie...


----------



## httpdigest (20. Jul 2018)

Wenn du aber gegen ein Interface implementierst, kann die hypothetische Testautomatisierungs-Library auch einfach eine Klasse (oder z.B. per Mockito einen Stub) generieren, die/der das Interface IKunde implementiert und für istRelevant() den entsprechenden Wert zurückliefert.
Du hättest dann zwar nicht eine Implementierung deiner zur Laufzeit verwendeten tatsächlichen KundeImpl Klasse, sondern nur eine beliebige Klasse, die das IKunde Interface implementiert, aber das sollte der Implementierung ja egal sein, wenn sie sowieso nur gegen das Interface programmiert ist.


----------



## mrBrown (20. Jul 2018)

_Andi91 hat gesagt.:


> Aber theoretisch sollte das trotzdem alles möglich sein denke ich... irgendwie...


Statisch eben nicht.



httpdigest hat gesagt.:


> Wenn du aber gegen ein Interface implementierst, kann die hypothetische Testautomatisierungs-Library auch einfach eine Klasse (oder z.B. per Mockito einen Stub) generieren, die/der das Interface IKunde implementiert und für istRelevant() den entsprechenden Wert zurückliefert.
> Du hättest dann zwar nicht eine Implementierung deiner zur Laufzeit verwendeten tatsächlichen KundeImpl Klasse, sondern nur eine beliebige Klasse, die das IKunde Interface implementiert, aber das sollte der Implementierung ja egal sein, wenn sie sowieso nur gegen das Interface programmiert ist.



Zur Laufzeit ja, aber eben nicht statisch


----------

