Ich habe über dieses Problem gelesen: Der Programmierfehler kostet Citigroup $ 7 Millionen nach legitimen Transaktionen, die 15 Jahre lang für Testdaten gehalten wurden .
Als das System Mitte der neunziger Jahre eingeführt wurde, filterte der Programmcode alle Transaktionen heraus, denen dreistellige Verzweigungscodes von 089 bis 100 zugewiesen wurden, und verwendete diese Präfixe zu Testzwecken.
Aber im Jahr 1998 begann das Unternehmen, alphanumerische Filialcodes zu verwenden, als es sein Geschäft ausbaute. Darunter befanden sich die Codes 10B, 10C usw., die vom System als innerhalb des ausgeschlossenen Bereichs liegend behandelt wurden, und so wurden ihre Transaktionen aus allen an die SEC gesendeten Berichten entfernt.
(Ich denke, dies zeigt, dass die Verwendung eines nicht expliziten Datenindikators ... suboptimal ist. Es wäre viel besser gewesen, eine semantisch explizite Branch.IsLive
Eigenschaft aufzufüllen und zu verwenden .)
Abgesehen davon war meine erste Reaktion "Unit-Tests hätten hier geholfen" ... aber würden sie?
Ich habe kürzlich gelesen, warum die meisten Komponententests mit Interesse verschwendet werden und meine Frage lautet: Wie würden die Komponententests aussehen, die bei der Einführung von alphanumerischen Verzweigungscodes fehlgeschlagen wären?
quelle
Antworten:
Fragen Sie sich wirklich: "Hätten Unit-Tests hier geholfen?", Oder fragen Sie sich: "Könnten irgendwelche Tests hier möglicherweise geholfen haben?".
Die naheliegendste Form des Testens, die geholfen hätte, ist die Voraussetzung im Code selbst, dass eine Verzweigungskennung nur aus Ziffern besteht (vorausgesetzt, dies ist die Annahme, auf die sich der Codierer beim Schreiben des Codes stützt).
Dies könnte dann in einer Art Integrationstest fehlgeschlagen sein, und sobald die neuen alphanumerischen Verzweigungs-IDs eingeführt werden, explodiert die Behauptung. Aber das ist kein Komponententest.
Alternativ könnte es einen Integrationstest des Verfahrens geben, der den SEC-Bericht generiert. Dieser Test stellt sicher, dass jede reale Zweigstellenkennung ihre Transaktionen meldet (und erfordert daher eine reale Eingabe, eine Liste aller verwendeten Zweigstellenkennungen). Das ist also auch kein Unit-Test.
Ich kann keine Definition oder Dokumentation der beteiligten Schnittstellen sehen, aber es kann sein, dass Unit-Tests den Fehler möglicherweise nicht erkannt haben, weil das Gerät nicht fehlerhaft war . Wenn das Gerät aus diesem Zweig Bezeichner ist nur zulässig , von Ziffern zu übernehmen, und die Entwickler machten nie eine Entscheidung , was der Code für den Fall tun soll es nicht, dann sie sollten nichtSchreiben Sie einen Komponententest, um ein bestimmtes Verhalten bei nicht-stelligen Kennungen zu erzwingen, da der Test eine hypothetisch gültige Implementierung der Einheit ablehnen würde, die alphanumerische Verzweigungskennungen korrekt behandelt, und Sie normalerweise keinen Komponententest schreiben möchten, der die Gültigkeit verhindert zukünftige Implementierungen und Erweiterungen. Oder vielleicht hat ein Dokument, das vor 40 Jahren geschrieben wurde, implizit (über einen lexikografischen Bereich in EBCDIC-Rohdaten anstelle einer menschlicheren Kollatierungsregel) festgelegt, dass 10B eine Testkennung ist, da sie tatsächlich zwischen 089 und 100 liegt. Aber dann Vor 15 Jahren hat sich jemand entschieden, es als echte Kennung zu verwenden, damit der "Fehler" nicht in der Einheit liegt, die die ursprüngliche Definition korrekt implementiert: es liegt in dem Prozess, der nicht bemerkt hat, dass 10B als Testidentifikator definiert ist und daher keiner Verzweigung zugewiesen werden sollte. Dasselbe würde in ASCII passieren, wenn Sie 089 - 100 als Testbereich definieren und dann einen Bezeichner 10 $ oder 1.0 eingeben. Es kommt einfach vor, dass in EBCDIC die Ziffern hinter den Buchstaben stehen.
Ein Unit-Test (oder wohl ein Funktionstest), der vorstellbar istVielleicht haben Sie den Tag gerettet, ist ein Test des Geräts, das neue Zweig-IDs generiert oder validiert. Dieser Test würde behaupten, dass die Bezeichner nur Ziffern enthalten dürfen und geschrieben werden, damit Benutzer der Zweigbezeichner dasselbe annehmen können. Oder vielleicht gibt es irgendwo eine Einheit, die echte Verzweigungskennungen importiert, aber die Testkennungen nie sieht, und die von der Einheit getestet werden kann, um sicherzustellen, dass alle Testkennungen abgelehnt werden (wenn die Kennungen nur drei Zeichen umfassen, können wir sie alle auflisten und das Verhalten von vergleichen den Validator des Testfilters, um sicherzustellen, dass sie übereinstimmen, was die üblichen Einwände gegen Stichproben betrifft). Wenn dann jemand die Regeln geändert hat, wäre der Komponententest fehlgeschlagen, da er dem neu erforderlichen Verhalten widerspricht.
Da der Test aus gutem Grund vorhanden war, bietet sich für jemanden die Möglichkeit, ihn aufgrund geänderter Geschäftsanforderungen zu entfernen. "Suchen Sie jeden Punkt im Code, der sich auf das gewünschte Verhalten stützt Veränderung". Dies ist natürlich schwierig und daher unzuverlässig, so dass die Rettung des Tages keineswegs garantiert ist. Wenn Sie jedoch Ihre Annahmen in Tests der Einheiten erfassen, für die Sie Eigenschaften annehmen, haben Sie sich eine Chance gegeben, und der Aufwand wird nicht vollständig verschwendet.
Ich stimme natürlich zu, dass es nichts zu testen gibt, wenn die Einheit nicht an erster Stelle mit einem "witzig geformten" Eingang definiert worden wäre. Fummelige Namespace-Abteilungen können schwierig zu testen sein, da die Schwierigkeit nicht darin besteht, Ihre lustige Definition zu implementieren, sondern sicherzustellen, dass jeder Ihre lustige Definition versteht und respektiert. Das ist keine lokale Eigenschaft einer Codeeinheit. Darüber hinaus entspricht das Ändern eines Datentyps von "einer Zeichenfolge" in "eine Zeichenfolge aus alphanumerischen Zeichen" dem Verarbeiten von Unicode durch ein ASCII-basiertes Programm: Es ist nicht einfach, wenn Ihr Code stark an die ursprüngliche Definition gekoppelt ist und wann Der Datentyp ist grundlegend für das, was das Programm tut, und ist dann oft stark gekoppelt.
Wenn Ihre Komponententests manchmal fehlschlagen (z. B. beim Refactoring) und dabei nützliche Informationen liefern (z. B. ist Ihre Änderung falsch), wurde der Aufwand nicht verschwendet. Was sie nicht tun, ist zu testen, ob Ihr System funktioniert. Wenn Sie also Unit-Tests schreiben, anstatt Funktions- und Integrationstests durchzuführen, nutzen Sie Ihre Zeit möglicherweise nicht optimal.
quelle
Unit-Tests hätten feststellen können, dass die Filialcodes 10B und 10C fälschlicherweise als "Testfilialen" klassifiziert wurden, aber ich halte es für unwahrscheinlich, dass die Tests für diese Filialklassifizierung umfangreich genug gewesen wären, um diesen Fehler abzufangen.
Andererseits hätten Stichproben der generierten Berichte ergeben können, dass verzweigte 10B und 10C in den Berichten häufig fehlten, viel eher als in den 15 Jahren, in denen der Fehler nun vorhanden sein durfte.
Schließlich ist dies ein gutes Beispiel dafür, warum es eine schlechte Idee ist, Testdaten mit den realen Produktionsdaten in einer Datenbank zu mischen. Wenn sie eine separate Datenbank verwendet hätten, die die Testdaten enthält, wäre es nicht notwendig gewesen, dies aus den offiziellen Berichten herauszufiltern, und es wäre unmöglich gewesen, zu viel herauszufiltern.
quelle
Die Software musste bestimmte Geschäftsregeln erfüllen. Wenn es Komponententests gegeben hätte, hätten die Komponententests überprüft, ob die Software die Geschäftsregeln korrekt handhabt.
Die Geschäftsregeln haben sich geändert.
Anscheinend bemerkte niemand, dass sich die Geschäftsregeln geändert hatten, und niemand änderte die Software, um die neuen Geschäftsregeln anzuwenden. Wenn es Unit-Tests gegeben hätte, müssten diese Unit-Tests geändert werden, aber niemand hätte das getan, weil niemand bemerkt hätte, dass sich die Geschäftsregeln geändert hatten.
Nein, Unit-Tests hätten das nicht erfasst.
Die Ausnahme wäre, wenn die Komponententests und die Software von unabhängigen Teams erstellt wurden und das Team, das die Komponententests durchführt, die Tests geändert hat, um die neuen Geschäftsregeln anzuwenden. Dann wären die Unit-Tests fehlgeschlagen, was hoffentlich zu einer Änderung der Software geführt hätte.
Im selben Fall würden natürlich auch die Unit-Tests fehlschlagen, wenn nur die Software und nicht die Unit-Tests geändert würden. Wenn ein Komponententest fehlschlägt, bedeutet dies nicht, dass die Software falsch ist, sondern, dass entweder die Software oder der Komponententest (manchmal beide) falsch sind.
quelle
Nein. Dies ist eines der großen Probleme bei Unit-Tests: Sie wiegen Sie in ein falsches Sicherheitsgefühl.
Wenn alle Ihre Tests bestanden sind, bedeutet dies nicht, dass Ihr System ordnungsgemäß funktioniert. Es bedeutet, dass alle Ihre Tests bestanden werden . Das bedeutet, dass die Teile Ihres Designs, für die Sie sich bewusst Gedanken gemacht und Tests geschrieben haben, so funktionieren, wie Sie es sich bewusst vorgestellt haben, was ohnehin keine große Sache ist Es ist also sehr wahrscheinlich, dass Sie es trotzdem richtig verstanden haben! Aber es tut nichts, um Fälle aufzufangen, an die Sie noch nie gedacht haben, wie diese, weil Sie nie daran gedacht haben, einen Test für sie zu schreiben. (Und wenn Sie dies getan hätten, wären Sie sich dessen bewusst gewesen, dass Codeänderungen erforderlich waren, und Sie hätten sie geändert.)
quelle
Nein, nicht unbedingt.
Die ursprüngliche Anforderung bestand darin, numerische Verzweigungscodes zu verwenden, sodass ein Einheitentest für eine Komponente erstellt worden wäre, die verschiedene Codes akzeptierte und etwa 10B ablehnte. Das System wäre als funktionierend übergeben worden (was es war).
Dann hätte sich die Anforderung geändert und die Codes aktualisiert, aber dies hätte zur Folge gehabt, dass der Unit-Test-Code, der die fehlerhaften Daten lieferte (das sind jetzt gute Daten), geändert werden müsste.
Nun nehmen wir an, dass die Leute, die das System verwalten, dies wissen und den Komponententest ändern würden, um die neuen Codes zu handhaben ... aber wenn sie gewusst hätten, dass dies eintrifft, hätten sie auch gewusst, dass der Code, der diese handhabt, geändert wird Codes sowieso .. und sie haben das nicht getan. Ein Komponententest, der ursprünglich Code 10B abgelehnt hat, hätte beim Ausführen gerne "alles in Ordnung" gesagt, wenn Sie nicht gewusst hätten, diesen Test zu aktualisieren.
Unit-Tests eignen sich gut für die ursprüngliche Entwicklung, aber nicht für Systemtests, insbesondere nicht 15 Jahre nach langem Vergessen der Anforderungen.
Was sie in einer solchen Situation brauchen, ist ein durchgehender Integrationstest. Hier können Sie die erwarteten Daten übergeben und prüfen, ob dies der Fall ist. Jemand hätte bemerkt, dass die neuen Eingabedaten keinen Bericht ergaben, und würde dann weitere Nachforschungen anstellen.
quelle
Typentests (der Prozess des Testens von Invarianten mit zufällig generierten gültigen Daten, wie beispielsweise die Haskell- Testbibliothek QuickCheck und verschiedene in anderen Sprachen inspirierte Ports / Alternativen) haben dieses Problem möglicherweise behoben, Unit-Tests hätten dies mit ziemlicher Sicherheit nicht getan .
Dies liegt daran, dass bei der Aktualisierung der Regeln für die Gültigkeit von Zweigstellencodes wahrscheinlich niemand daran gedacht hätte, diese spezifischen Bereiche zu testen, um sicherzustellen, dass sie ordnungsgemäß funktionieren.
Wenn jedoch ein Typentest verwendet wurde, sollte jemand zum Zeitpunkt der Implementierung des ursprünglichen Systems ein Paar von Eigenschaften geschrieben haben, um zu überprüfen, ob die spezifischen Codes für Testzweige als Testdaten behandelt wurden, und um zu überprüfen, ob keine anderen Codes vorhanden waren Wäre ..., wenn die Datentypdefinition für den Verzweigungscode aktualisiert worden wäre (was erforderlich gewesen wäre, um zu testen, ob eine der Änderungen für den Verzweigungscode von Ziffer zu Ziffer funktioniert hat), hätte dieser Test begonnen, Werte in zu testen der neue Bereich und hätte höchstwahrscheinlich den Fehler identifiziert.
Natürlich wurde QuickCheck erstmals im Jahr 1999 entwickelt, sodass es für dieses Problem bereits zu spät war.
quelle
Ich bezweifle wirklich, dass Unit-Tests dieses Problem lösen könnten. Es klingt wie eine dieser Tunnelblicksituationen, da die Funktionalität geändert wurde, um neue Verzweigungscodes zu unterstützen, dies jedoch nicht in allen Bereichen des Systems durchgeführt wurde.
Wir verwenden Unit-Tests, um eine Klasse zu entwerfen. Ein erneutes Ausführen eines Komponententests ist nur erforderlich, wenn sich das Design geändert hat. Wenn sich eine bestimmte Einheit nicht ändert, liefern die unveränderten Einheitentests die gleichen Ergebnisse wie zuvor. Komponententests zeigen Ihnen nicht die Auswirkungen von Änderungen an anderen Einheiten (wenn dies der Fall ist, schreiben Sie keine Komponententests).
Sie können dieses Problem nur auf folgende Weise erkennen:
Das Fehlen ausreichender End-to-End-Tests ist besorgniserregender. Sie können sich nicht auf Unit-Tests als NUR- oder HAUPT-Test für Systemänderungen verlassen. Klingt so, als müsste nur jemand einen Bericht über die neu unterstützten Zweigcodeformate erstellen.
quelle
Eine in die Laufzeit integrierte Behauptung könnte geholfen haben; zum Beispiel:
bool isTestOnly(string branchCode) { ... }
Siehe auch:
quelle
Die Konsequenz daraus ist, schnell zu scheitern .
Wir haben weder den Code noch viele Beispiele für Präfixe, die gemäß dem Code Testzweigpräfixe sind oder nicht. Wir haben nur Folgendes:
Die Tatsache, dass der Code Zahlen und Zeichenfolgen zulässt, ist mehr als ein bisschen seltsam. Natürlich können 10B und 10C als Hexadezimalzahlen betrachtet werden, aber wenn die Präfixe alle als Hexadezimalzahlen behandelt werden, liegen 10B und 10C außerhalb des Testbereichs und würden als echte Verzweigungen behandelt.
Dies bedeutet wahrscheinlich, dass das Präfix als Zeichenfolge gespeichert, in einigen Fällen jedoch als Zahl behandelt wird. Der einfachste Code, den ich mir vorstellen kann, repliziert dieses Verhalten (mit C # zur Veranschaulichung):
Wenn die Zeichenfolge auf Englisch eine Zahl ist und zwischen 89 und 100 liegt, handelt es sich um einen Test. Wenn es keine Zahl ist, ist es ein Test. Ansonsten ist es kein Test.
Wenn der Code diesem Muster folgt, hätte dies zum Zeitpunkt der Bereitstellung des Codes kein Komponententest festgestellt. Hier sind einige Beispiel-Unit-Tests:
Der Komponententest zeigt, dass "10B" als Testzweig behandelt werden sollte. User @ gnasher729 oben sagt, dass sich die Geschäftsregeln geändert haben und das zeigt die letzte Behauptung oben. Irgendwann hätte diese Behauptung zu einer wechseln sollen
isFalse
, aber das ist nicht geschehen. Unit-Tests werden zur Entwicklungs- und Build-Zeit ausgeführt, jedoch zu keinem Zeitpunkt danach.Was ist die Lektion hier? Der Code muss auf irgendeine Weise signalisieren, dass er unerwartete Eingaben erhalten hat. Hier ist eine alternative Methode zum Schreiben dieses Codes, mit der hervorgehoben wird, dass das Präfix eine Zahl sein soll:
Für diejenigen, die C # nicht kennen, gibt der Rückgabewert an, ob der Code ein Präfix aus der angegebenen Zeichenfolge analysieren konnte oder nicht. Wenn der Rückgabewert true ist, kann der aufrufende Code mit der Variablen isTest out prüfen, ob das Zweigpräfix ein Testpräfix ist. Wenn der Rückgabewert false ist, sollte der aufrufende Code melden, dass das angegebene Präfix nicht erwartet wird, und die Variable isTest out ist bedeutungslos und sollte ignoriert werden.
Wenn Sie mit Ausnahmen einverstanden sind, können Sie dies stattdessen tun:
Diese Alternative ist einfacher. In diesem Fall muss der aufrufende Code die Ausnahme abfangen. In beiden Fällen sollte der Code eine Möglichkeit haben, dem Aufrufer zu melden, dass er kein strPrefix erwartet hat, das nicht in eine Ganzzahl konvertiert werden konnte. Auf diese Weise versagt der Code schnell und die Bank kann das Problem schnell finden, ohne dass es der SEC peinlich ist.
quelle
So viele Antworten und nicht einmal ein Zitat von Dijkstra:
Daher kommt es darauf an. Wenn der Code richtig getestet wurde, würde dieser Fehler höchstwahrscheinlich nicht existieren.
quelle
Ich denke, ein Unit-Test hier hätte dafür gesorgt, dass das Problem überhaupt nicht existiert.
Bedenken Sie, Sie haben die
bool IsTestData(string branchCode)
Funktion geschrieben.Der erste Unit-Test, den Sie schreiben, sollte eine leere und eine leere Zeichenfolge sein. Dann für falsche Längenzeichenfolgen dann für nicht ganzzahlige Zeichenfolgen.
Um alle diese Tests zu bestehen, müssen Sie der Funktion eine Parameterprüfung hinzufügen.
Selbst wenn Sie dann nur auf 'gute' Daten prüfen, 001 -> 999, ohne an die Möglichkeit von 10A zu denken, zwingt Sie die Parameterprüfung, die Funktion neu zu schreiben, wenn Sie mit der Verwendung von alphanumerischen Zeichen beginnen, um die Ausnahmen zu vermeiden, die sie auslösen
quelle
IsValidBranchCode
für diese Prüfung nicht eine Funktion verwendet ? Und diese Funktion wäre wahrscheinlich geändert worden, ohne dass das geändert werden müssteIsTestData
? Wenn Sie also nur "gute Daten" testen, hätte der Test nicht geholfen. Der Edge-Case-Test hätte einige jetzt gültige (und nicht nur einige noch ungültige) Verzweigungscodes enthalten müssen, um einen Fehler zu verursachen.