Unit-Test, der bestätigt, dass der aktuelle Thread der Haupt-Thread ist


8

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.


1
FWIW, in unserem Code machen wir es nicht zu einer Unit-Test-Assertion, sondern zu einer Runtime-Assertion, da es hier wirklich darauf ankommt.
user1118321

Scheint eher eine Behauptung als ein tatsächlicher Testfall zu sein.
Whatsisname

Antworten:


11

Lassen Sie mich Ihnen meine bevorzugten Unit-Test-Prinzipien zeigen:

Ein Test ist kein Komponententest, wenn:

  1. Es spricht mit der Datenbank
  2. Es kommuniziert über das Netzwerk
  3. Es berührt das Dateisystem
  4. Es kann nicht gleichzeitig mit einem Ihrer anderen Komponententests ausgeführt werden
  5. Sie müssen spezielle Dinge an Ihrer Umgebung tun (z. B. das Bearbeiten von Konfigurationsdateien), um sie auszuführen.

Eine Reihe von Unit-Testing-Regeln - Michael Feathers

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.

Geben Sie hier die Bildbeschreibung ein

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.


Wie testet man einen Logger? Müssen Sie IO moc? Das scheint widerlich.
Am

1
@opa Wenn Ihr Logger eine interessante Geschäftslogik hat, isolieren Sie diese und testen Sie sie. Langweiliger Code, der bestätigt, dass wir ein Dateisystem haben, erfordert keine Unit-Tests.
candied_orange

@candied_orange Also müssen Sie jetzt die interne Logik offenlegen? Denn wie sonst werden Sie die vom Logger generierte Zeichenfolge trennen, bevor sie in eine Datei ausgegeben wird, indem Sie sie in eine private Methode aufteilen ?
Am

@opa Wenn Sie testen müssen, dass Sie dies tun können, indem Sie Ihren eigenen Stream übergeben. Unit-Tests Testeinheiten, keine Systemressourcen.
candied_orange

@candied_orange, außer wenn der Logger nur den Dateinamen verwendet, an den er als Eingabe ausgegeben werden soll. Punkt ist nicht, dass "Unit-Tests Testeinheiten nicht die Systemressourcen" falsch ist, es ist 100% korrekt. Sie können nicht so dogmatisch sein, zu sagen, wenn Tests Datei io berühren, dass es kein gültiger Unit-Test ist. Das Testen eines Loggers durch Lesen seiner Dateiausgabe ist viel einfacher als das Veröffentlichen privater Funktionen, nur um Unit-Tests durchzuführen, die Sie eigentlich niemals durchführen sollten . Es testet nicht die E / A, sondern den Logger. Der einfachste Weg, dies zu tun, besteht darin, die Datei zu lesen, wenn Sie immer noch sauberen Code wünschen.
Am

5

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.


2

sollte nicht über die Art des Verbrauchers von Dienstleistungen wissen.

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.


1

Ich kann sehen, warum es ein attraktiver Test wäre. Aber was würde es eigentlich testen? Sagen wir, wir haben die Funktion

showMessage(string m)
{
    new DialogueBox(m).Show(); //will fail if not called from UI thread
}

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.

showMessage(string m)
{
    try {
        new DialogueBox(m).Show(); //will fail if not called from UI thread
    }
    catch {
        Logger.Log(m); //alternative display method
    }
}

Jetzt muss ich etwas testen, aber es ist unklar, ob diese Art von Alternative im wirklichen Leben nützlich wäre


1

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.


1

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.


1

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 .


1

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,

Ich war in die entgegengesetzte Richtung verzerrt wie ein Typ der alten Schule, der Multithreading misstraute, ein echter Neuling bei der Akzeptanz war, und dachte, Korrektheit übertreffe die Leistung bis zu dem Punkt, dass ich all diese Kerne, die wir jetzt haben, selten mehr nutzen kann, bis ich Dinge entdeckte wie reine Funktionen und unveränderliche Designs und beständige Datenstrukturen, die es mir endlich ermöglichten, diese Hardware vollständig zu nutzen, ohne mir Sorgen um die Rennbedingungen und Deadlocks in der Welt zu machen. Ich muss zugeben, dass ich Multithreading bis etwa 2010 mit Leidenschaft hasste, abgesehen von ein paar parallelen Schleifen hier und da in Bereichen, die für die Thread-Sicherheit trivial sind, und viel mehr sequentiellen Code für das Design von bevorzugte Produkte, die mir durch Multithreading in früheren Teams Sorgen gemacht haben.

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.

  • Ja, mir ist klar, dass es immer Möglichkeiten gibt, Designbeschränkungen zu umgehen, die Sie normalerweise durchsetzen, wenn der Compiler Sie nicht schützen kann. Ich spreche nur praktisch; Wenn Sie das Objekt "GUI-Thread" oder was auch immer abstrahieren können, ist dies möglicherweise der einzige, der den GUI-Objekten / -Funktionen einen Parameter übergibt, und Sie können möglicherweise den Zugriff dieses Objekts auf andere Threads einschränken. Natürlich könnte es in der Lage sein, solche Reifen zu umgehen und tief zu graben und sich um solche Reifen herumzuarbeiten, um diese GUI-Funktionen / Objekte an andere Threads zu übergeben, um sie aufzurufen, aber zumindest gibt es dort eine Barriere, und Sie können jeden, der das tut, als "Idioten" bezeichnen ", und zumindest nicht im Unrecht sein, wenn es darum geht, Lücken für das, was das Design offensichtlich einzuschränken versuchte, eindeutig zu umgehen und zu suchen. :-D Das '
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.