Möchten Sie lieber private Inhalte für Tests intern / öffentlich machen oder eine Art Hack wie PrivateObject verwenden?

26

Ich bin ein ziemlicher Anfänger im Testen von Code und war assertvorher eine Hure. Eine Sache, die mich bei Unit-Tests beunruhigt, ist, dass Sie häufig public(oder zumindest internal) Felder privateausfüllen readonlymüssen, die sonst wären, um sie zu entfernen , stattdessen privateMethoden zu erstellen protected virtualusw.

Ich habe kürzlich entdeckt, dass Sie dies vermeiden können, indem Sie Dinge wie die PrivateObject- Klasse verwenden, um über Reflektion auf alles in einem Objekt zuzugreifen . Dies macht Ihre Tests jedoch weniger wartbar (Fehler bei der Ausführung und nicht beim Kompilieren, Fehler durch einfaches Umbenennen, schwierigeres Debuggen ...). Wie ist Ihre Meinung dazu? Was sind die Best Practices bei Unit-Tests zur Zugriffsbeschränkung?

Bearbeiten: Nehmen wir zum Beispiel an, Sie haben eine Klasse mit einem Cache in einer Datei auf der Festplatte und möchten in Ihren Tests stattdessen in den Speicher schreiben.

Zonko
quelle
2
Die Python-Leute haben nur Öffentlichkeit. Sie sind glücklich. Warum ärgern? Warum nicht alles veröffentlichen?
S.Lott
12
Denn das ist in den meisten Top-Sprachen alles andere als Best Practices. Die eigentliche Antwort ist, gute Schnittstellen zu verwenden, sodass Sie mit Scheinobjekten Tests durchführen können.
Rig
2
@Rig: Python ist keine Spitzensprache? Interessant.
S.Lott
7
"Es ist in den meisten Top-Sprachen weit von Best Practices entfernt" impliziert logischerweise nicht (oder spielt sogar darauf an) "Python ist keine Top-Sprache".
Mike Nakis
1
Genau, es ist eine Spitzensprache, aber die meisten Spitzensprachen sind nicht Python und befürworten die Festlegung eines angemessenen Umfangs. Ich stehe zu meiner Aussage. Es gibt Muster, die entworfen wurden, um Software mit hoher Testbarkeit zu erstellen und gleichzeitig sicherzustellen, dass die Variablen den Gültigkeitsbereich beibehalten. Sogar Python-Programmierer neigen dazu, Scope mit Präfixen aus dem zu emulieren, was ich gesehen habe.
Rig

Antworten:

36

Das solltest du niemals müssen

Machen Sie public(oder zumindest internal) Felder, die privateandernfalls gemacht worden wären, um sie zu entfernen readonly, stattdessen privateMethodenprotected virtual

Insbesondere das Nicht-Lesen eines Feldes kann ein unveränderliches Objekt veränderbar machen, und das wäre eine Katastrophe.

Bei Ihren Tests sollten Ihre Objekte als Black Boxes ( Wikipedia ) betrachtet werden. Das bedeutet, dass sie sich nur mit der öffentlichen Schnittstelle der Objekte befassen sollten, nicht mit Details zu ihrer Implementierung.

Wenn ein Objekt mit seiner öffentlichen Schnittstelle nicht ausreichend getestet werden kann, müssen Sie Möglichkeiten finden, seine Schnittstelle mit formalen und nützlichen Erweiterungen zu versehen, die das Testen erleichtern. Beispielsweise würde ein Registrierungssystem mit nur einem Paar von Register / Deregister-Methoden möglicherweise von einer IsRegistered-Methode profitieren, selbst wenn dies für die vorliegende Anwendung nicht erforderlich ist. Trotzdem handelt es sich um eine formale und nützliche Erweiterung der Benutzeroberfläche, die im Übrigen für Tests geeignet ist.

Wichtig ist, dass Sie beim Ändern der Implementierung des Objekts nicht den Komponententest ändern müssen. Theoretisch sollten Sie in der Lage sein, den Komponententest einmal zu schreiben und dann mehrere Programmierer aufzufordern, mehrere völlig unterschiedliche Implementierungen des zu testenden Objekts zu codieren, um mit dem Komponententest zu arbeiten.

Mike Nakis
quelle
2
Genau! Wenn Sie es nicht ohne Reflektion oder Hacks testen können, haben Sie wahrscheinlich einen Fehler in Ihrer API oder Ihrem Design.
Falcon
Wollen Sie damit sagen, dass es als schlechte Praxis gilt , privateMethoden protected virtualzu entwickeln, die beim Verspotten von Tests helfen?
Robula
1
@Robula In meinem Buch ja. Ich schreibe meine Tests nicht einmal in dasselbe Paket wie den Produktionscode, weil ich keinen Zweck habe, auf nicht öffentliche Mitglieder zugreifen zu können. Ich benutze auch keine Mocks. Wenn Sie Mocks verwenden müssen, müssen Sie natürlich alles tun, um das Verspotten zu erleichtern, sodass geschützte virtuelle Inhalte in vielen Situationen ein praktischer Kompromiss sein können. Im Idealfall sind für ein Design jedoch keine Verspottungen erforderlich, sondern nur alternative Implementierungen. Idealerweise handelt es sich beim Testen um Black-Box-Tests und nicht um White-Box-Tests, sodass die Integration ohne Verspottungen getestet wird.
Mike Nakis
13

