Filter anzeigen

All

News

Referenzen

  • #Software
  • #Development
  • #Testing
  • #JavaScript
  • #TDD
  • #Meetup
  • #Netzwerk
  • #JS Meetup
  • #Technologie
  • #ePrivacy
  • #DSVGO
  • #Datenschutz
  • #Digitalisierung
  • #Agil
  • #Apps
  • #Frontend
  • #Web
  • #Backend
  • #Informationsmanagement
  • #Referenzen
  • #Controlling
  • #Excel
  • #Microsoft
  • #Planning
  • #Projektmanagement
  • #Reporting
  • #SharePoint
  • #Prozesse
  • #Individualsoftware
  • #Förderung
  • #SAB
  • #UseCase
  • #Quality
  • #Electron
  • #React
  • #Tiefbau

Unit Tests

August 2019

von Paul Friedemann

#Software #Development #Testing
Source Code ist nicht statisch, sondern dynamisch. Im Laufe der Zeit ändern sich die Anforderungen und damit muss auch eine Software angepasst werden. Bei solchen Anpassungen müssen Entwickler stets darauf bedacht sein, dass ihre Änderungen keine unerwarteten Nebeneffekte in entfernten Ecken des Programm erzeugen. Wie sie neue Anforderungen problemlos und mit Zuversicht umsetzen können, lesen sie hier.

Die Grundlage allen Testings

In einem früheren Artikel ging es bereits um die Gründe für ein ausführliches Testen. Hier sollen nun die sogenannten Unit Tests genauer unter die Lupe genommen werden. Zu den Unit Tests gehören alle Tests, die einzelne Funktionalitäten testen und dabei unabhängig von anderen Teilen des Codes bleiben. Alle Codebeispiele sind in Typescript geschrieben. Dadurch können Typfehler bereits beim Programmieren vermieden werden, da die Funktionsargumente und der Rückgabewert einen eindeutigen Typ zugewiesen bekommen.

Ablauf eines Unit Tests

Ein typischer Unit Test kann in drei Phasen unterteilt werden. Zuerst wird der Teil des Produktiv-Codes initialisiert (Arrange), der getestet werden soll. Dieser wird "system under test" oder kurz "SUT" genannt. Hierfür soll die folgende Funktion dienen, die zwei Zahlen addiert und das Ergebnis zurückgibt. Zudem können in der Arrange-Phase Vorbereitungen für den eigentlichen Test getroffen werden, wie zum Beispiel das Generieren von Testinputs oder erwarteten Outputs.

//Produktiv-Code
function add(x: number, y: number): number {
  return x + y;
}

Da es sich bei unserem SUT um eine sehr simple Funktion handelt, ist eine Initialisierung nicht nötig. Die Funktion wird einfach in der zweiten Phase, der eigentlichen Aktion verwendet (Act). Hier wird genau die Aktion durchgeführt, die getestet werden soll. Zum Abschluss wird überprüft, ob das Ergebnis der Funktion dem gewünschten Ergebnis entspricht. Wir stellen also eine Behauptung (Assertion) auf und prüfen, ob diese zutrifft. Daraus ergibt sich, ob der Test fehlschlägt oder durchläuft. Am folgenden Beispiel sollen die drei Phasen (Arrange, Act, Assert, kurz "AAA") einmal verdeutlicht werden.

//Test-Code
import { add } from './add'; // Funktion die getestet werden soll importieren

// Wir beschreiben, welches Module oder welche Komponente gerade getestet wird
describe('Add', () => {
  // Jeder Testcase prüft eine funktionalität.
  it('should return 4 when adding 2 and 2', () => {
    //Arrange
    //bleibt leer, weil Testfall sehr simpel ist

    //Act
    const result = add(2, 2);

    //Assert
    expect(result).toBe(4);
  });
});

Führt man diesen Test nun mit einer entsprechenden Test-Umgebung aus, bei JavaScript/TypeScript empfiehlt sich Jest, sollte dieser fehlerfrei durchlaufen. Damit wäre der erste Test erfolgreich geschrieben.

Komplexere Funktionen

Bei der Add-Funktion sind nicht viele Testfälle nötig, da diese sehr simpel ist. Jedoch ist das nicht immer der Fall. Die folgende Funktion verarbeitet zwei Zahlen je nachdem welche Verarbeitungsmethode vom Nutzer ausgewählt wird:

