Laden...

public / internal vs. Unit-Tests

Erstellt von LittleBoy vor 12 Jahren Letzter Beitrag vor 12 Jahren 3.805 Views
L
LittleBoy Themenstarter:in
53 Beiträge seit 2007
vor 12 Jahren
public / internal vs. Unit-Tests

Hallo zusammen,

ich muss zugeben, dass ich alte Muster gewohnt bin: Ich habe gelernt: "Alles, was nicht von aussen gebraucht wird ist nicht public" - insofern mache ich in einer Assembly auch fröhlich alles internal, was nicht von aussen gebraucht wird.

Nun soll ich einen Unit-Test für meine Sachen schreiben - und da brauche ich dann ja plötzlich den Zugriff auf alles, was ich testen will. Ergo: Alles ist public.

Insofern: Übersehe ich da etwas, oder soll ich mich von dem guten alten "Nur das Notwendige ist public" verabschieden?

C
1.214 Beiträge seit 2006
vor 12 Jahren

Naja, es gibt zumindest Friend Assemblys:

[assembly:InternalsVisibleTo("AssemblyTest")]

Aber ich weiß nicht, ob man immer so viele interne Sachen testen muss. Deine Assembly hat eine öffentliche API, sonst ist sie ja nutzlos. Und da drüber sollten sich auch alle oder die meisten internen Sachen testen lassen.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo LittleBoy,

du solltest auf keinen Fall eine Methode nur deshalb public machen, um einen Unit-Test für sie durchführen zu können. Ich verstehe auch insgesamt nicht, wie du darauf kommst, du müsstest mehr öffentlich machen als schon ist. Mit Unit-Tests testet man doch normalerweise sowieso nur, was schon öffentlich ist. Was nicht öffentlich ist, muss nicht (direkt) getestet werden. Es spricht sogar einiges dagegen, interne Methoden unitzutesten, insbesondere weil man dadurch die Freiheit, die Implementierung nach belieben ändern zu können, solange das Verhalten nach außen gleich bleibt, unnötig einschränkt.

Ob man private Methoden unittesten soll, wurde schon mehrmals besprochen. Die Meinungen gehen zwar auseinander, aber der Tenor lautet m.E. Nein.

herbivore

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo LittleBoy,

um es noch in anderen Worte zu sagen: die internen/privaten Methoden werden durch die Tests der öffentlichen Methoden mitgetestet. Und wenn ein interner/privater Member durch solche Tests nicht erreicht bzw. abgedeckt werden, dann sind diese Member auch unnötig. Unnötige Member kann nach einem YAGNI-Vorgehen verhindert werden. Oder andersrum: wenn du nach TDD vorgehst ergeben sich gar keine unnötigen Member.

herbivore hats auch schon angesprochen: Durch die öffentlichen Member legst du quasi einen Vertrag fest, der im Sinne der Kompatibilität nicht mehr geändert werden soll. Bei internen/privaten Member exisitert dieser Vertrag nicht und du hast sämtliche Freiheiten diese Implementierung zu ändern (z.B. durch Refactoring). Würde hingegen alles public sein, dann verlierst du diesen doch sehr wichtigen Vorteil.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

D
500 Beiträge seit 2007
vor 12 Jahren

Hallo zusammen,

Mit Unit-Tests testet man doch normalerweise sowieso nur, was schon öffentlich ist. Was nicht öffentlich ist, muss nicht (direkt) getestet werden.

Aber ich weiß nicht, ob man immer so viele interne Sachen testen muss.

