Artikel

Discord-Bot-Funktionen ohne echten Server testen

Viele wichtige Botfehler lassen sich ohne echte Discord-Aktionen reproduzieren. An einer Ticketöffnung erklärt dieser Artikel Fachtests, kontrollierte Zeit, echte Testdatenbanken und gezielte Netzwerkfehler und verbindet sie mit Yurnas vorhandener Vitest-Struktur.

BlackZackBlackZack

1536 Wörter · 8 Min. Lesezeit

  • yurna
  • tests
  • vitest
  • qualität
Discord-Bot-Funktionen ohne echten Server testen

Schematische Illustration zur Artikelserie; keine privaten Betriebsdaten.

Ein Testserver ist hilfreich, aber kein Ersatz für reproduzierbare Prüfungen. Ein Rollenwechsel kann dort heute schnell und morgen langsam sein. Ein Fehler kurz nach einer erfolgreichen Datenänderung lässt sich durch manuelles Klicken kaum zuverlässig wiederholen. Gleichzeitig soll ein Test nicht aus Versehen echte Mitglieder benachrichtigen. Viele wichtige Eigenschaften eines Discord-Bots werden deshalb besser in einer kontrollierten Umgebung untersucht: gleiche Ausgangsdaten, festgelegte Zeit und genau platzierte Fehler. Der echte Server bleibt für die Prüfung der tatsächlichen Integration reserviert.

Yurnas Quellstand enthält Vitest-Konfigurationen und konkrete Tests, etwa für die Authentifizierung der Admin-API und das Entfernen sensibler Felder aus Datenstrukturen. Der Authentifizierungstest baut eine kleine Hono-Anwendung auf und prüft lesende sowie verändernde Zugriffe. Diese Beispiele zeigen, dass wichtige Grenzen ohne einen laufenden Discord-Server geprüft werden können. Sie belegen keine vollständige Testabdeckung aller Botfunktionen. Der folgende Entwurf entwickelt eine ergänzende Strategie am Beispiel einer Ticketöffnung.

Mit einer beobachtbaren Regel beginnen

Ein nützlicher Test beschreibt eine fachliche Erwartung. „Ein Mitglied erhält bei zweimaliger Öffnung derselben Kategorie nur ein nutzbares Ticket“ ist eine solche Regel. „Funktion A ruft Funktion B einmal auf“ kann dagegen lediglich die aktuelle Implementierung festschreiben. Wenn eine interne Umstrukturierung das gleiche Verhalten erhält, sollte ein guter Fachtest weiterhin bestehen. Deshalb werden zunächst Eingaben, sichtbares Ergebnis und relevante Nebenwirkungen beschrieben. Interne Aufrufdetails werden nur geprüft, wenn sie selbst Teil einer wichtigen Grenze sind.

Für das Beispiel gelten drei Annahmen: Ein Mitglied darf pro Kategorie ein offenes Ticket besitzen, eine Öffnung erzeugt einen Discord-Kanal und eine verlorene Antwort darf keine zweite Erstellung auslösen. Daraus entstehen mehrere Testfälle. Der normale Aufruf liefert einen Verweis auf das neue Ticket. Ein zweiter Aufruf liefert denselben fachlichen Vorgang. Ein externer Fehler hinterlässt einen erkennbaren Vorbereitungs- oder Fehlerzustand. Diese Erwartungen sind unabhängig davon, welche Discord-Bibliothek den Kanal später tatsächlich erstellt.

Eine kleine schriftliche Fallbeschreibung verhindert, dass der Test nur die gerade geschriebene Funktion nacherzählt. Sie nennt den Anfangszustand und das erwartete Ende, ohne die Schritte der Implementierung zu kopieren. Gerade bei Nebenläufigkeit ist diese Trennung wichtig. Wenn Test und Code denselben fehlerhaften Ablauf voraussetzen, können beide gemeinsam grün bleiben. Eine eigenständige fachliche Erwartung ist deshalb wertvoller als möglichst viele Assertions zu internen Details.

