Überdeckungskriterien beim Glass-Box-Testing

Stefan Winter

University of Ulm

LMU Munich

Materialien für diesen Vortrag

https://www.stefan-winter.net/presentations/teaching_demo_glassbox.html

Lernziele

  • Weshalb benötigt man Testendekriterien?
  • Was ist ein Kontrollflussgraph?
  • Was bedeutet Anweisungs-/Zweig-/Pfadüberdeckung?

Erschöpfendes Testen

Ihre Aufgabe im Software-Praktikum: Implementieren und testen Sie…

public int sum(int a, int b)

Die Funktion gibt die Summe der beiden ganzzahligen Parameter a und b zurück.

@Test
void testSum() {
    for (int i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i++) {
        for (int j = Integer.MAX_VALUE; j >= Integer.MIN_VALUE; j--) {
            assertTrue(Adder.sum(i, j) == i + j);
        }
    }
}

Laufzeit?

Eingaberaum: \(2^{32}\cdot{}2^{32} \approx 1.8\times{}10^{19}\)

Bei 1.25 \(\mu{}s\) pro Test: 731 178 Jahre 😨

Alternative: Testendekriterien

Testendekriterien

\(X\%\) \(Y\)-Überdeckung

\(X \in{} ]0, 100]\):

  • Überdeckungsgrad
  • Vorläufige Annahme: \(X = 100\), vollständige Überdeckung

\(Y\): Überdeckungsmaß

  • Glass-Box:
    • Kontrollflussbasierte Überdeckung
    • Datenflussbasierte Überdeckung
    • Fehlerbasierte Überdeckung

Kumulative Normalverteilung in “Blackscholes”

double CNDF(double InputX) {
    boolean sign = false;
    double OutputX;
    // Check for negative value of InputX
    if (InputX < 0.0) {
        InputX = -InputX;
        sign = true;
    }
    OutputX = computeNPrimeX(InputX);
    if (sign) {
        OutputX = 1.0 - OutputX;
    }
    return OutputX;
}

Glass-Box-Überdeckungsmaße:

  • Anweisungsüberdeckung
  • Zweigüberdeckung
  • Pfadüberdeckung

Kontrollflussgraphen

double CNDF(double InputX) {
    boolean sign = false;
    double OutputX;
    // Check for negative value of InputX
    if (InputX < 0.0) {
        InputX = -InputX;
        sign = true;
    }
    OutputX = computeNPrimeX(InputX);
    if (sign) {
        OutputX = 1.0 - OutputX;
    }
    return OutputX;
}

Kontrollflussgraph \(G = (V,E)\)

  • Knoten \(V\): Anweisungen
  • Kanten \(E \subseteq V\times{}V\): Kontrollfluss

Anweisungsüberdeckung

double CNDF(double InputX) {
    boolean sign = false;
    double OutputX;
    // Check for negative value of InputX
    if (InputX < 0.0) {
        InputX = -InputX;
        sign = true;
    }
    OutputX = computeNPrimeX(InputX);
    if (sign) {
        OutputX = 1.0 - OutputX;
    }
    return OutputX;
}

Testeingabe:

InputX \(= -0.5\)

Anzahl CFG-Knoten:

\(|V| = 9\)

Anzahl durch Tests besuchte CFG-Knoten:

\(|V_t| = 9\)

Anweisungsüberdeckung:

\(C_0 = \frac{|V_t|}{|V|} = 1\)

Zweigüberdeckung

double CNDF(double InputX) {
    boolean sign = false;
    double OutputX;
    // Check for negative value of InputX
    if (InputX < 0.0) {
        InputX = -InputX;
        sign = true;
    }
    OutputX = computeNPrimeX(InputX);
    if (sign) {
        OutputX = 1.0 - OutputX;
    }
    return OutputX;
}

Testeingabe:

\(t_1\): InputX \(= -0.5\)

\(t_2\): InputX \(= 0.5\)

Anzahl CFG-Kanten:

\(|E| = 10\)

Anzahl durch Tests besuchte CFG-Knoten:

\(|E_{t_1}| = 8\)

\(|E_{t_1,t_2}| = 10\)

Anweisungsüberdeckung:

\(C_{1_{t_1}} = \frac{|E_{t_1}|}{|E|} = 0.8\)

\(C_{1_{t_1,t_2}} = \frac{|E_{t_1,t_2}|}{|E|} = 1\)

Pfadüberdeckung

double CNDF(double InputX) {
    boolean sign = false;
    double OutputX;
    // Check for negative value of InputX
    if (InputX < 0.0) {
        InputX = -InputX;
        sign = true;
    }
    OutputX = computeNPrimeX(InputX);
    if (sign) {
        OutputX = 1.0 - OutputX;
    }
    return OutputX;
}

Pfade vom Start- zum Endknoten:
\(P = \{p = (v_1,\ldots{},v_n). (v_i,v_{i+1}) \subseteq p \Rightarrow (v_i, v_{i+1}) \in E\}\)

\(C_{1_{t_1,t_2}} = \frac{|P_{t_1,t_2}|}{|P|} = 0.5\)

🏁 Lernziele

  • Weshalb benötigt man Testendekriterien?
  • Was ist ein Kontrollflussgraph?
  • Was bedeutet Anweisungs-/Zweig-/Pfadüberdeckung?

Kontext und Ausblick

Weiter zu vertiefende Themen:

  • Kontrollflussbasierte Überdeckungskriterien für sicherheitskritische Systeme (MC/DC)
  • Datenflussbasierte Überdeckungskriterien
  • Mutation-Testing als Alternative zu strukturellen Testendekriterien
  • Testendekriterien für Integrationstests

Übung:

  • Stift und Papier: Kontrollflussgraphen und Überdeckungsanalyse
  • Keyboard und Monitor: Überdeckungsanalyse für GNU coreutils mit gcov und lcov

Literatur:

  • Spillner & Linz: Basiswissen Softwaretest, dpunkt-Verlag
  • Liggesmeyer: Software-Qualität, Springer

Übung: Fehler in Folien finden

Erschöpfendes Testen von public int sum(int a, int b):

@Test
void testSum() {
    for (int i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i++) {
        for (int j = Integer.MAX_VALUE; j >= Integer.MIN_VALUE; j--) {
            assertTrue(Adder.sum(i, j) == i + j);
        }
    }
}

Fehler

Testcode ignoriert arithmetischen Überlauf: Endlosschleifen!

@Test
void testSum() {
    for (int i = Integer.MIN_VALUE; ; i++) {
        for (int j = Integer.MAX_VALUE; ; j--) {
            assertTrue(Adder.sum(i, j) == i + j);
            if (j == Integer.MIN_VALUE) break;
        }
        if (i == Integer.MAX_VALUE) break;
    }
}

→ Mehr Testcode als zu testender Code. Wer testet eigentlich den Testcode?

LV “Flaky Tests” im WiSe 2024/25: Vertiefungsbereich Bachelor