//Produktiv-Code
type  OperationType  =  "add"  |  "mult"  |  "div" | "mod";
function  processNumbers(x: number, y: number, operation: OperationType): number {
  switch (operation) {
    case  "add":
      return  x  +  y;
    case  "mult":
      return  x  *  y;
    case  "div":
      return  x  /  y;
    case  "mod":
     return  x  %  y;

  return  0;
}

Der Rückgabewert der Funktion ist davon abhängig, welcher Operationstyp verwendet wird. Daher sollte für jede einzelne Operation ein Test geschrieben werden.

//Test-Code
import { processNumbers } from './processNumbers'; //Funktion importieren

describe('processNumbers', () => {
  it('should return 15 for add', () => {
    const result = processNumbers(10, 5, 'add');
    expect(result).toBe(15);
  });
  it('should return 50 for mult', () => {
    const result = processNumbers(10, 5, 'mult');
    expect(result).toBe(50);
  });
  it('should return 2 for div', () => {
    const result = processNumbers(10, 5, 'div');
    expect(result).toBe(2);
  });
  it('should return 0 for mod', () => {
    const result = processNumbers(10, 5, 'mod');
    expect(result).toBe(0);
  });
});

Durch diese Tests wird jede Zeile der Funktion überprüft und ist somit vollständig einsatzbereit. Doch was macht einen guten Test eigentlich aus?

Eigenschaften eines Unit Tests

Unit Tests sollten eine Reihe von Eigenschaften haben. Darunter zählen unter anderem:

  • simpel: Da Unit Tests die Grundlage bilden, sollten sehr viele davon geschrieben werden. Dementsprechend sollte der Aufwand für einen einzelnen Test sehr gering sein.
  • lesbar: Es sollte deutlich erkennbar sein, welcher Bereich getestet wird. Dadurch kann bei einem Fehler schnell das Problem identifiziert werden.
  • zuverlässig: Tests sollten nur fehlschlagen, wenn es einen Bug im Code gibt. Daher sollten Negationen bei den Behauptungen vermieden werden und die Tests sollten unabhängig von äußeren Einflüssen (Systemzeit oder ähnliches) funktionieren.
  • schnell: Unit Tests werden oft und in Massen durchgeführt, daher sollten sie keine teuren Funktionen enthalten.
  • kein Integration Test: Unit Tests sollen einzelne Funktionen testen und das unabhängig von äußeren Einflüssen. Diese müssen zur Not simuliert (mocking) werden. In Unit Tests sind keine Datenbank- oder Netzwerkzugriffe zu finden.

Unit Tests geschafft und jetzt?

Nur weil die einzelnen Funktionen funktionieren, heißt das nicht, dass das ganze Programm fehlerfrei ist. Das richtige Zusammenspiel mehrerer Komponenten ist der nächste Teil, der gründlich untersucht werden muss. Hierfür gibt es die Integration Tests, die im nächsten Artikel dieser Testing-Reihe genauer behandelt werden.

Nutzen Sie bisher Unit Tests oder Tests allgemein? Erzählen sie uns gern ihr Feedback oder stellen sie uns ihre Fragen an launch@esveo.com.

Unit Tests

August 2019

von Paul Friedemann

#Software #Development #Testing

Source Code ist nicht statisch, sondern dynamisch. Im Laufe der Zeit ändern sich die Anforderungen und damit muss auch eine Software angepasst werden. Bei solchen Anpassungen müssen Entwickler stets darauf bedacht sein, dass ihre Änderungen keine unerwarteten Nebeneffekte in entfernten Ecken des Programm erzeugen. Wie sie neue Anforderungen problemlos und mit Zuversicht umsetzen können, lesen sie hier.

Die Grundlage allen Testings

In einem früheren Artikel ging es bereits um die Gründe für ein ausführliches Testen. Hier sollen nun die sogenannten Unit Tests genauer unter die Lupe genommen werden. Zu den Unit Tests gehören alle Tests, die einzelne Funktionalitäten testen und dabei unabhängig von anderen Teilen des Codes bleiben. Alle Codebeispiele sind in Typescript geschrieben. Dadurch können Typfehler bereits beim Programmieren vermieden werden, da die Funktionsargumente und der Rückgabewert einen eindeutigen Typ zugewiesen bekommen.

Ablauf eines Unit Tests

Ein typischer Unit Test kann in drei Phasen unterteilt werden. Zuerst wird der Teil des Produktiv-Codes initialisiert (Arrange), der getestet werden soll. Dieser wird "system under test" oder kurz "SUT" genannt. Hierfür soll die folgende Funktion dienen, die zwei Zahlen addiert und das Ergebnis zurückgibt. Zudem können in der Arrange-Phase Vorbereitungen für den eigentlichen Test getroffen werden, wie zum Beispiel das Generieren von Testinputs oder erwarteten Outputs.

//Produktiv-Code
function add(x: number, y: number): number {
  return x + y;
}

Da es sich bei unserem SUT um eine sehr simple Funktion handelt, ist eine Initialisierung nicht nötig. Die Funktion wird einfach in der zweiten Phase, der eigentlichen Aktion verwendet (Act). Hier wird genau die Aktion durchgeführt, die getestet werden soll. Zum Abschluss wird überprüft, ob das Ergebnis der Funktion dem gewünschten Ergebnis entspricht. Wir stellen also eine Behauptung (Assertion) auf und prüfen, ob diese zutrifft. Daraus ergibt sich, ob der Test fehlschlägt oder durchläuft. Am folgenden Beispiel sollen die drei Phasen (Arrange, Act, Assert, kurz "AAA") einmal verdeutlicht werden.

//Test-Code
import { add } from './add'; // Funktion die getestet werden soll importieren

// Wir beschreiben, welches Module oder welche Komponente gerade getestet wird
describe('Add', () => {
  // Jeder Testcase prüft eine funktionalität.
  it('should return 4 when adding 2 and 2', () => {
    //Arrange
    //bleibt leer, weil Testfall sehr simpel ist

    //Act
    const result = add(2, 2);

    //Assert
    expect(result).toBe(4);
  });
});

Führt man diesen Test nun mit einer entsprechenden Test-Umgebung aus, bei JavaScript/TypeScript empfiehlt sich Jest, sollte dieser fehlerfrei durchlaufen. Damit wäre der erste Test erfolgreich geschrieben.

Komplexere Funktionen

Bei der Add-Funktion sind nicht viele Testfälle nötig, da diese sehr simpel ist. Jedoch ist das nicht immer der Fall. Die folgende Funktion verarbeitet zwei Zahlen je nachdem welche Verarbeitungsmethode vom Nutzer ausgewählt wird:

//Produktiv-Code
type  OperationType  =  "add"  |  "mult"  |  "div" | "mod";
function  processNumbers(x: number, y: number, operation: OperationType): number {
  switch (operation) {
    case  "add":
      return  x  +  y;
    case  "mult":
      return  x  *  y;
    case  "div":
      return  x  /  y;
    case  "mod":
     return  x  %  y;

  return  0;
}

Der Rückgabewert der Funktion ist davon abhängig, welcher Operationstyp verwendet wird. Daher sollte für jede einzelne Operation ein Test geschrieben werden.

//Test-Code
import { processNumbers } from './processNumbers'; //Funktion importieren

describe('processNumbers', () => {
  it('should return 15 for add', () => {
    const result = processNumbers(10, 5, 'add');
    expect(result).toBe(15);
  });
  it('should return 50 for mult', () => {
    const result = processNumbers(10, 5, 'mult');
    expect(result).toBe(50);
  });
  it('should return 2 for div', () => {
    const result = processNumbers(10, 5, 'div');
    expect(result).toBe(2);
  });
  it('should return 0 for mod', () => {
    const result = processNumbers(10, 5, 'mod');
    expect(result).toBe(0);
  });
});

Durch diese Tests wird jede Zeile der Funktion überprüft und ist somit vollständig einsatzbereit. Doch was macht einen guten Test eigentlich aus?

Eigenschaften eines Unit Tests

Unit Tests sollten eine Reihe von Eigenschaften haben. Darunter zählen unter anderem:

  • simpel: Da Unit Tests die Grundlage bilden, sollten sehr viele davon geschrieben werden. Dementsprechend sollte der Aufwand für einen einzelnen Test sehr gering sein.
  • lesbar: Es sollte deutlich erkennbar sein, welcher Bereich getestet wird. Dadurch kann bei einem Fehler schnell das Problem identifiziert werden.
  • zuverlässig: Tests sollten nur fehlschlagen, wenn es einen Bug im Code gibt. Daher sollten Negationen bei den Behauptungen vermieden werden und die Tests sollten unabhängig von äußeren Einflüssen (Systemzeit oder ähnliches) funktionieren.
  • schnell: Unit Tests werden oft und in Massen durchgeführt, daher sollten sie keine teuren Funktionen enthalten.
  • kein Integration Test: Unit Tests sollen einzelne Funktionen testen und das unabhängig von äußeren Einflüssen. Diese müssen zur Not simuliert (mocking) werden. In Unit Tests sind keine Datenbank- oder Netzwerkzugriffe zu finden.

Unit Tests geschafft und jetzt?

Nur weil die einzelnen Funktionen funktionieren, heißt das nicht, dass das ganze Programm fehlerfrei ist. Das richtige Zusammenspiel mehrerer Komponenten ist der nächste Teil, der gründlich untersucht werden muss. Hierfür gibt es die Integration Tests, die im nächsten Artikel dieser Testing-Reihe genauer behandelt werden.

Nutzen Sie bisher Unit Tests oder Tests allgemein? Erzählen sie uns gern ihr Feedback oder stellen sie uns ihre Fragen an launch@esveo.com.

Bevorzugte Kontaktaufnahme

esveo wird alle hier bereitgestellten Informationen ausschließlich in Übereinstimmung mit der Datenschutzerklärung verwenden