Garantiert die Pfadabdeckung, dass alle Fehler gefunden werden?


64

Wenn jeder Pfad durch ein Programm getestet wird, ist dann sichergestellt, dass alle Fehler gefunden werden?

Wenn nein, warum nicht? Wie können Sie alle möglichen Kombinationen von Programmabläufen durchgehen und das Problem, falls vorhanden, nicht finden?

Ich zögere vorzuschlagen, dass "alle Fehler" gefunden werden können, aber vielleicht liegt das daran, dass die Pfadabdeckung nicht praktikabel ist (da sie kombinatorisch ist), sodass sie nie erlebt wird?

Hinweis: Dieser Artikel enthält eine kurze Zusammenfassung der Abdeckungstypen, wenn ich darüber nachdenke.


33
Dies entspricht dem Halteproblem .

31
Was ist, wenn Code, der hätte da sein sollen, nicht da ist?
RemcoGerlich

6
@Schneemann: Nein, das ist es nicht. Es ist nicht möglich, das Halteproblem für alle Programme zu lösen, aber für viele spezifische Programme ist es lösbar. Für diese Programme können alle Codepfade in einer begrenzten (wenn auch möglicherweise langen) Zeitspanne aufgelistet werden.
Jørgen Fogh,

3
@ JørgenFogh Aber wenn Sie versuchen, Fehler in einem Programm zu finden , ist es nicht a priori unbekannt, ob das Programm anhält oder nicht? Geht es nicht um die allgemeine Methode, "alle Fehler in einem Programm über die Pfadabdeckung zu finden"? In welchem ​​Fall ähnelt es nicht der Frage, ob ein Programm anhält?
Andres F.

1
@AndresF. Es ist nur unbekannt, ob das Programm anhält, wenn die Teilmenge der Sprache, in der es geschrieben ist, in der Lage ist, ein nicht anhaltendes Programm auszudrücken. Wenn Ihr Programm in C ohne Verwendung von unbegrenzten Schleifen / Rekursion / Setjmp usw. oder in Coq oder in ESSL geschrieben ist, muss es anhalten und alle Pfade können verfolgt werden. (Turing-Vollständigkeit ist ernsthaft überbewertet)
Leushenko

Antworten:


128

Wenn jeder Pfad durch ein Programm getestet wird, ist dann sichergestellt, dass alle Fehler gefunden werden?

Nein

Wenn nein, warum nicht? Wie können Sie alle möglichen Kombinationen von Programmabläufen durchgehen und das Problem, falls vorhanden, nicht finden?

Denn selbst wenn Sie alle möglichen Pfade testen , haben Sie sie noch nicht mit allen möglichen Werten oder allen möglichen Wertekombinationen getestet . Zum Beispiel (Pseudocode):

def Add(x as Int32, y as Int32) as Int32:
   return x + y

Test.Assert(Add(2, 2) == 4) //100% test coverage
Add(MAXINT, 5) //Throws an exception, despite 100% test coverage

Es ist nun zwei Jahrzehnte her, seit darauf hingewiesen wurde, dass Programmtests das Vorhandensein von Fehlern überzeugend nachweisen können, ihre Abwesenheit jedoch niemals nachweisen können. Nachdem der Softwareentwickler diese wohlbekannte Bemerkung ausführlich zitiert hat, kehrt er zur Tagesordnung zurück und verfeinert weiterhin seine Teststrategien, genau wie der Alchemist von einst, der seine chrysokosmischen Reinigungen weiter verfeinert hat.

- EW Dijkstra (Hervorhebung hinzugefügt. Geschrieben im Jahr 1988. Es ist jetzt deutlich mehr als 2 Jahrzehnte.)


7
@digitgopher: Ich nehme an, aber wenn ein Programm keine Eingabe hat, welche nützliche Funktion hat es?
Mason Wheeler

34
Es besteht auch die Möglichkeit, dass Integrationstests, Fehler in den Tests, Fehler in Abhängigkeiten, Fehler im Build / Deployment-System oder Fehler in den ursprünglichen Spezifikationen / Anforderungen fehlen. Sie können niemals garantieren, dass Sie alle Bugs finden.
Ixrec

11
@Ixrec: SQLite unternimmt jedoch eine ziemlich tapfere Anstrengung! Aber seht euch an, was für eine enorme Anstrengung das ist! Für große Codebasen wäre das nicht gut skalierbar.
Mason Wheeler