Externe Grenzen klein und austauschbar halten

Die Ticketlogik benötigt eine Möglichkeit, einen Kanal anzulegen. Sie braucht dafür nicht zwingend das vollständige Discord-Clientobjekt mit sämtlichen Caches und Ereignissen. Ein begrenzter Adapter kann nur die benötigte Fähigkeit ausdrücken. Im Test wird dieser Adapter durch ein kontrolliertes Gegenstück ersetzt. Die Fachlogik bleibt echt. Dadurch lässt sich prüfen, ob sie bei Erfolg und Fehlern die richtigen Zustände speichert, ohne dafür eine riesige Attrappe des gesamten Discord-Systems zu bauen.

// Beispielhafter Adapter für die Testbarkeit einer Ticketöffnung.
type TicketChannelPort = {
  create(input: { guildRef: string; operationRef: string }): Promise<{ channelRef: string }>;
  findByOperation(operationRef: string): Promise<{ channelRef: string } | null>;
};

Der zweite Zugriff erlaubt im Entwurf einen Abgleich nach unklarem Ergebnis. Ein Testadapter kann eine Erstellung erfolgreich ausführen, aber anschließend einen Verbindungsfehler melden. Beim erneuten Versuch findet die Fachlogik den bereits entstandenen Kanal. Damit wird eine reale schwierige Situation modelliert: Wirkung und Rückmeldung können auseinanderfallen. Ein einfacher Mock, der nur „Erfolg“ oder „Fehler ohne jede Wirkung“ zurückgibt, würde diesen Fall nie sichtbar machen.

Die Attrappe sollte ihren eigenen Zustand besitzen und nicht automatisch jede Anfrage akzeptieren. Sie kann angelegte Testkanäle speichern, doppelte Erstellungen zählen und nicht vorhandene Ziele ablehnen. Trotzdem bleibt sie klein. Ihr Zweck ist die kontrollierte Grenze, nicht eine vollständige Nachbildung der Discord-Plattform. Ein zusätzlicher Integrationstest prüft später, ob der echte Adapter dieselben vertraglich erwarteten Ergebnisse liefert. So werden Fachlogik und Transport getrennt, aber nicht voneinander losgelöst geprüft.

Datenbankregeln mit einer echten Testdatenbank untersuchen

Eindeutige Schlüssel, Fremdschlüssel und Transaktionen sollten nicht ausschließlich durch Objektlisten im Arbeitsspeicher simuliert werden. Eine einfache Attrappe bildet deren tatsächliches Verhalten oft nicht korrekt nach. Für die Ticketöffnung ist deshalb eine isolierte Testdatenbank sinnvoll, die das relevante Schema verwendet. Dort kann wirklich geprüft werden, ob zwei konkurrierende Versuche denselben fachlichen Eintrag erzeugen können oder eine festgelegte Bedingung die zweite Erstellung verhindert.

Die Testdatenbank muss eindeutig vom produktiven Bestand getrennt sein. Sie wird mit künstlichen Konten, Servern und Kategorien aufgebaut und nach einem klaren Verfahren bereinigt. Ein Test benötigt keine echten Ticketinhalte oder privaten Kennungen. Die Testkonfiguration sollte unerwartete Zugriffe auf andere Datenquellen verhindern. Ein erfolgreicher Testlauf darf nicht davon abhängen, dass zufällig eine lokale Produktivdatei unter einem erwarteten relativen Namen liegt. Isolation ist hier eine Voraussetzung für Aussagekraft und sichere Wiederholung.

Nicht jeder Test braucht die vollständige Anwendung mit allen Tabellen. Für reine Berechnungen genügt eine direkte Funktion. Für den Schutz einer gemeinsamen Datenregel wird dagegen der echte Speicherweg verwendet. Diese Auswahl hält den Testbestand schnell und verständlich. Viele große Ende-zu-Ende-Tests wären langsam und schwer zu diagnostizieren; nur kleine Mocks würden wichtige Integrationsfehler übersehen. Die passende Ebene richtet sich danach, welche Garantie der einzelne Test tatsächlich nachweisen soll.