ich kann mich meinen Vorrednern dahin gehend anschliessen, dass man interne Dinge nicht explizit auf public setzen sollte, denn dann zwingst du den Benutzer Deiner API mehr zu sehen und erschwerst eventuell das Erlernen Deiner API, weil man sich auf einmal mit viel mehr Dingen konfrontiert sieht, als notwendig. Das InternalsVisibleTo Attribut ist schon in Ordnung.
Nun aber zu dem Thema, ob man Dinge, die mit dem internal Keyword markiert sind, testen solle: Ich sage ja.
Wir brauchen uns nicht darueber zu unterhalten, dass public Konstrukte getestet werden muessen. Internal Dinge werden irgendwo implizit ueber die offentliche API abgetestet, allerdings wachsen interne Klassen auch und erreichen einen gewissen Komplexitaetsgrad. Es vereinfacht das Testen bzw. die Komplexitaet des eigentlichen Tests, wenn ich bestimmte Dinge der internen Klasse explizit in einer dazugehoerigen Testklasse teste. Die Tests werden uebersichtlicher und auch praeziser, denn man sieht direkt, dass mit der internen Klasse etwas nicht stimmt, wenn ein Test fehlschlaegt. Zudem wird die Spezifikation fuer die interne Klasse durch explizite Tests offensichtlicher gemacht.
Wenn neuer Code entsteht, gibt es eine offentliche API, die abgetestet wird. In nachfolgenden Schritten refactored man seinen Code, erstellt eventuell interne Klassen, um das SRP Prinzip besser zu beachten. Wenn die neu entstandene interne Klasse noch klein ist, dann moegen die Test ueber die oeffentliche API hinreichend sein. Ab einem bestimmten Grad allerdings nicht mehr. Somit sehe ich es als legitim an, explizit Test dafuer zu schreiben, sobald die interne Klasse komplexer wird, besonders wenn diese dann noch von anderen Klassen verwendet wird. Ich sehe auch, dass es noch mehr die Angst nimmt Aenderungen an so einer Klasse vorzunehmen. Es ist ein noch etwas feineres Sicherheitsnetz fuer den Entwickler, man kann sich explizit auf die interne Klasse fokusieren. In einem weiteren Schritt testet man dann die offentliche API.
Besteht der "internal" Code schon, dann mach es das Testen einfacher, denn hinter einer oeffentlichen API kann ein hoher Komplexitaetsgrad entstehen, der das Testen nicht gerade einfach gestaltet. So kann man sich sukzessive durch die internen Klassen bis nach oben zur offentlichen API arbeiten.

Gruss,
DaMoe

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo DaMoe80,

interne Klassen testen ist aber was ganz anderes, als interne Methoden zu testen. In gewissem Sinne sind alle Klassen, die von einer anderen Klasse benutzt werden, ohne dass diese Klasse in der Schnittstelle der Klasse auftaucht, in diesem Kontext eine interne Klasse - unabhängig von den Zugriffsmodifiern der benutzen Klasse selbst. Nehmen wir an, ich schreibe eine Dictionary-Klasse, die in beide Richtungen funktioniert, so dass man nicht zur zum Key schnell das Value bekommt, sondern auch vom Value schnell den Key. Dann kann es sein, dass ich diese Klasse nur innerhalb einer anderen verwende, also quasi intern. Das ändert natürlich nichts daran, dass ich einen Unit-Test für die Dictionary-Klasse schreiben sollte, aber da natürlich wieder nur für die öffentlichen Member. Getestet werden sollten alle Units, egal ob diese Units (momentan) nur intern verwendet werden oder nicht, aber von den Units nur die öffentlichen Member.

herbivore

2.891 Beiträge seit 2004
vor 12 Jahren

also quasi intern

Ich denke, wir sollten und müssen hier zwischen "nur intern benutzten" Typen/Member und denen, deren Zugriff als internal gekennzeichnet sind, unterscheiden. Und cih meine, dass wir hier um den Zugriffsmodifizierer internal sprechen.

Als internal gekennzeichnete Dinge sind nun mal nicht "ganz öffentlich" und auch nicht "total privat". Meiner Meinung nach sind sie aber öffentlich genug, um getestet zu werden bzw. sogar zu müssen. Denn in der beinhaltenden Assembly heißt es ja genau das. Und dass man aus der Test-Assembly nicht mehr an die internal-Sachen rankommt, kann man dann durch das InternalsVisibleTo-Attribute lösen.

D
500 Beiträge seit 2007
vor 12 Jahren

@Herbivore: Ich weiss nicht worauf Du hinaus willst. Etwas anderes als in deinem zweiten Posting habe ich nicht behauptet. Wenn ich mal von "interne Klassen" gesprochen habe, war auch internal gemeint, was ich hauptsaechlich auch in meinem Beitrag verwendet habe. Ich habe somit auch auf nichts anderes angespielt als das, was dN!3L erwaehnt.

Ich habe mich vielmehr auf Folgendes bezogen:

Was nicht öffentlich ist, muss nicht (direkt) getestet werden. Es spricht sogar einiges dagegen, interne Methoden unitzutesten, insbesondere weil man dadurch die Freiheit, die Implementierung nach belieben ändern zu können, solange das Verhalten nach außen gleich bleibt, unnötig einschränkt.

Und das ist schon eine sehr absolute Aussage fuer mich und klingt fuer mich falsch bzw. eventuell in diesem Fall missverstaenglich. Was verstehst Du nun unter internen Methoden? Meinst Du "private" Methoden, die bspw. nun die Sichtbarkeit "public" besitzen?

Gruss,
DaMoe

L
LittleBoy Themenstarter:in
53 Beiträge seit 2007
vor 12 Jahren

Damit das hier nicht in die totale Grundsatzdiskussion und Theorie abwandert:

Konkret geht es darum:
Ich habe ein Programm geschrieben, was Projekte erstellt, diese dann mehr oder weniger komplexen Berechnungen unterzieht und dann diese Berechnungen grafisch auswertet.

In der Assembly ist letztlich nur das MainForm public - alles andere ist internal.

Jetzt wollte ich halt testen, ob z.B: ein Beispielprojekt korrekt durchgerechnet wird. Und damit stellt sich dann das Problem, dass alle Klassen z.B: zum Laden eines Projektes internal sind und somit aus der Test-Assembly nicht sichtbar.

Das muss und würde ich schon gerne testen, weil ich an dieser Stelle ungern den Nutzer Beta-Tester spielen lassen möchte - und voreilige Änderungen z.B: in einem Berechnungsalgorithmus dann zwar durchkompilieren aber ggf. zu falschen Resultaten an anderer Stelle führen.

Insofern denke ich, dass der Tip mit der InternalsVisibleTo mein Problem lösen sollte - insofern vielen Dank dafür. Das Attribut kannte ich noch nicht.

F
10.010 Beiträge seit 2004
vor 12 Jahren

Das wird immer passieren, weil es eben immer Glaubenskriege dazu gibt.

Die einen wie herbivore meinen das nur öffentliche Kontrakte zwingend zu Unittesten sind,
und die anderen meinen 100% Codecoverage ist nicht genug, am besten auch noch Testen von Unittests.

Vielleicht sollte man mal überlegen aus welchem Grund UnitTests "erfunden" wurden,
jedenfalls nicht zum Selbstzweck.

A
350 Beiträge seit 2010
vor 12 Jahren

Hi LittleBoy,

es würde sicher Sinn machen, deine Anwendung so zu refaktorieren , dass Teile in eigene Klassen wandern und demnach testbar werden.
Alles in einer Form mit internal Methoden zu machen halte ich für den falschen Weg.

Grüße

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo LittleBoy,

für deine Anwendung gilt wohl das von Ahrimaan erwähnte.
Wenn du eine Architektur basierend auf MVP o.ä. verwendest dann sollte es wenigstens drei Namespace geben: Model, View und Presenter. Ob diese auch in drei Assemblies (MeinProjekt.Model, MeinProjekt.View und MeinProjekt.Presenter) aufgeteilt werden hängt von der Größe des Projekts ab. Nehmen wir mal auch die Aufteilung in Assemblies an und dann erscheint es logisch dass es mehr public Member als nur die MainForm geben muss - mindestens eine Model-Klasse und ein Presenter muss auch nocht public sein. Somit hast du auch schon die Testbarkeit erreicht und das weiter oben von herbivore erwähnte kann umgesetzt werden.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo DaMoe80,

ich denke der Knackpunkt ist, dass ich über nicht-öffentliche Methoden und du über nicht öffentliche Klassen gesprochen hast. Nicht öffentliche Methoden sollte man nicht testen. Nicht öffentliche Klassen kann man - vor allem, wenn sie in sich abgeschlossen sind - natürlich testen und sollte das vermutlich auch, es sei denn, es ist einem aus bestimmten Gründen wichtig, dass man deren Schnittstelle später noch ändern kann. Im Grunde steht und fällt alles damit, wie eigenständig und stabil die internen Klassen sind. Je eigenständiger und stabiler sie sind, desto eher sollte man sie unittesten. Natürlich wieder nur ihre öffentlichen Member, also die, die mit public gekennzeichnet sind.

