Javascript Unit
Testing
PRESENTED BY
Joerg Reichert
Licensed under cc-by v3.0 (any
jurisdiction)
Einführung
Was sind Unit Tests?
http://xunitpatterns.com/Goals of Test Automation.html
http://xunitpatterns.com/Test Automation Framework.html
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/
Einführung
https://en.wikipedia.org/wiki/Test-driven_development
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)
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
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
Einführung
Sind Unit Tests ausreichend?
Quelle: https://giphy.com/gifs/business-productivity-punctures-WO74HAtUC9I40/media
Einführung
Unit Test
Testpyramide
Module Tests
Integration Tests
Akzeptanz Tests
Eigene Implementierung
Externe Libraries / Frameworks
Infrastruktur (Datenbank, Server, Browser)
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
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
Testdefinition
Hooks
https://mochajs.org/
describe('hooks', function() {
before(function() { /* runs before all tests in this block */ });
after(function() { /* runs after all tests in this block */ });
beforeEach(function() { /* runs before each test in this block */ });
afterEach(function() { /* runs after each test in this block */ });
// test cases
});
Testdefinition
Asynchroner Test mit Mocha
https://mochajs.org/
...
it('respond with matching records', function(done) {
db.find({type: 'User'}, function(err, res) {
if (err) return done(err);
assert.equal(res.length(), 3);
done();
});
});
done
Callback
Testdefinition
Spezielles
https://mochajs.org/
● Testausführung überspringen: describe.skip(...) / it.skip(...)
● Nur diesen Test ausführen: describe.only(...) / it.only(…) (seit Mocha
3.0 auch mehrfach verwendbar)
● Noch nicht implementierter Test: it(‘Beschreibung‘); (ohne func also)
● Async-await: it('responds with matching records', async function() {
const users = await db.find({ type: 'User' }); … });
● Bei asynchronen Tests statt done callback direkt ein Promise
zurückgeben (z.B. über chai as promise, später erklärt)
Test-Zusicherungen
Chai
https://www.chaijs.com/
● 3 Stile zur Auswahl, um Erwartungen auszudrücken
● Lesbarkeit durch Fluent API und Verkettungs-Möglichkeit
Test-Zusicherungen
Chai Beispiele
https://www.chaijs.com/api/bdd/
● expect({a: 1}).to.deep.equal({a: 1});
aber: expect({a: 1}).to.not.equal({a: 1})
alternativ: expect({a: 1}).to.eql({a: 1}).but.not.equal({a: 1});
● expect([{a: 1}]).to.deep.include({a: 1});
aber: expect([{a: 1}]).to.not.include({a: 1});
● expect({x: {a: 1}}).to.deep.include({x: {a: 1}});
aber: expect({x: {a: 1}}).to.not.include({x: {a: 1}});
● expect([{a: 1}]).to.have.deep.members([{a: 1}]);
aber: expect([{a: 1}]).to.not.have.members([{a: 1}]);
Test-Zusicherungen
Chai Beispiele (2)
https://www.chaijs.com/api/bdd/
● expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd');
expect({a: 1, b: 2}).to.have.all.keys('a', 'b');
● expect({a: 3}).to.have.property('a', 3);
● expect('foo').to.be.a('string'); // Typvergleich
● expect('foobar').to.include('foo') / expect([1, 2, 3]).to.include(2)
● expect(true).to.be.true; / expect(false).to.be.false; /
expect(null).to.be.null; / expect([]).to.be.empty;
● expect('foo').to.have.lengthOf(3);
● expect('foobar').to.match(/^foo/);
● expect(badFn).to.throw(err, 'salmon')
● expect.fail("custom error message");
Test-Zusicherungen
Behavior driven design (BDD)
https://en.wikipedia.org/wiki/Behavior-driven_development
Story vs. Specification
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
Testen in Isolation
Spys, Stubs, Fakes, Mocks (2)
http://xunitpatterns.com/Mocks, Fakes, Stubs and Dummies.html
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/
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
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
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
Testen in Isolation
const fs = require(‘fs‘);
...
„Rewire“ module dependencies in tests
https://github.com/jhnns/rewire
const rewire = require("rewire");
const myModule = rewire("../path/to/myModule.js");
const fsMock = ...
myModule.__set__("fs", fsMock);
myModule.js
myModuleTest.js
Testabdeckung
● instrumentiert ES5 und ES2015+ JavaScript code mit
Zeilennummeriung, so dass nachvollzogen werden kann, wie gut
die bestehenden Tests die
Implementierung abdecken
Istanbul
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
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
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
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
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
Integration in VSCode
https://github.com/eisc/fancy_LVB_Telegram_Bot
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/
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

