Unit-Tests für eine wissenschaftliche Computer-Bibliothek

15

Ich habe schon ein bisschen Erfahrung mit Unit-Tests in dem klassischen Software-Engineering-Projekt, das ich (nicht abschätzend) nenne, gesammelt: eine MVC mit einer Benutzer-GUI, einer Datenbank, einer Geschäftslogik in der mittleren Ebene usw. Ich schreibe eine wissenschaftliche Computer-Bibliothek in C # (Ja, ich weiß, dass C # zu langsam ist, benutze C, erfinde das Rad nicht neu und all das, aber wir haben eine Menge Leute, die wissenschaftliche Berechnungen in meiner Fakultät in C # durchführen.) und wir brauchen es irgendwie). Es ist ein kleines Projekt im Sinne der Softwareentwicklungsbranche, da ich es größtenteils selbst und von Zeit zu Zeit mit Hilfe einiger Kollegen schreibe. Außerdem werde ich dafür nicht bezahlt, und das Wichtigste ist, dass es sich um ein akademisches Projekt handelt. Ich meine, ich erwarte, dass es eines Tages professionelle Qualität hat, weil ich vorhabe, Open Source zu werden.

Wie auch immer, das Projekt wird immer umfangreicher (etwa 18.000 Codezeilen, die ich für Ein-Mann-Projekte halte) und gerät aus meinen Händen. Ich verwende Git für die Quellcodeverwaltung, und ich denke, ich habe alles in Ordnung, aber ich teste wie in der alten Schule und schreibe vollständige Konsolenanwendungen, die einen großen Teil des Systems testen, hauptsächlich, weil ich keine Ahnung habe, wie in diesem Szenario Unit-Tests durchführen, obwohl ich der Meinung bin, dass dies das ist, was ich tun sollte. Das Problem ist, dass die Bibliothek hauptsächlich Algorithmen enthält, zum Beispiel Graph-Algorithmen, Klassifikatoren, numerische Löser, Zufallsverteilungen usw. Ich weiß nur nicht, wie ich winzige Testfälle für jeden dieser Algorithmen spezifizieren soll, und da es sich bei vielen um solche handelt stochastisch Ich weiß nicht, wie ich die Richtigkeit überprüfen soll. Für die Klassifizierung sind zum Beispiel einige Metriken wie Präzision und Rückruf, Diese Metriken sind jedoch besser für den Vergleich zweier Algorithmen als für die Beurteilung eines einzelnen Algorithmus. Wie kann ich hier die Korrektheit definieren?

Schließlich gibt es auch das Leistungsproblem. Ich kenne eine ganze Reihe von Tests, aber die Leistung ist eines der wichtigsten Merkmale eines wissenschaftlichen Tools und nicht die Zufriedenheit der Benutzer oder andere Metriken der Softwareentwicklung.

Eines meiner größten Probleme ist mit Datenstrukturen. Der einzige Test, den ich für einen kd-Baum finden kann, ist ein Stresstest: viele zufällige Vektoren einfügen und dann viele zufällige Abfragen durchführen und mit einer naiven linearen Suche vergleichen. Das gleiche gilt für die Leistung. Und mit numerischen Optimierern habe ich Benchmark-Funktionen, die ich testen kann, aber auch dies ist ein Stresstest. Ich glaube nicht, dass diese Tests als Komponententests klassifiziert werden können, und vor allem, dass sie kontinuierlich durchgeführt werden, da die meisten von ihnen ziemlich schwer sind. Ich denke aber auch, dass diese Tests durchgeführt werden müssen. Ich kann nicht einfach zwei Elemente einfügen, die Wurzel platzieren und ja, es funktioniert für den Fall 0-1-n.

Was ist der (Einheiten-) Testansatz für diese Art von Software? Und wie organisiere ich die Unit-Tests und die Heavy-Tests im Code-Build-Commit-Integrate-Zyklus?

Alejandro Piad
quelle

Antworten:

19

Ich würde sagen, dass wissenschaftliches Rechnen eigentlich recht gut für Unit-Tests geeignet ist. Sie haben definierte Ein- und Ausgänge, klar definierte Vor- und Nachbedingungen, die sich nach Lust und Laune eines Designers wahrscheinlich nicht alle zwei Wochen ändern werden, und keine schwer zu testenden UI-Anforderungen.

