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 '