13
Sie würden nicht nur nicht alle möglichen Werte oder Kombinationen davon testen, sondern auch nicht alle relativen Timings, von denen einige die Rennbedingungen enthüllen oder Ihren Test in eine Sackgasse führen könnten, die dazu führen würde, dass nichts gemeldet wird . Es wäre nicht einmal ein Fehlschlag!
Ich werde nicht existieren Idonotexist

14
Ich erinnere mich (gestützt durch Schriften wie diese ), dass Dijkstra der Ansicht war, dass in guten Programmierpraktiken der Beweis, dass ein Programm (unter allen Umständen) korrekt ist, ein wesentlicher Bestandteil der Entwicklung des Programms sein sollte. Gesehen von diesem Gesichtspunkt, Prüfung ist wie Alchemie. Ich glaube, dies war keine Übertreibung, sondern eine sehr starke Meinung, die in einer sehr starken Sprache ausgedrückt wurde.
David K

71

Neben der Antwort von Mason gibt es noch ein weiteres Problem: Die Abdeckung gibt nicht an , welcher Code getestet wurde, sondern welcher Code ausgeführt wurde .

Stellen Sie sich vor, Sie haben eine Testsuite mit 100% Pfadabdeckung. Entfernen Sie nun alle Zusicherungen und führen Sie die Testsuite erneut aus. Voilà, die Testsuite hat immer noch eine 100% ige Pfadabdeckung, testet aber absolut nichts.


2
Es könnte sicherstellen, dass es keine Ausnahme gibt, wenn der getestete Code (mit den Parametern im Test) aufgerufen wird. Das ist etwas mehr als nichts.
Paŭlo Ebermann

7
@ PaŭloEbermann Einverstanden, etwas mehr als nichts. Es ist jedoch ungemein weniger als "alle Bugs finden";)
Andres F.

1
@ PaŭloEbermann: Ausnahmen sind ein Codepfad. Wenn der Code ausgelöst werden kann, bei bestimmten Testdaten jedoch keine Auslösung erfolgt, erreicht der Test keine 100% ige Pfadabdeckung. Dies gilt nicht speziell für Ausnahmen als Fehlerbehandlungsmechanismus. Visual Basic ON ERROR GOTOist auch ein Weg, wie C ist if(errno).
MSalters

1
@MSalters Ich spreche von Code, der (nach Spezifikation) keine Ausnahme auslösen sollte, unabhängig von der Eingabe. Wenn es welche gibt, wäre das ein Bug. Wenn Sie Code haben, der zum Auslösen einer Ausnahme angegeben ist, sollte dieser natürlich getestet werden. (Und natürlich reicht es, wie Jörg sagte, normalerweise nicht aus, zu überprüfen, ob der Code keine Ausnahme auslöst, um sicherzustellen, dass er das Richtige tut, auch wenn kein Code ausgelöst wird.) Und einige Ausnahmen können von einem Nicht-Auslöser ausgelöst werden -sichtbarer Codepfad, wie bei Nullzeiger-Dereferenzierung oder Division durch Null. Erfasst Ihr Pfadabdeckungs-Tool diese?
Paŭlo Ebermann

2
Diese Antwort nagelt es. Ich würde die Behauptung noch weiter gehen und sagen, dass die Pfadabdeckung aus diesem Grund niemals garantiert, dass auch nur ein einziger Fehler gefunden wird. Es gibt Metriken , die jedoch erkannt werden , dass zumindest Änderungen garantieren kann - Test Mutation kann tatsächlich garantieren , dass (einige) Änderungen des Codes wird erkannt werden.
Eis

34

Hier ist ein einfaches Beispiel, um die Dinge abzurunden. Betrachten Sie den folgenden Sortieralgorithmus (in Java):

int[] sort(int[] x) { return new int[] { x[0] }; }

Jetzt lass uns testen:

sort(new int[] { 0xCAFEBABE });

Nehmen wir nun an, dass (A) dieser spezielle Aufruf sortdas richtige Ergebnis zurückgibt, (B) alle Codepfade von diesem Test abgedeckt wurden.

Aber offensichtlich sortiert das Programm nicht wirklich.

Daraus folgt, dass die Abdeckung aller Codepfade nicht ausreicht, um sicherzustellen, dass das Programm keine Fehler aufweist.


12