Sie nennen einige Elemente, die Probleme verursachen könnten. Hier ist was zu tun:

  • Randomisierte Algorithmen: Es gibt zwei Möglichkeiten. Wenn Sie die Randomisierung selbst testen möchten, planen Sie einfach eine große Anzahl von Wiederholungen ein und stellen Sie sicher, dass der erwartete Anteil der Fälle das gewünschte Kriterium erfüllt und dass die Fehlergrenzen groß genug sind, um Fehlversuche zu vermeiden. (Eine Testsuite, die unzuverlässig Phantom-Bugs signalisiert, ist viel schlimmer als eine, die nicht alle denkbaren Fehler erkennt.) Alternativ können Sie eine konfigurierbare Zufallsquelle verwenden und die Systemuhr (oder was auch immer Sie verwenden) durch eine deterministische Quelle über Abhängigkeit ersetzen Injektion, so dass Ihre Tests vollständig vorhersehbar werden.
  • Algorithmen, die nur in Bezug auf Präzision / Rückruf definiert sind: Nichts hindert Sie daran, eine ganze Reihe von Eingabefällen einzugeben und Präzision und Rückruf zu messen, indem Sie alle addieren. Es geht nur darum, solche Testfälle halbautomatisch und effizient zu generieren, damit die Bereitstellung der Testdaten nicht zum Engpass für Ihre Produktivität wird. Alternativ kann es auch funktionieren , einige sorgfältig ausgewählte Eingabe / Ausgabe-Paare anzugeben und zu behaupten, dass der Algorithmus genau die gewünschte Eingabe berechnet , wenn die Routine vorhersagbar genug ist.
  • Nicht-funktionale Anforderungen: Wenn die Beschreibung wirklich explizit Raum / Zeit - Anforderungen gibt, dann sind Sie im Grunde haben ganze Suiten von Eingangs- / Ausgangspaaren und überprüfen, ob die Ressourcennutzung Konform laufen ungefähr auf die gewünschten Nutzungsmuster. Der Trick dabei ist, zuerst Ihre eigene Testklasse zu kalibrieren, damit Sie nicht zehn Probleme mit unterschiedlichen Größen messen, die zu schnell zu messen sind oder die so lange dauern, dass die Ausführung der Testsuite unpraktisch wird. Sie können sogar einen kleinen Use-Case-Generator schreiben, der Testfälle unterschiedlicher Größe erstellt, je nachdem, wie schnell die PU ist, auf der sie ausgeführt wird.
  • Tests mit schneller und langsamer Ausführung: Ob Unit- oder Integrationstests, häufig werden sehr schnelle und einige sehr langsame Tests durchgeführt. Da es sehr wertvoll ist, Ihre Tests regelmäßig durchzuführen, gehe ich normalerweise den pragmatischen Weg und teile alles, was ich habe, in eine schnelle und eine langsame Suite auf, damit die schnelle so oft wie möglich ausgeführt werden kann (sicherlich vor jedem Commit), und egal ob zwei tests 'semantisch' gehören zusammen oder nicht.
Kilian Foth
quelle
+1. Vielen Dank, es gibt viel Einsicht in Ihre Antwort. Nur ein paar Fragen: Wie wäre es mit Optimierungsalgorithmen wie der Meta-Heuristik? Ich habe eine Reihe von Benchmark-Funktionen, aber alles, was ich damit machen kann, ist, zwei verschiedene Algorithmen zu vergleichen. Muss ich auch einen Benchmark-Algorithmus finden? Was bedeutet es, dass ein genetischer Algorithmus korrekt ist? Und wie teste ich jede der "parametrisierbaren" Strategien, wie die Art der Rekombination und Mutation usw.?
Alejandro Piad
1
Für die Meta-Heuristik würde ich ein paar charakteristische E / A-Paare auswählen, dh die "berühmten Erfolge" der Routine, und überprüfen, ob die Methode (oder die bessere der beiden) tatsächlich diese Lösung findet. "Cherry-Picking" -Probleme, die zufällig gut funktionieren, sind natürlich ein No-No in der Optimierungsforschung, aber für Softwaretests ist das kein Problem - Sie behaupten nicht die Qualität des Algorithmus, sondern nur die korrekte Implementierung. Das ist die einzige "Richtigkeit", die Sie beweisen können. Was mehrfach parametrierbare Routinen angeht: Ja, ich fürchte, das erfordert eine kombinatorische Menge an Tests ...
Kilian Foth
So ist es wie das Entwerfen eines trivialen Benchmarks, den alle korrekten Implementierungen genau lösen sollten? Gibt es eine Möglichkeit, die Qualität des Algorithmus zu beweisen? Ich weiß, dass ich die meiste Zeit keinen Qualitätsstandard definieren kann, aber ich könnte mir wenigstens wünschen, dass keine Änderung die erreichte Qualität mindert?
Alejandro Piad