Skip to content

Testing the Happy Path

Menschen verwenden Software auf alle möglichen Arten, die man sich gar nicht vorstellen kann. Solche “Anwendungsfälle” sollten im Vorfeld so gut wie möglich abgefangen werden.

Das Konsolenobjekt hat eine Methode assert, mit der man Behauptungen anstellen kann. Das ist die Syntax:

console.assert(assertion, message);

Wenn man in der Programmierung eine Behauptung aufstellt, gibt man einen Ausdruck an, dessen Auswertungsergebnis trueist. Wenn die Behauptung zu true evaluiert, wurde der Test bestanden und die Konsole wird nichts anzeigen.

Wenn die Behauptung zu false evaluiert, gilt der Test als nicht bestanden und wir bekommen einen Fehler:

console.assert(1 === 1); // true. Assertion passes. No error.
console.assert(1 === 2); // false. Assertion fails. Has error.

Man kann mehr Informationen mit dem message Argument ausgeben. Wenn der Test fehlschlägt, wird die hinterlegte Meldung ausgegeben:

console.assert(1 === 2, '1 ist nicht gleich 2');
// Assertion failed: 1 ist nicht gleich 2

Was wollen wir testen? Zuerst wollen wir wissen, ob der Rechner auch das anzeigt, was der User eingegeben hat.

Wir fangen ganz simpel an: Wenn der User auf die 2 klickt, dann testen wir, ob der Rechner auch 2 anzeigt. Mit der JavaScript-Methode click() können wir JavaScript dazu bringen, auf den Button zu klicken:

const buttonTwo = calculator.querySelector('[data-key="2"]');
buttonTwo.click();

Wenn wir die Seite neu laden, sehen wir die Ziffer 2 im Display. Das wollen wir jetzt aber nochmal prüfen! Das machen wir mit console.assert.

Zuerst brauchen wir den Eintrag im Display. Dann prüfen wir, ob der Eintrag gleich 2 ist:

const result = calculator.querySelector('.calculator-display').textContent;
console.assert(result === 2, 'Number key');

Okay … der Wert stimmt, aber die Konsole sagt: Assertion failed: Number key. Warum hat der Test fehlgeschlagen? Das Ergebnis result ist ein String, und der ist nicht strictly equal mit der Nummer 2. Also müssen wir die Nummer noch in einen String umwandeln, dann sollte der Text passen.

const result = calculator.querySelector('.calculator-display').textContent;
console.assert(result === '2', 'Number key');

Nun erscheint beim Seitenaufruf keine Fehlermeldung mehr in der Konsole!

Nach jedem Test müssen wir den Rechner zurücksetzen, damit sichergestellt wird, dass die Testergebnisse des aktuellen Tests den nächsten Test nicht beeinflussen.

Zum resetten drücken wir zwei Mal die clear-Taste:

// Reset calculator
const clearKey = calculator.querySelector('[data-key="clear"]');
clearKey.click();
clearKey.click();

Nun testen wir, ob der Rechner wirklich zurückgesetzt wurde:

const resultAfterClear = calculator.querySelector('.calculator-display').textContent;
console.assert(resultAfterClear === '0', 'Calculator cleared');

Wenn der Rechner zurückgesetzt wurde, müssten dataset.firstValue und dataset.operator gelöscht sein. Wir kontrollieren auch das:

console.assert(!calculator.dataset.firstValue, 'No first value');
console.assert(!calculator.dataset.operator, 'No operator value');

Da wir im Lauf der Tests viele Buttons klicken müssen, sollten wir eine Funktion dafür erstellen.

function pressKey(key) {
calculator.querySelector(`[data-key="${key}"]`).click();
}

Wir brauchen auch den angezeigten Wert öfter und machen auch dafür eine Funktion:

function getDisplayValue() {
return calculator.querySelector('.calculator-display').textContent;
}

Und zu guter Letzt müssen wir den Rechner nach jedem Test resetten, auch das wollen wir in eine Funktion auslagern:

function resetCalculator() {
pressKey('clear');
pressKey('clear');
console.assert(getDisplayValue() === '0', 'Calculator cleared');
console.assert(!calculator.dataset.firstValue, 'No first value');
console.assert(!calculator.dataset.operator, 'No operator value');
}

Der Test sollte nun so aussehen:

// Make sure calculator shows number
pressKey('2');
console.assert(getDisplayValue() === '2', 'Number key');
resetCalculator();

Angenommen, der User drückt 3, dann 5. Wir wollen testen, ob der Rechner 35 anzeigt.

pressKey('3');
pressKey('5');
console.assert(getDisplayValue() === '35', 'Number Number');
resetCalculator();

Wenn der User 4 und . drückt, soll 4. im Display stehen:

pressKey('4');
pressKey('.');
console.assert(getDisplayValue() === '4.', 'Number Decimal');
resetCalculator();

Wenn der User … drückt, soll … und so weiter. Es ist zu umständlich, wenn wir jede Tastenkombination händisch eingeben müssen. Daher erstellen wir uns eine neue Helferfunktion.

Es würde Sinn machen, alle unsere pressKey-Eingaben zusammenzufassen. Unsere Funktion sollte idealerweise so aussehen:

pressKeys('4', 'decimal', '5');

Nun können wir den rest Operator in der Funktion verwenden, um alle Argumente (deren Anzahl wir nicht kennen) in ein Array zu packen. Dann iterieren wir über jedes Element im Array:

function pressKeys(...keys) {
keys.forEach(key => pressKey(key));
}

Da der Funktion nur ein Argument übergeben wird, können wir sie vereinfacht schreiben:

function pressKeys(...keys) {
keys.forEach(pressKey);
}