In C # können Sie mit dem InternalsVisibleToAttributeBefehl zulassen, dass Ihre Testassembly internalKlassen in der zu testenden Assembly sieht . Es hört sich so an, als wüsstest du das schon.

In den meisten Fällen möchte ich nur die öffentliche API meiner Assembly testen. In einem Fall, in dem ich White-Box-Tests für eine Einheit durchführen möchte, verschiebe ich den Code in eine internalKlasse und mache sie zu einer publicMethode dieser Klasse. Dann kann ich einen Unit-Test für diese Klasse programmieren.

Dies eignet sich gut für die Abhängigkeitsinjektion und das Strategiemuster. Sie können die Methode in eine Schnittstelle abstrahieren, die Schnittstelle in den Konstruktor Ihres übergeordneten Objekts einfügen und einfach testen, ob das übergeordnete Objekt die entsprechenden Delegierungen vornimmt. Dann können Sie testen, ob sich das untergeordnete Objekt ordnungsgemäß verhält. Das macht alles modularer.

Scott Whitlock
quelle
8

Nach meiner Erfahrung ist es am besten, die Interna Ihrer Klasse nicht speziell für den Test freizulegen. Auch wenn dies den Test scheinbar einfacher zu schreiben macht. Der Test ist der erste Client Ihrer Klasse. Wenn es also schwierig ist, Ihre Klasse über die öffentliche Schnittstelle zu testen, ist es wahrscheinlich schwierig, Ihre Klasse auch an anderen Orten zu verwenden.

Wie einfach es ist, die Klasse über ihre öffentliche API zu testen, hängt davon ab, wie einfach die Klasse zu verwenden sein wird. Stellen Sie sich die Tests teilweise als eine Möglichkeit vor, die Verwendung Ihrer Klasse zu prototypisieren.

Überlegen Sie, warum Ihr Test etwas über die Klasse erfassen muss, das nicht über die öffentliche API verfügbar ist. Vielleicht tut Ihre Klasse zu viel und muss stattdessen in eine Gruppe kooperierender Klassen zerlegt werden? Anschließend können Sie einige der zusammenarbeitenden Klassen verspotten, um festzustellen, was die getestete Klasse tut.

Versuchen Sie, das Prinzip der Abhängigkeitsinversion anzuwenden und Schnittstellen zwischen Ihren Klassen einzuführen, die Sie in Ihren Tests nachahmen können. Sie können die Mitarbeiter zu Ihrem Test bereitstellen, indem Sie die Steuerung umkehren .

Ziehen Sie auch in Betracht, das Prinzip "Tell Don't Ask" anzuwenden , um die Struktur Ihrer Klassenschnittstelle in einer robusten, locker gekoppelten Art und Weise zu strukturieren, in der die richtigen Informationen offengelegt und der Rest verborgen wird.

flamingpenguin
quelle
6

Ich persönlich bevorzuge es, den Code, den ich teste, so rein wie möglich zu lassen. Wenn der Testcode Hacks benötigt (und in C ++ böse Zeiger usw.), mache ich das gerne.

Ameise
quelle
1
Ich stimme zu, das ist der Weg, es zu tun. Behalten Sie die Hässlichkeit im Testcode, wo es nichts schaden kann.
Alan Delimon
@ AlanDelimon Könnte es nicht den Verstand nachfolgender Wartungsprogrammierer verletzen?
Flamingpenguin
1
@flamingpenguin hässlicher Code kann Programmierer überall verletzen; Aber hässlicher Testcode wird wahrscheinlich weniger Schaden anrichten, da er nur Leute betrifft, die die Klasse modifizieren, und nicht auch Leute, die sie einfach konsumieren.
Dan Neely
2
@flamingpenguin Es könnte sein, aber wenn Sie hässlichen Code irgendwo ablegen müssen, könnte es genauso gut in einem Komponententest sein, in dem es weniger wahrscheinlich ist, dass Änderungen erforderlich sind und Teil der Anwendung sind.
Alan Delimon
5

Die Sichtbarkeit Ihres Codes hat nichts mit Ihren Unit-Tests zu tun!

Sie machen Ihre Methoden privat, nicht mit dem Ziel, sie vor dem Testen zu verbergen, und Sie machen sie nicht öffentlich, um sie zum Testen freizugeben, oder? Dies ist Ihre Implementierung, die hier wesentlich ist, nicht die Tests - diese Server als Beweis für Ihren Code, aber sie sind nicht Ihr Code .

Es gibt viele Möglichkeiten, den Zugriff auf verborgene (private oder interne) Methoden und Eigenschaften zu ermöglichen. Viele Test-Frameworks und IDEs (z. B. Visual Studio) unterstützen Sie dabei, indem sie alles generieren, was für den Zugriff benötigt wird. Sie müssen lediglich Ihre Testfälle erstellen. Warum also Sorgen machen? Halten Sie Ihren Code besser sauber und ordentlich.

Alexander Galkin
quelle