Betrachten Sie die absFunktion, die den absoluten Wert einer Zahl zurückgibt. Hier ist ein Test (Python, stellen Sie sich ein Test-Framework vor):

def test_abs_of_neg_number_returns_positive():
    assert abs(-3) == 3

Diese Implementierung ist korrekt, erreicht jedoch nur eine Codeabdeckung von 60%:

def abs(x):
    if x < 0:
        return -x
    else:
        return x

Diese Implementierung ist falsch, wird jedoch zu 100% mit Code abgedeckt:

def abs(x):
    return -x

2
Hier ist eine weitere Implementierung, die den Test besteht (bitte entschuldigen Sie das nicht def abs(x): if x == -3: return 3 else: return 0zeilenweise unterbrochene Python): Sie könnten das else: return 0Teil möglicherweise umgehen und eine 100% ige Abdeckung erhalten, aber die Funktion wäre im Wesentlichen nutzlos, obwohl sie den Komponententest besteht.
Lebenslauf vom

7

Als weitere Ergänzung zu Masons Antwort kann das Verhalten eines Programms von der Laufzeitumgebung abhängen.

Der folgende Code enthält ein Use-After-Free:

int main(void)
{
    int* a = malloc(sizeof(a));
    int* b = a;
    *a = 0;
    free(a);
    *b = 12; /* UAF */
    return 0;
}

Dieser Code ist Undefiniertes Verhalten. Abhängig von der Konfiguration (release | debug), dem Betriebssystem und dem Compiler ergeben sich unterschiedliche Verhaltensweisen. Nicht nur die Pfadabdeckung garantiert nicht, dass Sie die UAF finden, sondern Ihre Testsuite deckt in der Regel nicht die verschiedenen möglichen Verhaltensweisen der UAF ab, die von der Konfiguration abhängen.

Zum anderen ist es unwahrscheinlich, dass die Pfadabdeckung in der Praxis mit einem Programm erreicht werden kann, auch wenn die Aufdeckung aller Fehler garantiert ist. Betrachten Sie das folgende:

int main(int a, int b)
{
    if (a != b) {
        if (cryptohash(a) == cryptohash(b)) {
            return ERROR;
        }
    }
    return 0;
} 

Wenn Ihre Testsuite alle Pfade dafür erzeugen kann, dann gratulieren Sie einem Kryptographen.


Einfach für ausreichend kleine ganze Zahlen :)
CodesInChaos

Ohne etwas darüber zu wissen cryptohash, ist es ein wenig schwierig zu sagen, was "ausreichend klein" ist. Vielleicht dauert es zwei Tage, bis ein Superrechner fertig ist. Aber ja, intkönnte sich als etwas herausstellen short.
Dureuill

Bei 32-Bit-Ganzzahlen und typischen kryptografischen Hashes (SHA2, SHA3 usw.) sollte dies recht billig sein. Ein paar Sekunden oder so.
CodesInChaos

7

Aus den anderen Antworten geht hervor, dass 100% Code-Abdeckung in Tests nicht 100% Code-Korrektheit bedeutet, oder sogar, dass alle Fehler, die durch Tests gefunden werden konnten, gefunden werden (egal, welche Fehler kein Test finden konnte).

Eine andere Möglichkeit, diese Frage zu beantworten, ist eine aus der Praxis:

In der realen Welt und auf Ihrem eigenen Computer gibt es viele Softwareteile, die mit einer Reihe von Tests entwickelt wurden, die eine hundertprozentige Abdeckung bieten und dennoch Fehler aufweisen, einschließlich Fehler, die durch bessere Tests identifiziert werden können.

Eine damit verbundene Frage lautet daher:

Was ist der Sinn von Code-Coverage-Tools?

Tools zur Codeabdeckung helfen dabei, Bereiche zu identifizieren, die nicht getestet wurden. Das kann in Ordnung sein (der Code ist nachweislich auch ohne Tests korrekt), es kann unmöglich sein, ihn aufzulösen (aus irgendeinem Grund kann ein Pfad nicht erreicht werden), oder es kann der Ort eines großen stinkenden Fehlers sein, entweder jetzt oder nach zukünftigen Änderungen.