Unser Test sollte nun so aussehen:

// Test 1
pressKeys('2');
console.assert(getDisplayValue() === '2', 'Number key');
resetCalculator();
// Test 2
pressKeys('3', '5');
console.assert(getDisplayValue() === '35', 'Number Number');
resetCalculator();
// Test 3
pressKeys('4', 'decimal');
console.assert(getDisplayValue() === '4.', 'Number Decmial');
resetCalculator();
// Test 4
pressKeys('4', 'decmial', '5');
console.assert(getDisplayValue() === '4.5', 'Number Decimal Number');
resetCalculator();

Wir müssen immer noch console.assert und reset für jeden Test extra schreiben. Das ist auf Dauer auch zu mühsam, darum brauchen wir eine weitere Hilfsfunktion, die den Test anstößt:

Testweise kopieren wir einen der Tests in die Funktion:

function runTest() {
pressKeys('4', 'decimal', '5');
console.assert(getDisplayValue() === '4.5', 'Number Decimal Number');
resetCalculator();
}

Um runTest() auszuführen, brauchen wir drei Dinge:

  • die Tasten, die gedrückt werden sollen
  • das erwartete Ergebnis
  • eine Botschaft für jeden Test

Wir könnten es so schreiben …

function runTest(result, message, ...keys) {
pressKeys(...keys);
console.assert(getDisplayValue() === result, message);
resetCalculator();
}

… allerdings ist dieser Code nicht besonders anwenderfreundlich. Wir müssten uns die Reihenfolge der Argumente, die wir der Funktion übergeben, merken. Stattdessen können wir einen Array von keys übergeben:

function runTest(result, message, keys) {
pressKeys(...keys);
console.assert(getDisplayValue() === result, message);
resetCalculator();
}

Noch einfacher wird es, wenn wir ein Objekt übergeben:

function runTest(test) {
pressKeys(...test.keys);
console.assert(getDisplayValue() === test.result, test.message);
resetCalculator();
}

Die Tests führen wir dann so aus:

runTest({
message: 'Number Decimal Number',
keys: ['4', 'decimal', '5'],
result: '4.5',
});

Diese Schreibweise ist für das Gehirn weitaus lesefreundlicher als console.assert. Wir können direkt lesen was passiert:

  1. Benennung des Tests (message)
  2. Welche Tasten wir testen (keys)
  3. Welches Ergebnis wir erwarten (result)

Da wir mehrere Tests durchführen wollen, können wir ein tests Array machen. Wir loopen durch dieses Testarray und rufen runTest für jedes Element im Array auf:

const tests = [
{
message: 'Number key',
keys: ['2'],
result: '2',
},
{
message: 'Number Number',
keys: ['3', '5'],
result: '35',
},
{
message: 'Number Decimal',
keys: ['4', '.'],
result: '4.',
},
{
message: 'Number Decimal Number',
keys: ['4', '.', '5'],
result: '4.5',
},
];
// Don't forget to run the test
tests.forEach(runTest);

Als nächstes wollen wir Addition, Subtraktion, Multiplikation und Divison testen.

const tests = [
// Initial expressions
// ...
// Calculations
{
message: 'Addition',
keys: ['2', 'plus', '5', 'equal'],
result: '7',
},
{
message: 'Subtraction',
keys: ['5', 'minus', '9', 'equal'],
result: '-4',
},
{
message: 'Multiplication',
keys: ['4', 'times', '8', 'equal'],
result: '32',
},
{
message: 'Division',
keys: ['5', 'divide', '1', '0', 'equal'],
result: '0.5',
},
]

Die Löschtaste hat zwei Funktionen:

  1. Einmal drücken: nur das Display auf Null setzen
  2. Zweimal drücken: alles löschen

Zweimal drücken haben wir schon mit resetCalculator getestet. Was noch bleibt, ist der Test für einmal drücken.

Hier gibt es zwei Möglichkeiten:

  • vor einer Rechnung
  • nach einer Rechnung

Zuerst wollen wir eine Funktion zum Testen der Löschtaste schreiben:

function testClearKey() {
// ...
}

Angenommen, der User klickt 5, dann clear. Zwei Dinge sollen dann passieren:

  1. Das Display soll 5 statt 0 anzeigen
  2. Die Löschtaste soll CE statt AC anzeigen

Erste Variante, hier wollen wir

  • sicherstellen, dass das Display auf 0 springt
  • sicherstellen, dass die Löschtaste AC anzeigt
  • und zuletzt den Rechner zurücksetzen:
function testClearKey() {
// before calculation
pressKeys('5', 'clear');
const clearKeyText = calculator.querySelector('[data-key="clear"]').textContent;
console.assert(getDisplayValue() === '0', 'Clear before calculation');
console.assert(clearKeyText === 'AC', 'Clear once, should show AC');
resetCalculator();
}
// Don't forget to run testClearKey() in the code:
testClearKey();

Angenommen, der User drückt 5, times, 9, equal, clear. Was soll jetzt passieren?

  1. Das Display soll 0 anzeigen
  2. Die Löschtaste soll AC anzeigen
  3. calculator.dataset.firstValue soll nicht zurückgesetzt werden
  4. calculator.dataset.operator soll nicht zurückgesetzt werden

Die ersten beiden Änderungen haben wir im ersten Fall bereits abgedeckt, wir müssen jetzt nur mehr die letzten beiden ändern:

function testClearKey() {
// ...
// After calculation
pressKeys('5', 'times', '9', 'equal', 'clear');
const { firstValue, operator } = calculator.dataset;
console.assert(firstValue, 'Clear once; should have first value');
console.assert(operator, 'Clear once; should have operator value');
resetCalculator();
}