jdienst

27 July 2019

Wie letzte Woche angekündigt, werde ich diese Woche beginnen, die verschiedenen Überdeckungsmaße für Code vorzustellen. Als Beispielprojekt habe ich mir das Game of Life von Conway ausgesucht, was als interessante Übungsaufgabe Einiges an Logik hergibt.

Eine einfache Zelle

Die Regeln für Conways Game of Life sind sehr einfach:

  • Eine tote Zelle wird in der nächsten Generation belebt, wenn sie genau drei lebende Nachbarzellen hat

  • Eine lebende Zelle lebt in der nächsten Generation weiter, wenn sie genau drei oder weniger als drei lebende Nachbarzellen besitzt

  • Eine lebende Zelle stirbt in der nächsten Generation, wenn sie mehr als drei lebende Nachbarn besitzt (Überbevölkerung und so ;-) )

Mit diesen Vorgaben habe ich eine einfach Klasse implementiert. Manche Methoden der Klasse sind zur besseren Testbarkeit eingeführt worden:

package jdienst.basic;

public class Cell {

  private boolean isAlive = false;
  private Cell[] neighbors;

  public Cell(int neighborCount) {
    neighbors = new Cell[neighborCount];
  }

  public boolean isAlive() {
    return isAlive;
  }

  public void enlive() {
    isAlive = true;
  }

  public Cell setNeighbors(Cell[] neighbors) {
    this.neighbors = neighbors;
    return this;
  }

  public boolean isAliveInNextGeneration() {
    int livingNeighbors = 0;
    for (Cell cell : neighbors) {
      if (cell.isAlive()) {
        livingNeighbors++;
      }
    }

    if (!isAlive && livingNeighbors == 3) {
      return true;
    }
    else if (isAlive && livingNeighbors <= 3) {
      return true;
    }

    return false;
  }
}

Zeilenüberdeckung/-abdeckung

Das Ganze habe ich testgetrieben entwickelt. Am Ende ist eine Testsuite von vier Tests entstanden, die auf den ersten Blick hinreichend gut aussieht. Leider kann man sich darauf nicht verlassen. Deswegen gibt es die Möglichkeit die Tests mit Testabdeckungsmaßen zu überprüfen.

Das einfachste dieser Maße ist die Zeilenüberdeckung/-abdeckung, sie gibt an, ob eine bestimmte Codezeile überhaupt von den Tests ausgeführt wird. Dieses Verfahren zeigt also an, welche Abschnitte der Codebasis nicht von der Testsuite ausgeführt werden.

Daraus folgt, dass Fehler, die sich in diesen Bereichen verstecken, nicht gefunden werden können. Deswegen sollte als Faustregel als Ziel immer eine Abdeckung zu 100 % stehen. Wenigstens bei Code, der Logik enthält. Eine vollständige Abdeckung in der gesamten Codebasis ist nicht effizient umsetzbar und deswegen unwirtschaftlich.

Testsuite bewerten

An dieser Stelle zeige ich die ersten beiden Tests. Den Rest kann man in meinem Repository auf Github finden. Das Projekt inklusive Konfiguration für alle Tools wurde mit Maven angelegt. Mit mvn clean package site:site löst man das Reporting aus. Unter target/site/project-reports.html kann der Report für die Zeilenüberdeckung angezeigt werden.

Hier nun endlich die Tests:

@Test
public void shouldBeDeadAfterConstruction() {
  Cell cell = new Cell(8);
  assertFalse("Cell is alive. But it should not be alive after construction", cell.isAlive());
}

@Test
public void setThreeLivingNeighbors_IsDead_IsAliveInNextGeneration() {
  Cell cell = getCell(false, 8);
  Cell[] neighbors = new Cell[8];
  for (int i = 0; i < 8; i++) {
     if (i < 3) {
      neighbors[i] = getCell(true, 8);
    }
    else {
      neighbors[i] = getCell(false, 8);
    }
  }
  cell.setNeighbors(neighbors);
  assertTrue("Cell should be alive with three living neighbors", cell.isAliveInNextGeneration());
}

public void setThreeLivingNeighbors_IsAlive_IsAliveNextGeneration() { /* ... */ }

public void setEightLivingNeighbors_IsAlive_IsDeadNextGeneration() { /* ... */ }

Wie man sieht, reichen die Tests aus, um sicherzustellen, dass jede Zeile Code mindestens einmal von den Tests ausgeführt wird.

Line Coverage der Zelle Überblick

Ein genauerer Blick verrät uns jedoch, dass das Tool noch Verbesserungsvorschläge in der Verzweigungsüberdeckung (Branch Coverage) hat. Die Detailansicht gibt Aufschluss darüber, an welchen Stellen es hakt. Darum kümmern wir uns nächste Woche.

Line Coverage der Zelle im Detail

Fazit

Die Zeilenüberdeckung/-abdeckung ist das einfachste Überdeckungsmaß. Mit diesem kann festgestellt werden, an welcher Stelle die Tests noch ausgebaut werden müssen, damit jede Zeile der Codebasis von den Tests ausgeführt wird. Leider trifft es keine Aussage darüber, ob die Tests auf irgendeine Weise sinnvoll sind.

Welche Fehler es noch in der Cell-Klasse gibt und wie die Qualität der Tests durch Verzweigungsüberdeckung gesteigert werden kann, wird im nächsten Blog gezeigt. Bis dahin: Happy Testing!