Die Frage ist, ob es sinnvoll ist, einen Komponententest zu schreiben, der bestätigt, dass der aktuelle Thread der Haupt-Thread ist. Für und Wider?
Kürzlich habe ich den Unit-Test gesehen, der den aktuellen Thread für den Rückruf des Dienstes bestätigt. Ich bin mir nicht sicher, ob es eine gute Idee ist. Ich glaube, es ist eher eine Art Integrationstest. Meiner Meinung nach sollte der Komponententest die Methode isoliert behaupten und nicht über die Art des Verbrauchers von Dienstleistungen Bescheid wissen.
Unter iOS soll der Verbraucher dieses Dienstes beispielsweise eine Benutzeroberfläche sein, für die standardmäßig eine Einschränkung zum Ausführen eines Codes im Hauptthread besteht.
architecture
unit-testing
object-oriented-design
multithreading
ios
Vladimir Kaltyrin
quelle
quelle
Antworten:
Lassen Sie mich Ihnen meine bevorzugten Unit-Test-Prinzipien zeigen:
Wenn Ihr Unit-Test darauf besteht, dass es sich um den "Haupt-Thread" handelt, ist es schwierig, nicht gegen Nummer 4 zu verstoßen.
Dies mag hart erscheinen, aber denken Sie daran, dass ein Komponententest nur eine von vielen verschiedenen Arten von Tests ist.
Es steht Ihnen frei, diese Grundsätze zu verletzen. Aber wenn Sie dies tun, nennen Sie Ihren Test keinen Komponententest. Setzen Sie es nicht mit den realen Unit-Tests und verlangsamen Sie sie.
quelle
Sollten Sie einen Unit-Test schreiben, der prüft, ob ein Thread der Haupt-Thread ist? Kommt drauf an, aber wahrscheinlich keine gute Idee.
Wenn der Punkt eines Komponententests die ordnungsgemäße Funktion einer Methode überprüfen soll, ist das Testen dieses Aspekts mit ziemlicher Sicherheit kein Testen dieses Aspekts. Wie Sie selbst sagten, handelt es sich konzeptionell eher um einen Integrationstest, da sich die ordnungsgemäße Funktionsweise einer Methode wahrscheinlich nicht ändert, je nachdem, welcher Thread ausgeführt wird, und derjenige, der für die Bestimmung des verwendeten Threads verantwortlich ist, der Aufrufer und nicht der Methode selbst.
Dies ist jedoch der Grund, warum ich sage, dass es davon abhängt, da es sehr wohl von der Methode selbst abhängen kann, falls die Methode neue Threads erzeugt. Obwohl dies aller Wahrscheinlichkeit nach nicht Ihr Fall ist.
Mein Rat wäre, diesen Check aus dem Unit-Test herauszulassen und ihn für diesen Aspekt auf einen geeigneten Integrationstest zu portieren.
quelle
Wenn ein Verbraucher eine Dienstleistung in Anspruch nimmt, gibt es einen ausdrücklichen oder stillschweigenden "Vertrag" darüber, welche Funktionen wie bereitgestellt werden. Einschränkungen, in welchem Thread der Rückruf erfolgen kann, sind Teil dieses Vertrags.
Ich gehe davon aus, dass Ihr Code in einem Kontext ausgeführt wird, in dem es eine Art "Ereignis-Engine" gibt, die im "Haupt-Thread" ausgeführt wird, und eine Möglichkeit für andere Threads, anzufordern, dass der Code für die Ereignis-Engine-Zeitpläne im Haupt-Thread ausgeführt wird.
In der reinsten Sicht von Unit-Tests würden Sie jede Abhängigkeit absolut verspotten. In der realen Welt machen das nur sehr wenige Menschen. Der zu testende Code darf die realen Versionen von mindestens grundlegenden Plattformfunktionen verwenden.
Die eigentliche Frage IMO ist also, ob Sie die Event-Engine und die Laufzeit-Threading-Unterstützung als Teil der "grundlegenden Plattformfunktionen" betrachten, von denen Unit-Tests abhängen dürfen oder nicht. Wenn Sie dies tun, ist es absolut sinnvoll zu überprüfen, ob ein Rückruf im "Hauptthread" erfolgt. Wenn Sie sich über die Event-Engine und das Threading-System lustig machen, entwerfen Sie Ihre Mocks so, dass sie feststellen können, ob der Rückruf im Haupt-Thread stattgefunden hätte. Wenn Sie die Event-Engine verspotten, aber nicht die Laufzeit-Threading-Unterstützung, möchten Sie testen, ob der Rückruf in dem Thread erfolgt, in dem Ihre Mock-Event-Engine ausgeführt wird.
quelle
Ich kann sehen, warum es ein attraktiver Test wäre. Aber was würde es eigentlich testen? Sagen wir, wir haben die Funktion
Jetzt kann ich dies testen und in einem Nicht-UI-Thread ausführen, um zu beweisen, dass ein Fehler ausgelöst wird. Damit dieser Test jedoch einen Wert hat, muss die Funktion ein bestimmtes Verhalten aufweisen, das bei Ausführung in einem Nicht-UI-Thread ausgeführt werden soll.
Jetzt muss ich etwas testen, aber es ist unklar, ob diese Art von Alternative im wirklichen Leben nützlich wäre
quelle
Wenn dokumentiert ist, dass der Rückruf nicht threadsicher ist, um ihn auf einem anderen als beispielsweise dem UI-Thread oder dem Hauptthread aufzurufen, erscheint es in einem Komponententest eines Codemoduls, der den Aufruf dieses Rückrufs bewirkt, durchaus sinnvoll, dies sicherzustellen Es wird nichts getan, was nicht threadsicher ist, indem außerhalb des Threads gearbeitet wird, für den das Betriebssystem oder das Anwendungsframework dokumentiert ist, um die Sicherheit zu gewährleisten.
quelle
Ordnen Sie
Act
Assert
Sollte ein Komponententest bestätigen, dass es sich um einen bestimmten Thread handelt? Nein, da in diesem Fall keine Einheit getestet wird, handelt es sich nicht einmal um einen Integrationstest, da nichts über die Konfiguration der Einheit aussagt ...
Das Festlegen, auf welchem Thread sich der Test befindet, entspricht dem Festlegen des Namens Ihres Testservers oder besser des Namens der Testmethode.
Jetzt kann es sinnvoll sein, die Ausführung des Tests für einen bestimmten Thread anzuordnen, jedoch nur dann, wenn Sie sicher sein möchten, dass ein bestimmtes Ergebnis basierend auf dem Thread zurückgegeben wird.
quelle
Wenn Sie eine Funktion haben, von der dokumentiert ist, dass sie nur im Hauptthread ordnungsgemäß ausgeführt wird, und diese Funktion in einem Hintergrundthread aufgerufen wird, ist dies nicht der Funktionsfehler. Der Fehler ist der Fehler des Anrufers, daher sind Unit-Tests sinnlos.
Wenn der Vertrag so geändert wird, dass die Funktion im Hauptthread korrekt ausgeführt wird und ein Fehler gemeldet wird, wenn er in einem Hintergrundthread ausgeführt wird, können Sie einen Komponententest dafür schreiben, bei dem Sie ihn einmal im Hauptthread und dann in a aufrufen Hintergrund-Thread und überprüfen Sie, ob es sich wie dokumentiert verhält. Jetzt haben Sie einen Unit-Test, aber keinen Unit-Test, der überprüft, ob er auf dem Haupt-Thread ausgeführt wird.
Ich würde es sehr nahe , dass die erste Zeile Ihrer Funktion sollte behaupten , dass es auf dem Haupt - Thread ausgeführt wird .
quelle
Unit-Tests sind nur dann nützlich, wenn sie die korrekte Implementierung dieser Funktion und nicht die korrekte Verwendung testen, da ein Unit-Test nicht sagen kann, dass eine Funktion nicht wie möglich von einem anderen Thread in der restlichen Codebasis aufgerufen wird. ' Ich kann nicht sagen, dass Funktionen nicht mit Parametern aufgerufen werden, die ihre Vorbedingungen verletzen (und technisch gesehen ist das, was Sie testen möchten, im Grunde eine Verletzung einer Vorbedingung in der Verwendung, gegen die Sie nicht effektiv testen können, weil der Test dies kann. ' t einschränken, wie andere Stellen in der Codebasis solche Funktionen verwenden; Sie können jedoch testen, ob Verstöße gegen die Voraussetzungen zu angemessenen Fehlern / Ausnahmen führen.
Dies ist für mich ein Assertionsfall bei der Implementierung der relevanten Funktionen selbst, wie einige andere hervorgehoben haben, oder noch ausgefeilter ist es, sicherzustellen, dass die Funktionen threadsicher sind (obwohl dies bei der Arbeit mit einigen APIs nicht immer praktisch ist).
Auch nur eine Randnotiz, aber "Haupt-Thread"! = "UI-Thread" in allen Fällen. Viele GUI-APIs sind nicht threadsicher (und das Erstellen eines thread-sicheren GUI-Kits ist verdammt schwierig), aber das bedeutet nicht, dass Sie sie von demselben Thread aus aufrufen müssen, der den Einstiegspunkt für Ihre Anwendung hat. Dies kann sogar nützlich sein, wenn Sie Ihre Assertions innerhalb der relevanten UI-Funktionen implementieren, um "UI-Thread" von "Haupt-Thread" zu unterscheiden, z. B. die Erfassung der aktuellen Thread-ID, wenn ein Fenster zum Vergleichen erstellt wird, anstatt vom Haupteinstiegspunkt der Anwendung (dem) reduziert zumindest die Anzahl der Annahmen / Nutzungsbeschränkungen, die die Implementierung nur auf das anwendet, was wirklich relevant ist).
Die Gewindesicherheit war tatsächlich der "Gotcha" -Auslösungspunkt in einem ehemaligen Team von mir, und in unserem speziellen Fall hätte ich sie als die kontraproduktivste "Mikrooptimierung" von allen bezeichnet, die mehr Wartungskosten verursachte als selbst handschriftliche Montage. Wir hatten in unseren Komponententests eine ziemlich umfassende Codeabdeckung sowie ziemlich ausgefeilte Integrationstests, nur um Deadlocks und Rennbedingungen in der Software zu finden, die sich unseren Tests entzogen haben. Und das lag daran, dass die Entwickler zufällig Multithread-Code verwendeten, ohne sich jedes einzelnen Nebeneffekts bewusst zu sein, der möglicherweise in der Kette von Funktionsaufrufen auftreten könnte, die sich aus ihren eigenen ergeben würden, mit einer ziemlich naiven Idee, dass sie solche Fehler im Nachhinein durch einfaches Werfen beheben könnten Schlösser links und rechts,
Für mich ist diese Art, zuerst Multithreading zu betreiben und später Fehler zu beheben, eine schreckliche Strategie für Multithreading, bis ich Multithreading anfangs fast hasse. Sie stellen entweder sicher, dass Ihre Entwürfe absolut sicher sind und dass ihre Implementierungen nur Funktionen mit ähnlichen Garantien verwenden (z. B. reine Funktionen), oder Sie vermeiden Multithreading. Das mag ein bisschen dogmatisch wirken, aber es ist besser, im Nachhinein schwer zu reproduzierende Probleme zu entdecken (oder schlimmer noch nicht zu entdecken), die sich Tests entziehen. Es macht keinen Sinn, einen Raketentriebwerk zu optimieren, wenn dies dazu führen kann, dass er auf halbem Weg auf dem Weg ins All unerwartet aus heiterem Himmel explodiert.
Wenn Sie unweigerlich mit Code arbeiten müssen, der nicht threadsicher ist, dann sehe ich das nicht als ein Problem, das mit Unit- / Integrationstests so oft gelöst werden muss. Ideal wäre es, den Zugang einzuschränken . Wenn Ihr GUI-Code von der Geschäftslogik entkoppelt ist, können Sie möglicherweise ein Design erzwingen, das den Zugriff auf solche Aufrufe von etwas anderem als dem Thread / Objekt einschränkt, das ihn erstellt *. Der für mich ideale Fernmodus besteht darin, es anderen Threads unmöglich zu machen, diese Funktionen aufzurufen, als zu versuchen, sicherzustellen, dass dies nicht der Fall ist.
quelle