In gewisser Hinsicht ist die Rechtschreibprüfung vergleichbar: Etwas kann die Rechtschreibprüfung bestehen und so falsch geschrieben werden, dass es mit einem Wort im Wörterbuch übereinstimmt. Oder es kann "scheitern", weil die richtigen Wörter nicht im Wörterbuch enthalten sind. Oder es kann vergehen und völliger Unsinn sein. Die Rechtschreibprüfung ist ein Tool, mit dem Sie Stellen identifizieren können, an denen Sie möglicherweise das Korrekturlesen verpasst haben. Ebenso wenig kann jedoch eine vollständige und korrekte Korrektur gewährleistet werden, sodass die Codeabdeckung keine vollständige und korrekte Prüfung gewährleisten kann.

Und natürlich ist der falsche Weg, die Rechtschreibprüfung zu verwenden, für jeden Vorschlag bekannt, den es vorschlägt, so dass das Ducking noch schlimmer wird, wenn das Mutterschaf ihm einen Kredit hinterlässt.

Bei der Codeabdeckung kann es verlockend sein, Fälle auszufüllen, damit die verbleibenden Pfade erreicht werden. Dies gilt insbesondere für nahezu perfekte 98%.

Dies ist das Äquivalent zu einer Rechtschreibprüfung, bei der nur die richtigen Wörter angezeigt werden. Das Ergebnis ist ein Durcheinander.

Wenn Sie sich jedoch überlegen, welche Tests die nicht abgedeckten Pfade wirklich benötigen, hat das Code-Coverage-Tool seine Aufgabe erfüllt. nicht, um Ihnen die Richtigkeit zu versprechen, sondern um auf einige der zu erledigenden Arbeiten hinzuweisen .


+1 Diese Antwort gefällt mir, weil sie konstruktiv ist und einige der Vorteile der Deckung erwähnt.
Andres F.

4

Die Pfadabdeckung kann Ihnen nicht sagen, ob alle erforderlichen Funktionen implementiert wurden. Das Weglassen einer Funktion ist ein Fehler, der jedoch von der Pfadabdeckung nicht erkannt wird.


1
Ich denke, das hängt von der Definition eines Fehlers ab. Ich denke nicht, dass fehlende Features oder Funktionen als Fehler angesehen werden sollten.
Eis

@eis - Sie sehen kein Problem mit einem Produkt, dessen Dokumentation besagt, dass X funktioniert, obwohl dies nicht der Fall ist? Das ist eine ziemlich enge Definition von "Bug". Als ich die Qualitätssicherung für die C ++ - Produktlinie von Borland leitete, waren wir nicht so großzügig.
Pete Becker

Ich verstehe nicht, warum in der Dokumentation X steht, wenn dies nie implementiert wurde
eis

@eis - Wenn das ursprüngliche Design Feature X erfordert, wird in der Dokumentation möglicherweise Feature X beschrieben. Wenn dies nicht implementiert wird, handelt es sich um einen Fehler, und die Pfadabdeckung (oder eine andere Art von Black-Box-Test) wird ihn nicht finden.
Pete Becker

Hoppla, die Pfadabdeckung ist ein White-Box- Test, kein Black-Box- Test . White-Box-Tests können fehlende Funktionen nicht erkennen.
Pete Becker

4

Ein Teil des Problems ist, dass 100% ige Abdeckung nur garantiert, dass der Code nach einer einzelnen Ausführung korrekt funktioniert . Einige Fehler, wie z. B. Speicherlecks, sind nach einer einzelnen Ausführung möglicherweise nicht erkennbar oder verursachen Probleme. Mit der Zeit können jedoch Probleme für die Anwendung auftreten.

Angenommen, Sie haben eine Anwendung, die eine Verbindung zu einer Datenbank herstellt. Vielleicht vergisst der Programmierer bei einer Methode, die Verbindung zur Datenbank zu schließen, wenn sie mit ihrer Abfrage fertig sind. Sie könnten mehrere Tests mit dieser Methode durchführen und dabei keine Fehler in der Funktionalität feststellen, aber Ihr Datenbankserver könnte in ein Szenario geraten, in dem keine Verbindungen mehr verfügbar sind, da diese bestimmte Methode die Verbindung nicht geschlossen hat, als sie hergestellt wurde und die Verbindungen geöffnet sein mussten jetzt timeout.


