2. Einführung
Was sind Unit Tests?
http://xunitpatterns.com/Goals of Test Automation.html
http://xunitpatterns.com/Test Automation Framework.html
3. Einführung
● Teile der Implementierung (Units) isoliert ausführen
● Ihre Ausführung ist wiederholt, automatisiert möglich
● Spezifizieren und verifizieren erwartetes Input-Output-Verhalten
dieser Units (sind dadurch ein Stück weit Dokumentation (erklärt
allerdings nur das was, nicht das warum)
● Vertrauen in die Richtigkeit der Implementierung erhöhen (Tests
können nur die Existenz von Fehlern belegen, nicht aber deren
Fehlen) und ermöglichen damit angstfreies Refactoring und
Weiterentwicklung ermöglichen (Sicherheitsnetz)
● Ergänzen statische Code-Analyse-Tools und Style-Checker (Linter)
Was sind Unit Tests?
http://tryqa.com/what-are-the-principles-of-testing/
5. Einführung
● Ansatz Test-first / Test-early
● Bessere Modularisierung durch Fokus auf Testbarkeit des Codes
● Sich vorher Gedanken über das gewünschte (auch verkettete)
Eingabe-Ausgabe-Verhalten machen (z.B. Ausgabe der ersten Unit
ist Eingabe der zweiten Unit), gleiches gilt für Ausgangszustände
die durch die Eingaben in Endzustände transformiert werden
● Minimalismus: „Write only code that make the test green“
● Welche Szenarien, Sonderfälle gibt es? Für jede wird ein eigener
Test geschrieben, Äquivalenzklassenbildung mit jeweils einen
Vertreter aus jeder Klasse als Input in einem Test verwendet wird
Test-Driven-Design (TDD)
6. Einführung
● Minimal: Jeweils nur einen Aspekt testen und verifizieren, dadurch
leichte Verständlichkeit, nur eine Assertion pro Test
● Isoliert: Keine Abhängigkeiten zu anderen Tests, keine Annahmen
über die Ausführungsreihenfolge
● Deterministisch: Gleiches Testresultat auch bei mehrfachen
Ausführen, also keine Abhängigkeit zu Umgebung / Last / Zeit
● Erwartbar: Vereinbaren und Einhalten von (Namens-)Konventionen
beim Schreiben von Tests, dadurch bessere Verständlichkeit von
Tests, vor allem wenn Test fehlschlägt, soll (mögliche) Ursache
und Quelle des Fehlers ersichtlich sein
Test Prinzipien
https://esj.com/articles/2012/09/24/better-unit-testing.aspx
7. Einführung
● Einfach: Kein Overengineering von Test, Code-Duplikation bei Tests
kann ok sein, Tests sollten nicht so komplex sein, dass sie selbst
eigentlich wieder Tests benötigen würden
● Black-Box: Keine/wenige Annahmen über die Implementierung
treffen - Eingabe-Ausgabe-Verhalten testen, nicht die konkrete
Implementierung (= Black-Box- vs. White-Box-Testing)
● Angemessen: Nicht alles muss getestet werden, Risiko-vs-
Aufwandabschätzung (auch Aufwand für Pflege der Tests)
● Schnell: Keine lange Ausführungszeit, damit oft ausgeführt,
sofortiges Feedback an Entwickler nach jeder Änderung am Code
Test Prinzipien (2)
https://github.com/ghsukumar/SFDC_Best_Practices/wiki/
F.I.R.S.T-Principles-of-Unit-Testing
8. Einführung
Sind Unit Tests ausreichend?
Quelle: https://giphy.com/gifs/business-productivity-punctures-WO74HAtUC9I40/media
10. Javascript
● Testdefinition: Mocha, Jasmine, Jest, QUnit
● Lesbarkeit: Chai
● Isolation: Sinon, Rewire
● Verbesserung der Testausführung: Karma, Grunt, VSCode-Plugins
● Finden fehlender Testabdeckung: Istanbul, js-mutation-testing, grunt-
mutation-testing
● Spezielle test driver: Selenium (Out-of-scope)
Frameworks
11. Testdefinition
Einfacher Test mit Mocha
https://mochajs.org/
var assert = require('assert');
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal([1,2,3].indexOf(4), -1);
});
});
});
Import
Gruppieren
Test
Ausgangs-zustand
ErwarteteAusgabe
Impl
Aufruf
mit
Eingabe
Zusicherung über erwartete Eingabe und tatsächliche Ausgabe
19. Testen in Isolation
● Wann einsetzen?: Zum Testen von Funktionen, die Seiteneffekte
verursachen, d.h. die während der Ausführung weitere
Funktionen aufruft, die wir aber nicht (weil externe Bibliothek)
oder separat testen wollen (da Teil einer anderen Unit)
● Spy: verifizieren, ob, wie oft und mit welchen Argumenten eine
abhängige Funktion während der Ausführung aufgerufen wurde,
die original Funktion wird dabei nicht ausgetauscht
● Stub: abhängige Funktion wird durch einen Dummy ausgetauscht,
um z.B. den Aufruf der Datenbank im Test zu verhindern
● Mock: wie Stub, kann aber fixierte Eingaben-Ausgaben festlegen
Spys, Stubs, Mocks
https://semaphoreci.com/community/tutorials/best
-practices-for-spies-stubs-and-mocks-in-sinon-js
20. Testen in Isolation
Spys, Stubs, Fakes, Mocks (2)
http://xunitpatterns.com/Mocks, Fakes, Stubs and Dummies.html
21. Testen in Isolation
const sinon = require('sinon');
it('should call save once', function() {
const saveSpy = sinon.spy(Database, 'save');
funcToTest(saveSpy)
saveSpy.restore();
sinon.assert.calledOnce(saveSpy);
const call = saveSpy.getCall(0);
expect(call.args[0]).to.equal(expectedUser);
});
Sinon Spy
https://sinonjs.org/
Import
Spy at property save
of object Database
Pass spy as parameter
Remove spy
First call to save
First argumentof that first call
https://sinonjs.org/releases/v7.2.2/spies/
22. Testen in Isolation
const sinon = require('sinon');
it('should call save once', function() {
const saveStub = sinon.stub(Database, 'save');
funcToTest(saveSpy)
saveStub.restore();
sinon.assert.calledOnce(saveStub);
const saveStub2 = sinon.stub(Database, 'save');
saveStub2.throws(new Error('oops'));
funcToTest(saveStub2)
});
Sinon Stub
https://sinonjs.org/releases/v7.2.2/stubs/
Import
Stub property save of
object Database
Pass stub as parameter
Remove stub
Use stub to issueerror when called and
assert behavior
23. Testen in Isolation
const sinon = require('sinon');
it('should call save once', function() {
const dbMock = sinon.mock(Database);
dbMock.expects('save').once().withArgs(expectedUser);
funcToTest(dbMock)
dbMock.verify();
dbMock.restore();
});
Sinon Mock
https://sinonjs.org/releases/v7.2.2/mocks/
Import
Mock object Database
Pass mock as parameter
Remove mock
Mock call to save
Verify expected call
24. Testen in Isolation
const sinon = require('sinon');
it('should fake it', function() {
const fake = sinon.fake.returns('apple pie');
expect(fake()).to.equal('apple pie');
expect(fake.callCount).to.equal(1);
var fake2 = sinon.fake.returns('42');
sinon.replace(console, 'log', fake2);
console.log('apple pie'); // prints ‚42‘
sinon.restore();
});
Sinon Fake
https://sinonjs.org/releases/v7.2.2/fakes/
Import
Create fake
Remove replacement
with fake
Call to fake returns stringFake grabs its call count
Replace existing property with fake
26. Testabdeckung
● instrumentiert ES5 und ES2015+ JavaScript code mit
Zeilennummeriung, so dass nachvollzogen werden kann, wie gut
die bestehenden Tests die
Implementierung abdecken
Istanbul
27. Testabdeckung
● Code coverage != Assertion coverage: kann also Tests schreiben,
die den gesamten Code ausführen, ohne die produzierten
(Zwischen-)Zustände und Ausgabe zu prüfen
● Dateien, für die gar keine Tests existieren, wirken sich nicht
negativ auf Code coverage aus (Javascript feature)
● Mutation testing injiziert Fehler in die
Codebasis (z.B. durch Kippen von
Boolean-Ausdrücken) und prüft, ob
diese Änderungen durch bestehende
Tests gefunden werden, die dann
fehlschlagen sollten
Mutation testing
https://en.wikipedia.org/wiki/Mutation_testing
https://www.researchgate.net/figure/Mutation-testing-procedure_fig1_279174620
28. Testabdeckung
● Code coverage != Assertion coverage: kann also Tests schreiben,
die den gesamten Code ausführen, ohne die produzierten
(Zwischen-)Zustände und Ausgabe zu prüfen
● Dateien, für die gar keine Tests existieren, wirken sich nicht
negativ auf Code coverage aus (Javascript feature)
● Mutation testing injiziert Fehler in die
Codebasis (z.B. durch Kippen von
Boolean-Ausdrücken) und prüft, ob
diese Änderungen durch bestehende
Tests gefunden werden, die dann
fehlschlagen sollten
Mutation testing
https://en.wikipedia.org/wiki/Mutation_testing
https://www.researchgate.net/figure/Mutation-testing-procedure_fig1_279174620
29. Erweiterte Testausführung
● Tests in a loop: Tests sofort bei jedem Speichern einer Datei im
Projekt ausführen (in Javascript ist das initialen Laden aller
Testdateien ziemlich langsam)
● Testen in verschiedenen Browsern / Plattformen
● Vorbereitende Aktionen ausführen, bevor die Tests ausgeführt
werden (z.B. Browser starten, Telegram Anwendung starten)
● https://blog.mayflower.de/4333-Karma-Testrunner-Einfuehrung.ht
ml
● https://www.npmjs.com/search?q=keywords:karma-plugin
Karma
https://github.com/karma-runner/karma
https://karma-runner.github.io/latest/index.html
30. Integration in VSCode
Plugins
ID Name
sourcegraph.javascript-typescript JavaScript and TypeScript IntelliSense
leizongmin.node-module-intellisense Node.js Modules Intellisense
xabikos.javascriptsnippets JavaScript (ES6) code snippets
christian-kohler.npm-intellisense npm Intellisense
christian-kohler.path-intellisense Path Intellisense
IntelliSense / Code snippets
Sonstiges
ID Name
dai-shi.vscode-es-beautifier es-beautifier
fabiospampinato.vscode-git-history Git File History
https://marketplace.visualstudio.com/search?term=javascript&t
arget=VSCode&category=All categories&sortBy=Relevance
31. Integration in VSCode
Plugins
ID Name
spoonscen.es6-mocha-snippets ES6 Mocha Snippets
hbenl.vscode-test-explorer Test Explorer UI
hbenl.vscode-mocha-test-adapter Mocha Test Explorer
rintoj.chai-spec-generator Test Spec Generator
markis.code-coverage Code Coverage
Testen
ID Name
dbaeumer.vscode-eslint ESLint
chenxsan.vscode-standardjs StandardJS - JavaScript Standard Style
Lint
33. Zusammenfassung
● Tests einfach halten
● Black Box Tests bevorzugen gegenüber White-Box-Tests
● Vermeiden von Interacting Tests durch Zurücksetzen von
Zuständen und Mocks in before, beforeEach, afterEach und/oder
after mit sinon.resetAll oder reset am gestubten/gemockten
Objekt
● Tests im automatisierten Build (z.B. über TravisCI) mitlaufen
lassen, nur Release bauen und releasen, wenn alle Tests grün
sind
Best practices
http://tryqa.com/what-are-the-principles-of-testing/
34. Zusammenfassung
● Äquivalenz-Klassen bilden, z.B. jeweils Stellvertreter-Eingaben
finden, bei der z.B. die Teilbedingungen von if-Statements im
getesteten Code in allen Kombinationen durchlaufen werden
(Zeilen-, Zweig-, Pfad-Abdeckung)
● immer mit Kopien bei Expectations und Eingaben arbeiten, damit
man nicht versehentlich die durch die Implementierung
veränderte Eingabe als erwarteten Vergleichswert nutzt
● Spread-Operator nutzen, um neue Varianten von Testdaten zu
erzeugen
● Komplexe Testdaten aus JSON-Datei in Tests importieren
Best practices (2)
https://developer.mozilla.org/de/docs/Web/Java
Script/Reference/Operators/Spread_operator