Zeit bewusst steuerbar machen

Cooldowns, Ablaufzeiten und verzögerte Aufgaben lassen sich mit echten Wartepausen schlecht prüfen. Ein Test, der eine Minute schläft, ist langsam und trotzdem empfindlich gegenüber Ausführungsbedingungen. Besser ist eine kontrollierte Uhr. Die Fachlogik kann einen Zeitpunkt als Parameter erhalten oder eine austauschbare Zeitquelle verwenden. Für Timer bietet Vitest laut seiner Dokumentation entsprechende Testwerkzeuge. Der konkrete Einsatz muss zum verwendeten Zeitmechanismus passen.

Im Beispiel wird eine abgebrochene Ticketvorbereitung nach einer festgelegten Frist erneut bearbeitbar. Der Test setzt die Uhr zunächst unmittelbar vor diese Grenze. Eine Übernahme darf noch nicht erfolgen. Danach wird genau die Grenze erreicht und das erwartete Verhalten geprüft. So wird eine Unklarheit zwischen „größer als“ und „größer oder gleich“ sichtbar. Mit echtem Warten wäre dieser Fall schwer präzise zu treffen und könnte zufällig mal bestehen und mal scheitern.

Zeitkontrolle darf nicht vergessen, laufende asynchrone Arbeit abzuschließen. Ein vorgerückter Timer bedeutet nicht automatisch, dass alle dadurch gestarteten Promises bereits beendet sind. Der Test wartet deshalb gezielt auf das beobachtete Ergebnis und nicht auf eine beliebige zusätzliche Pause. Nach jedem Test werden veränderte Timer und globale Zeitquellen wiederhergestellt. Andernfalls beeinflusst ein Fall den nächsten und erzeugt scheinbar zufällige Fehler, die mit der Fachlogik wenig zu tun haben.

Gleichzeitigkeit durch feste Haltepunkte erzeugen

Zwei Funktionen einfach mit Promise.all zu starten, garantiert noch nicht die gewünschte Konkurrenz. Vielleicht läuft die erste bereits vollständig durch, bevor die zweite die kritische Stelle erreicht. Ein reproduzierbarer Test verwendet daher kontrollierte Haltepunkte. Beide Aufrufe lesen denselben Anfangszustand und werden erst danach gezielt fortgesetzt. Dadurch entsteht genau die Reihenfolge, deren Sicherheit untersucht werden soll. Der Test hängt nicht vom zufälligen Zeitplan des Rechners ab.

Für die Ticketöffnung werden zwei Aufrufe bis unmittelbar vor die verbindliche Reservierung geführt. Danach dürfen beide weiterlaufen. Erwartet werden ein eindeutiger fachlicher Vorgang und konsistente Antworten. Anschließend wird eine Variante geprüft, bei der die erste Erstellung bereits wirksam ist, ihre Abschlussantwort aber noch blockiert wird. Die zweite Anfrage muss den laufenden oder vorhandenen Vorgang erkennen. Diese Fälle prüfen verschiedene Grenzen und sollten nicht in einem einzigen schwer verständlichen Riesentest verschwinden.

Auch umgekehrte Reihenfolgen sind wertvoll. Ein älterer Lesezugriff kann nach einer neueren Änderung zurückkehren. Eine Benutzeroberfläche darf dadurch nicht unbemerkt wieder den alten Stand anzeigen. Für Botfunktionen betrifft das etwa Rollenlisten oder Konfigurationskopien. Ein kontrollierter Testadapter kann die Rückgaben in beliebiger Reihenfolge freigeben. So wird sichtbar, ob der Code Aktualität durch Revisionen oder andere Regeln schützt oder lediglich auf gewöhnlich schnelle Antworten vertraut.

