Ein E-Mail-Archivierer kann auf genau eine Art scheitern, die wirklich zählt: etwas mit Ihrer Post tun, das Sie nicht verlangt haben. Eine Nachricht als gelesen markieren, sie im falschen Ordner ablegen, das Falsche löschen. Deshalb erscheint jedes Feature in MailVault auf die gleiche Weise: erst nachdem eine Maschine die echte Anwendung geöffnet, das Feature so ausgeführt hat, wie es ein Mensch täte, und das Ergebnis vom tatsächlichen Bildschirm abgelesen hat.

Das hier ist eine Fortschrittsnotiz dazu, wie das funktioniert, denn der Aufbau erwies sich als interessanter als die Features selbst.

Die Tests benutzen die App. Sie simulieren sie nicht.

MailVault ist eine Tauri-Anwendung: ein Rust-Backend, das die IMAP-Arbeit erledigt, eine React-Oberfläche in einem nativen Webview. Tauri hat hier eine unterschätzte Superkraft: Wird die App mit dem Feature webdriver gebaut, wird die echte Anwendung genauso fernsteuerbar wie ein Browser. Unsere Suite läuft mit WebdriverIO über die wry -Fähigkeit, durch tauri-driver, gegen die tatsächlich kompilierte Binärdatei.

Der Unterschied zählt. Ein Komponententest rendert eine Liste in einem imaginären Browser und prüft virtuelle Ausgaben. Unsere End-to-End-Tests öffnen dasselbe Fenster, das auch ein Nutzer bekommt, warten, bis dieselben Zeilen gezeichnet sind, klicken dieselben Schaltflächen und prüfen anschließend, was die Oberfläche und die Post auf dem Server tatsächlich sagen. Ein Ausschnitt aus einem Lauf dieser Woche, der die Auswahl-Aktionsleiste testet:

Selection Action Bar effects
   ✓ marks a selected row as read and repaints it
   ✓ marks a row back as unread
   ✓ marks several selected rows as read in one action
   ✓ archives a selected row and flips its source icon to local
   ✓ unarchives it again
   ✓ moves a selected row to another folder and drops it from the list
   ✓ deletes a selected row from the server and drops it from the list

7 passing (833ms)

Jede Zeile ist ein vollständiger Umlauf: ein Klick in der Oberfläche, ein Aufruf ins Rust-Backend, eine IMAP-Konversation mit einem Server und ein Neuzeichnen, das der Test mit eigenen Augen prüft. Wenn die Suite sagt, dass Archivieren das Quellsymbol auf lokal umstellt, bedeutet das: Eine Maschine hat zugesehen, wie sich dieses Symbol geändert hat.

Die Mailserver werden mit dem Lauf geboren und sterben mit ihm

Nichts davon berührt ein echtes Konto. Jeder Lauf startet frische Mock-IMAP-Server auf lokalen Ports und bestückt sie mit zwei bekannten Konten, damit die Suite Kontowechsel und den vereinheitlichten Posteingang prüfen kann, nicht nur einen einzigen Glücksfall-Pfad. Ein Konto enthält absichtlich siebenhundert Nachrichten, mehr als beide Ladefenster der App fassen, sodass es immer ein Postfach gibt, das wirklich nur teilweise geladen ist, der Zustand, in dem Paginierungs-Bugs leben.

Die App selbst wird auf ein Wegwerf-Home-Verzeichnis gerichtet, das eigens für den Lauf angelegt wird. Ihr Archiv, ihre Einstellungen, ihre lokale Datenbank landen allesamt in einem Ordner, den es eine Minute zuvor noch nicht gab und der gelöscht wird, sobald der Lauf endet. Keine Zugangsdaten, kein Netzwerk, keine echte Post irgendwo in der Nähe des Prozesses.

Determinismus ist der andere Grund. Echte Mailserver rationieren Sie. Gmail begrenzt IMAP-Downloads auf 2.500 MB pro Tag, worüber wir bereits in der vorigen Feldnotizgeschrieben haben, und jeder Anbieter ist auf eine Art launisch, die sich eine Testsuite nicht leisten kann. Ein Mock-Server tut jedes Mal exakt das, was das Szenario vorgibt, in Millisekunden. Die Aufgabe der Suite ist es, unsere Bugs zu fangen, nicht Googles Wetter.

Ein E-Mail-Client, getestet, als würde er etwas Wichtiges verwahren. MailVault archiviert Ihre Post in Standarddateien auf Ihrer eigenen Festplatte, und jede Version muss sich diese Suite zuerst verdienen.

Besuchen Sie MailVault

Die Maschine unter dem Schreibtisch ist nicht die Maschine, an der wir arbeiten

Hier ist der Teil, der den Alltag verändert hat. Ein vollständiger End-to-End-Lauf ist schwer: Er baut das React-Frontend, kompiliert das Rust-Backend und öffnet und schließt die Anwendung anschließend dutzende Male: eine frische Sitzung pro Spec-Datei, allein vierzehn Spec-Dateien in der verbundenen Suite. Läuft das auf der Entwicklungsmaschine, sieht man Fenster den Fokus stehlen, während die CPU einen vergisst. Niemand schreibt währenddessen Code.