Wie man an der Antwort von LittleBoy sieht, gibt es auch eine große Spannbreite, welche Klasse man als intern ansieht (und internal macht). Die Klasse von LittleBoy hätte ich nie im Leben internal gemacht. Ich mache nur ganz wenige Klassen internal, nämlich solche, bei denen es schädlich wäre, wenn der Benutzer der Assembly direkt auf sie zugreifen könnte. Das oben beschriebene bidirektionale Dictionary kann von mir aus überall verwendet werden, nicht nur von der Klasse, die es ursprünglich benötigt hat. Kein Grund es internal zu machen.

Hallo FZelle,

Vielleicht sollte man mal überlegen aus welchem Grund UnitTests "erfunden" wurden,

dann mal los. Wozu dienen sie aus deiner Sicht? Daran können wir dann ja die verschiedenen Positionen messen.

herbivore

F
10.010 Beiträge seit 2004
vor 12 Jahren

Unittests sind entstanden um jederzeit die Korrekte Funktion der Software sicherzustellen.
Was bedeutet das?
Das das was als zugesicherte Funktion zur Verfügung steht jederzeit genau das macht, was es soll.
Zugesicherte Funktionen sind die zugänglichen Kontrakte der einzelnen Units.

Ich musste mal eine SW refaktorieren die 98% Codecoverage hatte, worauf die Entwickler und die Projektleitung mehr oder weniger Stolz waren.
Da sich an den Funktionen und der öffentlichen Schnittstelle ( incl Plugins ) nichts ändern sollte waren vorhandene Unittests hilfreich.
Beim Refaktoring wird aber der interne Aufbau Teilweise grundlegend geändert.
Hier war es so, das wirklich "nur" Unittests aber keine Behaviortests/Integrationtetss vorhanden waren, wodurch aber nur die Funktionen der Einzelteile, aber nicht deren Zusammenspiel sichergestellt war.

Das Refaktoring war ein Graus, denn ich musste mehr Zeit mit Pflege/Beseitigung von Unittests verbringen, als für den eigentlichen Code.
Die Software hatte nach meinem rigorosen Refaktoring ( nur die SW, nicht die Unittest ) 50% weniger Code.
Warum war das so?
Hier war wirklich alles und jedes ( auf Private oder Internal ) per Unittests abgedeckt.

Es gab hunderte Funktionen die Ähnliches machten, die aber bis auf die "Letzte" nicht mehr benutzt wurden, da sich z.b. Anforderungen geändert hatten.
Wenn es aber dadurch massenweise toten Code gibt, sagt 98% Codecoverage nichts mehr aus.
Und wenn dann der Aufwand zur Erstellung der Tests grösser als für die Software selber ist, werden Unittests zum Selbstzweck.
Und dann verlieren auch alle Projektbeteiligten die Lust an Unittests, weil mit einem mal der Sinn der Software nicht mehr im Vordergrund steht, sondern nur die Tests.

Manchmal sollte man einen Schritt zurück machen um das ganze Bild zu sehen, denn es sollte zwar handwerklich gut gemacht sein, aber eine schielende Monalisa ( macht sie ja tatsächlich ) ist zwar was besonderes, aber wenn es zuviel wird, mag jedes einzelne Auge Handwerklich OK gemacht sein, macht aber ab einem gewissen Grad das Bild kaputt.

Wie bei allem, die Dosis machts.

L
LittleBoy Themenstarter:in
53 Beiträge seit 2007
vor 12 Jahren

Wenn du eine Architektur basierend auf MVP o.ä. verwendest dann sollte es wenigstens drei Namespace geben: Model, View und Presenter. Ob diese auch in drei Assemblies (MeinProjekt.Model, MeinProjekt.View und MeinProjekt.Presenter) aufgeteilt werden hängt von der Größe des Projekts ab.

Das ist genau der Punkt: Ich habe es nicht in drei Assemblies geteilt - ich unterteile MVP in diesem Fall nur nach Unterordnern und dementsprechende Namespaces - insbesondere da sich die Menge vom Code in diesem Programmteil eigentlich in Grenzen hält.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo LittleBoy,

ob du die Unterteilung in mehrere Assembly aktuell machst oder nicht, spielt nicht die Hauptrolle. Wichtig ist, dass du die Modifier so setzt, dass eine Aufteilung in (drei) verschiedene Assemblys möglich ist, ohne den Code zu ändern.

herbivore