Netzwerkfehler gezielt platzieren

Ein Fehler vor einer externen Wirkung unterscheidet sich von einem Fehler danach. Ebenso sind eine Ablehnung, eine Begrenzung und ein Timeout verschiedene Fälle. Der Testplan nennt deshalb konkrete Positionen: vor dem Senden, nach erfolgreicher externer Erstellung, vor lokaler Bestätigung oder während einer Wiederholung. Jede Position kann ein anderes erwartetes Verhalten besitzen. Ein allgemeiner Test „API wirft Fehler“ wäre für diese Unterschiede zu grob.

Die Vitest-Dokumentation zum Nachbilden von Netzwerkanfragen beschreibt Möglichkeiten, kontrollierte Antworten an einer HTTP-Grenze zu liefern. Für Yurna kann ein solcher Test den echten Transportadapter untersuchen, während der entfernte Dienst ersetzt wird. Unerwartete ausgehende Anfragen sollten im Test auffallen, statt still ins echte Netz zu gelangen. Dadurch wird die verwendete externe Oberfläche sichtbar und eine vergessene Abhängigkeit nicht zufällig durch einen erreichbaren Dienst kaschiert.

Fehlerantworten werden aus dokumentierten Strukturen und bewusst künstlichen Werten aufgebaut. Reale Zugangstoken oder private Serverantworten sind dafür unnötig. Wenn eine Bibliothek eine bestimmte Fehlerform erwartet, wird diese Form in einem kleinen Vertragstest geprüft. Die Fachtests verwenden anschließend strukturierte eigene Fehlergründe. So bleibt die Anwendung weniger abhängig von zufälligen Details einer umfangreichen externen Fehlermeldung und die Tests erklären klar, welche Ebene gerade untersucht wird.

Antworten und Datenschutz mitprüfen

Ein korrekt gespeichertes Ticket ist nur ein Teil des Ergebnisses. Die Antwort muss denselben Zustand erklären. Ein Test prüft daher, ob die Meldung bei bestehendem Ticket einen Verweis liefert und bei noch laufender Vorbereitung keinen fertigen Kanal behauptet. Vollständige Textsnapshots können dabei hilfreich sein, sind aber nicht immer die beste Hauptprüfung. Kleine semantische Erwartungen an Zustand, Ziel und Sichtbarkeit bleiben bei harmlosen sprachlichen Verbesserungen stabiler.

Yurnas vorhandene Tests zur Entfernung sensibler Felder zeigen einen weiteren wichtigen Bereich. Verschachtelte Daten und Arrays werden dabei berücksichtigt. Für konkrete Browser- oder API-Antworten sollte zusätzlich geprüft werden, dass nur erlaubte Felder erscheinen. Eine allgemeine Bereinigungsfunktion ersetzt keine bewusst begrenzte Antwortstruktur. Der Test verwendet eindeutig künstliche Geheimwerte und bestätigt, dass sie weder in der Nutzerausgabe noch in vorgesehenen Fehlerdaten auftauchen. So wird ein wichtiges Verhalten überprüft, ohne echte Geheimnisse zu berühren.

Am Ende bleibt ein kleiner echter Integrationslauf sinnvoll, etwa auf einer ausdrücklich dafür vorgesehenen Community. Er prüft Bibliotheksanbindung, tatsächliche Rechte und Darstellung. Die schwierigen Fehlerfolgen wurden aber bereits vorher kontrolliert untersucht. Ein Testbericht sollte diese Grenze ehrlich nennen: Fachregeln und künstliche Fehlerpfade geprüft, reale Plattformdarstellung separat betrachtet. Yurna gewinnt dadurch einen Testbestand, der Änderungen schnell erklärt und reproduzierbar absichert, statt nur gelegentlich auf einem echten Server einen grünen Eindruck zu erzeugen.