Also finden die Läufe nicht dort statt. Ein kleiner Mac mini mit Apple Silicon steht im lokalen Netzwerk und tut sonst nichts. Er ist nur über SSH erreichbar, nur aus dem Netzwerk heraus, und hält einen Klon des Repositorys und der Toolchain vor. Ist ein Feature bereit, sich zu beweisen, wird der Lauf mit einem Befehl an den Mini geschickt, und die Entwicklungsmaschine baut weiter am nächsten Ding, während der Mini das aktuelle baut, startet und durchklickt.

Die beiden Tätigkeiten überlappen sich, statt sich abzuwechseln, was nach einer Kleinigkeit klingt, bis man zählt, wie oft am Tag eine Suite läuft, während Features entwickelt werden. Jede Sitzung auf dem Mini startet die App kalt, mit frischem Zustand und frischen Servern, was zugleich ein ehrlicher Kaltstart-Test ist, auf Hardware, die praktischerweise langsamer ist als die Maschine, auf der die App geschrieben wurde. Ist ein Feature auf dem Mini träge, ist das nicht das Problem des Mini. Das ist eine Vorschau auf den vier Jahre alten Laptop von irgendjemandem.

Die Fehlerausgabe kommt genauso zurück, wie sie lokal käme: welche Spec, welche Assertion, was die Oberfläche tatsächlich zeigte. Fixen, erneut abschicken. Die Schleife ist langweilig, und langweilig ist genau der Sinn der Sache.

Was das hier findet, was Unit-Tests höflich übersehen

Die besten Funde der Suite liegen nie in der Schicht, die ein Unit-Test abdecken würde. Sie leben in den Nähten: ein aus dem Posteingang geöffneter Thread, der den Antworttext aus dem falschen Postfach zeigt, eine verschobene Nachricht, die den Server verließ, aber nicht die Liste, eine Archivierungsaktion, die funktionierte, aber das Symbol der falschen Zeile neu zeichnete. Jeder dieser Fälle sind drei Subsysteme, die sich einigen, einzeln korrekt und gemeinsam falsch zu sein. Der einzige Test, der sie fängt, ist der, der tut, was der Nutzer tut.

Deshalb sind die Specs als Nutzersätze formuliert ("marks a selected row as read and repaints it") statt als Funktionsnamen. Schlägt einer fehl, liest sich der Bericht wie ein Bug, den ein Mensch melden würde.

Führen Sie die Suite selbst aus

MailVault ist Open Source, und die Suite läuft vollständig gegen die Mock-Server: keine Konten, keine Zugangsdaten, nichts zu konfigurieren. Wenn Sie Node 18 oder neuer und eine Rust-Toolchain installiert haben:

git clone https://github.com/GraphicMeat/mail-vault-app
cd mail-vault-app
npm install
cargo install tauri-webdriver-automation
npm run test:e2e

Der Befehl cargo install liefert tauri-wd, die WebDriver-Brücke, die die Suite startet, um die App zu steuern. Der letzte Befehl erledigt den Rest: Er baut das Frontend im Testmodus und kompiliert das Rust-Backend mit dem Feature webdriver und startet dann die Mock-IMAP-Server und steuert die App durch jede Spec. Der erste Lauf verbringt die meiste Zeit im Rust-Compiler; danach greifen npm run test:e2e:ui und npm run test:e2e:connected auf denselben Build zurück und führen ihre Suiten direkt aus.

Während sie läuft, öffnet sich das App-Fenster, klickt sich selbst und schließt sich, wieder und wieder. Das ist entweder unheimlich oder genau der Punkt, je nachdem, wie Sie zu einem Roboter stehen, der Ihre E-Mails liest.

Was wir noch nicht gelöst haben

Ehrlichkeit ist billiger als Marketing, also: Die Mock-Server sprechen das IMAP, das wir implementiert haben, nicht jeden Dialekt, den Gmail und Outlook improvisieren, und die OAuth-Anmeldung gegen die echten Anbieter wird noch von einer menschlichen Hand geprüft. Die Suiten für visuelle Regression und Backup laufen noch manuell statt bei jedem Dispatch. Und der Mini führt jeweils nur eine Suite aus, nacheinander: sechzehn Gigabyte Speicher sind ein Budget, und wir hätten lieber einen vertrauenswürdigen Lauf als zwei wacklige. Alle drei stehen auf der Liste, in dieser Reihenfolge.

Unterdessen kommen die Features weiterhin auf die langsame Art: geschrieben, verschickt, durchgeklickt von einem kleinen Computer, dem nicht langweilig wird, und erst ausgeliefert, wenn er nichts mehr findet. Ihre Post verdient zumindest so viel Zeremonie.

Ihre Post, lokal archiviert, von einer App, die härter getestet als vermarktet wird. Kostenlos und Open Source für macOS und Linux.

Besuchen Sie MailVault