Einverstanden, dass das Teil des Problems ist, aber das eigentliche Problem ist grundlegender. Selbst bei einem theoretischen Computer mit unbegrenztem Speicher und ohne Parallelität bedeutet eine Testabdeckung von 100% nicht, dass keine Fehler vorliegen. Triviale Beispiele dafür finden Sie in den Antworten hier, aber hier ist eine andere: Wenn mein Programm vorhanden ist times_two(x) = x + 2, wird dies vollständig von der Testsuite abgedeckt assert(times_two(2) == 4), aber dies ist offensichtlich immer noch fehlerhafter Code! Keine Notwendigkeit für Speicherlecks :)
Andres F.

2
Es ist ein großartiger Punkt und ich erkenne, dass es ein größerer / grundlegenderer Nagel im Sarg der Möglichkeit von fehlerfreien Anwendungen ist, aber wie Sie sagen, wurde er bereits hier hinzugefügt und ich wollte etwas hinzufügen, das nicht ganz abgedeckt war vorhandene Antworten. Ich habe von Anwendungen gehört, die abgestürzt sind, weil Datenbankverbindungen nicht zurück in den Verbindungspool freigegeben wurden, als sie nicht mehr benötigt wurden. Ein Speicherverlust ist nur ein kanonisches Beispiel für Ressourcenmissmanagement. Mein Punkt war, hinzuzufügen, dass eine ordnungsgemäße Verwaltung der Ressourcen im Allgemeinen nicht vollständig getestet werden kann.
Derek W

Guter Punkt. Einverstanden.
Andres F.

3

Wenn jeder Pfad durch ein Programm getestet wird, ist dann sichergestellt, dass alle Fehler gefunden werden?

Wie bereits gesagt, lautet die Antwort NEIN.

Wenn nein, warum nicht?

Abgesehen von dem, was gesagt wird, treten auf verschiedenen Ebenen Fehler auf, die mit Unit-Tests nicht getestet werden können. Um nur einige zu nennen:

  • Fehler, die bei Integrationstests entdeckt wurden (Unit-Tests sollten schließlich keine echten Ressourcen verbrauchen)
  • Fehler in den Anforderungen
  • Fehler in Design und Architektur

2

Was bedeutet es für jeden zu testenden Pfad?

Die anderen Antworten sind großartig, aber ich möchte nur hinzufügen, dass die Bedingung "jeder Pfad durch ein Programm wird getestet" selbst vage ist.

Betrachten Sie diese Methode:

def add(num1, num2)
  foo = "bar"  # useless statement
  $global += 1 # side effect
  num1 + num2  # actual work
end

Wenn Sie einen Test schreiben, der bestätigt add(1, 2) == 3, werden Sie von einem Code-Coverage-Tool darüber informiert, dass jede Zeile ausgeführt wird. Aber Sie haben tatsächlich nichts über den globalen Nebeneffekt oder die nutzlose Zuordnung behauptet. Diese Zeilen wurden ausgeführt, aber nicht wirklich getestet.

Mutationstests würden helfen, solche Probleme zu finden. Ein Mutationstest-Tool verfügt über eine Liste vordefinierter Methoden, um den Code zu "mutieren" und festzustellen, ob die Tests noch erfolgreich sind. Zum Beispiel:

  • Eine Mutation könnte das +=zu ändern -=. Diese Mutation würde keinen Testfehler verursachen. Dies würde also beweisen, dass Ihr Test keine Aussage über den globalen Nebeneffekt macht.
  • Eine andere Mutation könnte die erste Zeile löschen. Diese Mutation würde keinen Testfehler verursachen. Dies würde also beweisen, dass Ihr Test nichts aussagekräftiges über die Zuweisung aussagt.
  • Eine weitere Mutation könnte die dritte Zeile löschen. Das würde einen Testfehler verursachen, der in diesem Fall zeigt, dass Ihr Test etwas über diese Zeile aussagt.

Im Wesentlichen sind Mutationstests eine Möglichkeit, Ihre Tests zu testen . Aber so wie Sie niemals die tatsächliche Funktion mit jedem möglichen Satz von Eingaben testen werden, werden Sie niemals jede mögliche Mutation ausführen. Auch dies ist also begrenzt.

Jeder Test, den wir machen können, ist eine Heuristik, um auf fehlerfreie Programme umzusteigen. Nichts ist perfekt.


0