Unit testing mit Javascript

  • 1.
    Javascript Unit Testing PRESENTED BY JoergReichert Licensed under cc-by v3.0 (any jurisdiction)
  • 2.
    Einführung Was sind UnitTests? http://xunitpatterns.com/Goals of Test Automation.html http://xunitpatterns.com/Test Automation Framework.html
  • 3.
    Einführung ● Teile derImplementierung (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/
  • 4.
  • 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: Jeweilsnur 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: KeinOverengineering 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 Testsausreichend? Quelle: https://giphy.com/gifs/business-productivity-punctures-WO74HAtUC9I40/media
  • 9.
    Einführung Unit Test Testpyramide Module Tests IntegrationTests Akzeptanz Tests Eigene Implementierung Externe Libraries / Frameworks Infrastruktur (Datenbank, Server, Browser)
  • 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 mitMocha 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
  • 12.
    Testdefinition Hooks https://mochajs.org/ describe('hooks', function() { before(function(){ /* runs before all tests in this block */ }); after(function() { /* runs after all tests in this block */ }); beforeEach(function() { /* runs before each test in this block */ }); afterEach(function() { /* runs after each test in this block */ }); // test cases });
  • 13.
    Testdefinition Asynchroner Test mitMocha https://mochajs.org/ ... it('respond with matching records', function(done) { db.find({type: 'User'}, function(err, res) { if (err) return done(err); assert.equal(res.length(), 3); done(); }); }); done Callback
  • 14.
    Testdefinition Spezielles https://mochajs.org/ ● Testausführung überspringen:describe.skip(...) / it.skip(...) ● Nur diesen Test ausführen: describe.only(...) / it.only(…) (seit Mocha 3.0 auch mehrfach verwendbar) ● Noch nicht implementierter Test: it(‘Beschreibung‘); (ohne func also) ● Async-await: it('responds with matching records', async function() { const users = await db.find({ type: 'User' }); … }); ● Bei asynchronen Tests statt done callback direkt ein Promise zurückgeben (z.B. über chai as promise, später erklärt)
  • 15.
    Test-Zusicherungen Chai https://www.chaijs.com/ ● 3 Stilezur Auswahl, um Erwartungen auszudrücken ● Lesbarkeit durch Fluent API und Verkettungs-Möglichkeit
  • 16.
    Test-Zusicherungen Chai Beispiele https://www.chaijs.com/api/bdd/ ● expect({a:1}).to.deep.equal({a: 1}); aber: expect({a: 1}).to.not.equal({a: 1}) alternativ: expect({a: 1}).to.eql({a: 1}).but.not.equal({a: 1}); ● expect([{a: 1}]).to.deep.include({a: 1}); aber: expect([{a: 1}]).to.not.include({a: 1}); ● expect({x: {a: 1}}).to.deep.include({x: {a: 1}}); aber: expect({x: {a: 1}}).to.not.include({x: {a: 1}}); ● expect([{a: 1}]).to.have.deep.members([{a: 1}]); aber: expect([{a: 1}]).to.not.have.members([{a: 1}]);
  • 17.
    Test-Zusicherungen Chai Beispiele (2) https://www.chaijs.com/api/bdd/ ●expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd'); expect({a: 1, b: 2}).to.have.all.keys('a', 'b'); ● expect({a: 3}).to.have.property('a', 3); ● expect('foo').to.be.a('string'); // Typvergleich ● expect('foobar').to.include('foo') / expect([1, 2, 3]).to.include(2) ● expect(true).to.be.true; / expect(false).to.be.false; / expect(null).to.be.null; / expect([]).to.be.empty; ● expect('foo').to.have.lengthOf(3); ● expect('foobar').to.match(/^foo/); ● expect(badFn).to.throw(err, 'salmon') ● expect.fail("custom error message");
  • 18.
    Test-Zusicherungen Behavior driven design(BDD) https://en.wikipedia.org/wiki/Behavior-driven_development Story vs. Specification
  • 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 constsinon = 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 constsinon = 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 constsinon = 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 constsinon = 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
  • 25.
    Testen in Isolation constfs = require(‘fs‘); ... „Rewire“ module dependencies in tests https://github.com/jhnns/rewire const rewire = require("rewire"); const myModule = rewire("../path/to/myModule.js"); const fsMock = ... myModule.__set__("fs", fsMock); myModule.js myModuleTest.js
  • 26.
    Testabdeckung ● instrumentiert ES5und 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 ● Testsin 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 IDName 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 IDName 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
  • 32.
  • 33.
    Zusammenfassung ● Tests einfachhalten ● 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