Ich versuche einen MailboxProcessor in F # zu testen. Ich möchte testen, dass die Funktion, die ich gebe, tatsächlich ausgeführt wird, wenn Sie eine Nachricht senden.
Der ursprüngliche Code verwendet Xunit, aber ich habe eine fsx davon erstellt, die ich mit fsharpi ausführen kann.
Bisher mache ich das:
%Vor%Dieser Code funktioniert, sieht aber für mich nicht gut aus.
1) ist die Verwendung von TaskCompletionSource normal in f # oder gibt es einige dedizierte Sachen, die es mir erlauben, auf einen Abschluss zu warten?
2) Ich verwende ein zweites Argument in der Funktion waitingFor, um es zu umgehen, ich weiß, ich könnte einen Typ MyType & lt; 'a & gt; () verwenden, um es zu tun, gibt es eine andere Option? Ich würde lieber keinen neuen MyType verwenden, den ich als umständlich empfinde.
3) Gibt es eine andere Möglichkeit, meinen Agenten zu testen, als dies zu tun? der einzige Beitrag, den ich bisher über das Thema gefunden habe, ist dieser Blogpost von 2009 Ссылка
Das ist hart, ich versuche das auch schon seit einiger Zeit anzugehen. Das ist, was ich bis jetzt gefunden habe, es ist zu lang für einen Kommentar, aber ich würde zögern, es eine volle Antwort entweder zu nennen ...
Vom einfachsten bis zum komplexesten Fall hängt es wirklich davon ab, wie gründlich Sie testen wollen und wie komplex die Agentenlogik ist.
Was Sie haben, ist für kleine Agenten in Ordnung, deren einzige Aufgabe darin besteht, den Zugriff auf eine asynchrone Ressource zu serialisieren, mit wenig oder keiner internen Statusbehandlung. Wenn Sie das f
wie in Ihrem Beispiel angeben, können Sie ziemlich sicher sein, dass es in einem relativ kurzen Timeout von einigen hundert Millisekunden aufgerufen wird. Sicher, es scheint klobig und es ist doppelt so groß wie der Code für alle Wrapper und Helfer, aber diese können wiederverwendet werden, Sie testen mehr Agenten und / oder mehr Szenarien, so dass sich die Kosten relativ schnell amortisieren.
Das Problem, das ich sehe, ist, dass es nicht sehr nützlich ist, wenn Sie auch mehr als das bestätigen wollen, als die Funktion aufgerufen wurde - zum Beispiel der interne Agentenstatus nach dem Aufruf.
Eine Anmerkung, die auch auf andere Teile der Antwort anwendbar ist: Ich starte normalerweise Agenten mit einem Abbruch-Token, das erleichtert sowohl den Produktions- als auch den Testzyklus.
Hinzufügen AsyncReplyChannel<'reply>
zum Nachrichtentyp und posten Sie Nachrichten mit PostAndAsyncReply
anstelle von Post
Methode auf dem Agenten. Dadurch wird Ihr Agent in etwa so geändert:
Dies mag wie eine künstliche Anforderung für die "Schnittstelle" des Agenten erscheinen, aber es ist praktisch, einen Methodenaufruf zu simulieren und es ist einfach zu testen - warten Sie auf die PostAndAsyncReply
(mit einer Zeitüberschreitung) und Sie können die meisten davon loswerden Testhilfscode.
Da Sie einen separaten Aufruf für die angegebene Funktion und replyChannel.Reply
haben, kann die Antwort auch den Agentenstatus und nicht nur das Funktionsergebnis widerspiegeln.
Dies ist, worüber ich im Detail sprechen werde, da ich denke, dass es am allgemeinsten ist.
Falls der Agent komplexeres Verhalten einkapselt, fand ich es praktisch, das Testen einzelner Nachrichten zu überspringen und modellbasierte Tests zu verwenden, um ganze Folgen von Operationen gegen ein Modell des erwarteten externen Verhaltens zu verifizieren. Ich verwende dafür die FsCheck.Experimental API:
In Ihrem Fall wäre das machbar, würde aber keinen Sinn machen, da es keinen internen Modellierungszustand gibt. Um Ihnen ein Beispiel zu geben, wie es in meinem speziellen Fall aussieht, betrachten Sie einen Agenten, der Client-WebSocket-Verbindungen verwaltet, um Nachrichten an die Clients zu senden. Ich kann den ganzen Code nicht teilen, aber die Schnittstelle sieht so aus
%Vor% Intern verwaltet der Agent ein Map<ClientInfo, MessageConsumer>
.
Jetzt zum Testen kann ich das externe Verhalten in Form von informellen Spezifikationen modellieren, wie: "Senden an einen abonnierten Client kann erfolgreich sein oder fehlschlagen, abhängig vom Ergebnis des Aufrufs der MessageConsumer-Funktion" und "Senden an einen nicht abonnierten Client sollte nicht" t rufen Sie einen MessageConsumer auf ". So kann ich zum Beispiel Typen wie diese definieren, um den Agenten zu modellieren.
%Vor%Und dann verwenden Sie FsCheck.Experimental, um die Vorgänge des Hinzufügens und Entfernens von Clients mit unterschiedlich erfolgreichen Konsumenten zu definieren und zu versuchen, Daten an sie zu senden. FsCheck generiert dann zufällige Sequenzen von Operationen und überprüft die Implementierung des Agenten gegen das Modell zwischen den einzelnen Schritten.
Dies erfordert zusätzlichen "Nur-Test" -Code und hat am Anfang einen erheblichen mentalen Overhead, aber Sie können relativ komplexe Stateful-Logik testen. Was mir besonders gefällt ist, dass es mir hilft, den gesamten Vertrag zu testen, nicht nur einzelne Funktionen / Methoden / Nachrichten, genauso wie property-based / generative Testing hilft, mehr als nur einen einzigen Wert zu testen.
Ich bin noch nicht so weit gegangen, aber was ich als Alternative gehört habe, ist zum Beispiel Akka.NET Modellunterstützung und verwenden Sie seine Testfunktionen, mit denen Sie Agenten in speziellen Testkontexten ausführen, erwartete Nachrichten überprüfen und so weiter. Wie ich bereits sagte, habe ich keine Erfahrung aus erster Hand, sondern scheint eine praktikable Option für komplexere Stateful-Logik zu sein (selbst auf einer einzelnen Maschine, nicht in einem verteilten Multiknoten-Aktorsystem).
Tags und Links unit-testing f# mailboxprocessor