Naja ... ja eigentlich, wenn jeder Pfad "durch" das Programm getestet wird. Das heißt aber, jeder mögliche Pfad durch den gesamten Raum aller möglichen Zustände, die das Programm haben kann, einschließlich aller Variablen. Selbst für ein sehr einfaches statisch kompiliertes Programm - zum Beispiel einen alten Fortran-Zahlenknacker - ist dies nicht realisierbar, obwohl es zumindest vorstellbar ist: Wenn Sie nur zwei ganzzahlige Variablen haben, haben Sie im Grunde alle Möglichkeiten, Punkte miteinander zu verbinden ein zweidimensionales Gitter; es sieht tatsächlich sehr nach Travelling Salesman aus. Für n solche Variablen haben Sie es mit einem n- dimensionalen Raum zu tun, sodass die Aufgabe für jedes echte Programm völlig untrahierbar ist.

Schlimmer noch: Für ernsthafte Dinge gibt es nicht nur eine feste Anzahl primitiver Variablen, sondern Sie erstellen Variablen in Funktionsaufrufen im Handumdrehen, oder Sie haben Variablen mit variabler Größe ... oder ähnliches, wie dies in einer Turing-vollständigen Sprache möglich ist. Das macht den Staatsraum unendlich dimensioniert und macht alle Hoffnungen auf eine vollständige Abdeckung zunichte, selbst bei absurd leistungsfähigen Testgeräten.


Das heißt ... eigentlich sind die Dinge nicht ganz so trostlos. Es ist möglich, ganze Programme als richtig zu beweisen , aber Sie müssen ein paar Ideen aufgeben.

Erstens: Es ist sehr ratsam, zu einer deklarativen Sprache zu wechseln. Imperative Sprachen waren aus irgendeinem Grund immer die bei weitem beliebtesten, aber die Art und Weise, wie sie Algorithmen mit realen Interaktionen kombinieren, macht es extrem schwierig, überhaupt zu sagen, was Sie mit „richtig“ meinen .

Viel einfacher in rein funktionalen Programmiersprachen: Diese unterscheiden klar zwischen den wirklich interessanten Eigenschaften mathematischer Funktionen und den unscharfen realen Interaktionen, über die man eigentlich nichts sagen kann. Für die Funktionen ist es sehr einfach, „korrektes Verhalten“ anzugeben: Wenn für alle möglichen Eingaben (aus den Argumenttypen) das entsprechende gewünschte Ergebnis ausgegeben wird, verhält sich die Funktion korrekt.

Nun, Sie sagen, das ist immer noch unlösbar. Schließlich ist der Raum aller möglichen Argumente im Allgemeinen auch unendlich-dimensional. Richtig - obwohl für eine einzelne Funktion sogar naive Abdeckungstests Sie weiter führen, als Sie jemals in einem zwingenden Programm erhoffen könnten! Es gibt jedoch ein unglaublich mächtiges Werkzeug, das das Spiel verändert: universelle Quantifizierung / parametrischer Polymorphismus . Grundsätzlich können Sie auf diese Weise Funktionen für sehr allgemeine Datentypen schreiben, mit der Garantie, dass, wenn dies für ein einfaches Beispiel der Daten funktioniert, dies für alle möglichen Eingaben überhaupt funktioniert.

Zumindest theoretisch. Es ist nicht einfach, die richtigen Typen zu finden, die wirklich so allgemein sind, dass Sie dies vollständig beweisen können - normalerweise benötigen Sie eine abhängig geschriebene Sprache , und diese sind in der Regel recht schwierig zu verwenden. Aber das Schreiben in einem funktionalen Stil mit parametrischem Polymorphismus allein erhöht bereits enorm Ihre „Sicherheitsstufe“ - Sie werden nicht unbedingt alle Fehler finden, aber Sie müssen sie ziemlich gut verbergen, damit der Compiler sie nicht erkennt!


Ich bin mit deinem ersten Satz nicht einverstanden. Wenn Sie jeden Status des Programms durchgehen, werden an sich keine Fehler erkannt. Selbst wenn Sie nach Abstürzen und expliziten Fehlern suchen, haben Sie die tatsächliche Funktionalität in keiner Weise überprüft, sodass Sie nur einen kleinen Teil des Fehlerbereichs abgedeckt haben.
Matthew Read

@MatthewRead: Wenn Sie dies konsequent anwenden, ist der "Fehlerraum" ein geeigneter Unterraum des Raums aller Zustände. Natürlich ist dies hypothetisch, da selbst die „richtigen“ Zustände viel zu viel Platz einnehmen, um umfassende Tests zu ermöglichen.
Leftaroundabout
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.