+ Trygghet til å gjøre refaktoreringer
+
+ Ingen kompilatorstøtte (og mindre IDE-støtte)
+
+ Cross-browser utfordringer
+
+ Testene er raske å kjøre
+
+ Det har kommet gode verktøy for å teste JS
+
+ Fordi du ville gjort det hvis det var Java
+
+
+
+ Blir mindre avhengig av én person
+
+ Holder kvalitetsfokus
+
+ Kan fjerne kompeksitet
+
+ Gjenbrukbarhet
+
+ Vedlikeholdbarhet
+
+ Tester er dokumentasjon
+
+
+---
+
+# Problem: å komme i gang
+
+Hvilket rammeverk skal vi velge?
+
+Hva skal vi teste? Hvor begynner vi?
+
+Hvordan får jeg med teamet (og kunden)?
+
+---
+
+.middle.center
+
+# Løsning
+
+---
+
+.middle.center
+
+Start i det _små_.
+
+Det er greit å ikke løse alle problemene på en gang.
+
+---
+
+.middle.center
+
+Velg et rammeverk og godta at det ikke er perfekt.
+
+Vi valgte jasmine (fordi noen på teamet hadde vært borti det før og vi visste andre prosjekter benyttet det)
+
+---
+
+.middle.center
+
+Fokuser på tester foran infrastruktur.
+
+---
+
+.middle.center
+
+Vær en diktator.
+
+Ta initiativet til testing.
+
+---
+
+.middle.center
+
+Jobb med kultur.
+
+Tester _vil_ gi verdi. GSP vs Tech. Alle "kan" JavaScript.
+
+---
+
+.middle.center
+
+Stopp opp og del læring med de andre på teamet.
+
+---
+
+.middle.center
+
+Start med enhetstester for _ny_ kode.
+
+Dette er enklest å komme i gang med, og å komme i gang er viktigere enn å umiddelbart finne området hvor tester har mest verdi.
+
+---
+
+.middle.center
+
+Ikke prioriter å teste det som må "føles på".
+
+Ha gode abstraksjoner for GUI, tekst og animasjoner slik at du slipper å teste dette.
+
+---
+
+# Eksempel - enhetstest
+
+minifierSpec.js:
+
+ .javascript
+ describe("minifier", function() {
+ describe("isDevelopment", function() {
+
+
+
+
+
+
+
+
+ });
+ });
+
+
+---
+
+# Eksempel - enhetstest
+
+minifierSpec.js:
+
+ .javascript
+ describe("minifier", function() {
+ describe("isDevelopment", function() {
+ it("should return true if data-development is true", function() {
+ var minifier = new Minifier(),
+ src = '';
+
+ var isDevelopment = minifier.isDevelopment(src);
+
+ expect(isDevelopment).toBeTruthy();
+ });
+ });
+ });
+
+
+
+---
+
+# Eksempel - enhetstest
+
+minifierSpec.js:
+
+ .javascript
+ describe("minifier", function() {
+ describe("isDevelopment", function() {
+ it("should return true if data-development is true", function() {
+ var minifier = new Minifier(),
+ src = '';
+
+ var isDevelopment = minifier.isDevelopment(src);
+
+ expect(isDevelopment).toBeTruthy();
+ });
+ });
+ });
+
+Testresultat:
+
+![](img/unittest-failing.png?v1)
+
+---
+
+# Eksempel - enhetstest
+
+minifier.js:
+
+ .javascript
+ function Minifier(){
+ }
+
+ Minifier.prototype.isDevelopment = function(line) {
+ return !!line.match(/data-development="?true"?/);
+ }
+
+
+---
+
+# Eksempel - enhetstest
+
+minifier.js:
+
+ .javascript
+ function Minifier(){
+ }
+
+ Minifier.prototype.isDevelopment = function(line) {
+ return !!line.match(/data-development="?true"?/);
+ }
+
+Testresultat:
+
+![](img/unittest-pass.png?v1)
+
+---
+
+.middle.center
+
+# Du er i gang!
+... og har allerede kommet lengre enn mange andre
+
+---
+# Problem: teste legacy JS
+
+ .javascript
+ function populateDropdownWithPersons(select) {
+ $.getJSON('http://localhost:1337/persons.json', function(data) {
+ var options = [];
+
+ $.each(data, function(key, val) {
+ options.push('
+Nettverkskall
+
+Parsing av respons
+
+Manipulering av DOM
+
+Oppbygging av options/HTML
+
+
+-> Person-modell?
+
+-> Person-modell / mapper?
+
+-> I eget view?
+
+-> optionsMapper som kan mappe en liste med objekter til `
');
+ });
+ });
+
+---
+
+# Eksempel
+
+optionsMapperSpec.js:
+
+ .javascript
+ describe("optionsMapper", function() {
+ it("should convert array of objects to array of
');
+ });
+ });
+
+optionsMapper.js
+
+ .javascript
+ var BEKK = BEKK || {};
+ BEKK.optionsMapper = function (array, opts) {
+ var options = [];
+
+ for(var i = 0, length = array.length; i < length; i++) {
+ var obj = array[i];
+ options.push('
');
+ }
+
+ return options;
+ }
+
+---
+
+# Løsning
+
+Jobb med patterns og abstraksjoner
+
+* Bruk single responsibility principle
+* Del opp i filer
+
+---
+
+# Løsning
+
+Jobb med patterns og abstraksjoner
+
+* Bruk single responsibility principle
+* Del opp i filer
+
+Parprogrammer for å løse vanskelige problemer
+
+---
+
+# Løsning
+
+Jobb med patterns og abstraksjoner
+
+* Bruk single responsibility principle
+* Del opp i filer
+
+Parprogrammer for å løse vanskelige problemer
+
+Ta i bruk bibliotek som f.eks. sinon.js, som kan hjelpe med:
+
+* spy/stubs/mocks
+* Manipulering av tid (Fake timers)
+
+---
+
+# Løsning
+
+Jobb med patterns og abstraksjoner
+
+* Bruk single responsibility principle
+* Del opp i filer
+
+Parprogrammer for å løse vanskelige problemer
+
+Ta i bruk bibliotek som f.eks. sinon.js, som kan hjelpe med:
+
+* spy/stubs/mocks
+* Manipulering av tid (Fake timers)
+
+Diktatoren fortsetter i sin jobb, og:
+
+* sørger for at mest mulig testes
+* prototyper og skaffer nødvendig kunnskap for å hjelpe teamet videre
+
+---
+
+.middle.center
+
+# ... men enhetstester er ikke alltid nok
+
+---
+
+# Problem
+
+Noen sentrale aspekter vi hadde oversett:
+
+* Det skjer mye mellom AJAX-respons og visning av HTML.
+* Det skjer mye basert på "veien gjennom siden".
+* Det er mange plasser feil må håndteres (på forskjellige måter).
+
+---
+
+# Problem
+
+Noen sentrale aspekter vi hadde oversett:
+
+* Det skjer mye mellom AJAX-respons og visning av HTML.
+* Det skjer mye basert på "veien gjennom siden".
+* Det er mange plasser feil må håndteres (på forskjellige måter).
+
+I tillegg var innholdet i vår index.html:
+
+
+
+Og vi hadde nesten bare "ferdig-prosessert" data via REST.
+
+---
+
+# Løsning: Integrasjonstester
+
+Det vil si testing av hele klientside-stacken.
+
+Noen av våre krav:
+
+* Lik våre andre tester, altså ren JavaScript for å skrive testene.
+* De må kunne kjøre under bygging.
+* Tregt (og komplekst) baksystem, altså mocker vi AJAX-kall.
+
+1 time med hele teamet for å sketsje ut første test. Stopp opp og prat sammen!
+
+... men husk: de må være _tåpelig_ raske og enkle å implementere og endre.
+Vanen vi slåss mot er refresh i nettleseren!
+
+---
+
+# Hvordan en test ser ut
+
+ .javascript
+ it("should list all persons in response", function() {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ });
+
+---
+
+# Hvordan en test ser ut
+
+ .javascript
+ it("should list all persons in response", function() {
+
+ // henter en AJAX-respons
+ var response = readFixtures("responses/persons.json");
+
+
+
+
+
+
+
+
+
+
+
+
+ });
+
+---
+
+# Hvordan en test ser ut
+
+ .javascript
+ it("should list all persons in response", function() {
+
+ // henter en AJAX-respons
+ var response = readFixtures("responses/persons.json");
+
+ var view = new PersonsView({ persons: new Persons() });
+ view.render(); // viser overskrift og annen statisk info
+
+
+
+
+
+
+
+
+
+ });
+
+---
+
+# Hvordan en test ser ut
+
+ .javascript
+ it("should list all persons in response", function() {
+
+ // henter en AJAX-respons
+ var response = readFixtures("responses/persons.json");
+
+ var view = new PersonsView({ persons: new Persons() });
+ view.render(); // viser overskrift og annen statisk info
+
+
+
+ view.persons.fetch(); // trigger AJAX-kall
+
+
+
+
+
+ });
+
+---
+
+# Hvordan en test ser ut
+
+ .javascript
+ it("should list all persons in response", function() {
+
+ // henter en AJAX-respons
+ var response = readFixtures("responses/persons.json");
+
+ var view = new PersonsView({ persons: new Persons() });
+ view.render(); // viser overskrift og annen statisk info
+
+ // setter opp responsen
+ fakeResponse(response, {}, function() {
+ view.persons.fetch(); // trigger AJAX-kall
+ });
+
+
+
+
+ });
+
+---
+
+# Hvordan en test ser ut
+
+ .javascript
+ it("should list all persons in response", function() {
+
+ // henter en AJAX-respons
+ var response = readFixtures("responses/persons.json");
+
+ var view = new PersonsView({ persons: new Persons() });
+ view.render(); // viser overskrift og annen statisk info
+
+ // setter opp responsen
+ fakeResponse(response, {}, function() {
+ view.persons.fetch(); // trigger AJAX-kall
+ });
+
+ // view-abstraksjonen har html tilgjengelig
+ expect($(view.html).find(".persons li").length).toEqual(20);
+
+ });
+
+---
+
+# Hvordan en test ser ut
+
+ .javascript
+ it("should list all persons in response", function() {
+
+ // henter en AJAX-respons
+ var response = readFixtures("responses/persons.json");
+
+ var view = new PersonsView({ persons: new Persons() });
+ view.render(); // viser overskrift og annen statisk info
+
+ // setter opp responsen
+ fakeResponse(response, {}, function() {
+ view.persons.fetch(); // trigger AJAX-kall
+ });
+
+ // view-abstraksjonen har html tilgjengelig
+ expect($(view.html).find(".persons li").length).toEqual(20);
+
+ });
+
+To biblioteker i bruk:
+
+* jasmine-jquery, for lasting av fixtures
+* Sinon.js, for mocking av AJAX-kall
+
+---
+
+# Testabstraksjoner
+
+ .javascript
+ function getPersonsViewFromResponse(response, options) {
+ var view = new PersonsView({ persons: new Persons() });
+ view.render(); // viser overskrift og annen statisk info
+
+ // setter opp responsen
+ fakeResponse(response, options, function() {
+ view.persons.fetch(); // trigger AJAX-kall
+ });
+
+ return view;
+ }
+
+---
+
+# Testabstraksjoner
+
+ .javascript
+ function getPersonsViewFromResponse(response, options) {
+ var view = new PersonsView({ persons: new Persons() });
+ view.render(); // viser overskrift og annen statisk info
+
+ // setter opp responsen
+ fakeResponse(response, options, function() {
+ view.persons.fetch(); // trigger AJAX-kall
+ });
+
+ return view;
+ }
+
+og:
+
+ .javascript
+ function fakeResponse(response, options, callback) {
+ var statusCode, headers, server, resp;
+
+ statusCode = options.statusCode || 200;
+ headers = options.headers || { "Content-Type": "application/json" }
+
+ server = sinon.fakeServer.create();
+ server.respondWith([statusCode, headers, response]);
+
+ callback();
+
+ server.respond();
+ server.restore();
+ }
+
+---
+
+# Test med brukerinput og feiltilstand
+
+ .javascript
+ it("should show error message when pagination fails", function() {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ });
+
+---
+
+# Test med brukerinput og feiltilstand
+
+ .javascript
+ it("should show error message when pagination fails", function() {
+
+ // AJAX-responser
+ var response = readFixtures("responses/persons.json");
+ var errorResponse = readFixtures("responses/errors.json");
+
+ // Lager initielt view
+ var view = getPersonsViewFromResponse(response);
+
+ expect($(view.html).find(".errors")).not.toExist();
+
+
+
+
+
+
+
+
+
+
+
+ });
+
+---
+
+# Test med brukerinput og feiltilstand
+
+ .javascript
+ it("should show error message when pagination fails", function() {
+
+ // AJAX-responser
+ var response = readFixtures("responses/persons.json");
+ var errorResponse = readFixtures("responses/errors.json");
+
+ // Lager initielt view
+ var view = getPersonsViewFromResponse(response);
+
+ expect($(view.html).find(".errors")).not.toExist();
+
+ // Setter opp en feilrespons ...
+ fakeResponse(errorResponse, { statusCode: 503 }, function() {
+
+
+
+
+
+ });
+
+
+ });
+
+---
+
+# Test med brukerinput og feiltilstand
+
+ .javascript
+ it("should show error message when pagination fails", function() {
+
+ // AJAX-responser
+ var response = readFixtures("responses/persons.json");
+ var errorResponse = readFixtures("responses/errors.json");
+
+ // Lager initielt view
+ var view = getPersonsViewFromResponse(response);
+
+ expect($(view.html).find(".errors")).not.toExist();
+
+ // Setter opp en feilrespons ...
+ fakeResponse(errorResponse, { statusCode: 503 }, function() {
+
+ // ... og trigger feilen ved å klikke på neste-knappen
+ // (view-abstraksjonen lytter på klikket og starter pagineringen)
+ $(view.html).find("a.more").click();
+
+ });
+
+ expect($(view.html).find(".errors")).toExist();
+ });
+
+---
+
+.middle.center
+
+Så hva har vi fått til med disse abstraksjonene?
+
+---
+
+# Speed, speed, speed
+
+Våre tester:
+
+![](img/fast-tests.png)
+
+Husk: Følelsen av raske tester i nettleseren.
+
+Dersom man har tester man stoler på, er det (nesten) nok å refreshe testene.
+
+---
+
+# Problem: DOM-avhengighet
+
+ .javascript
+ var persons = $("#persons .person");
+ var projects = $(persons[0]).find(".projects li");
+ expect(projects.length).toEqual(3);
+
+DOM-avhengig og krever mye oppsett.
+
+---
+
+.middle.center
+
+Mer oppsett i DOM-en gjør det vanskeligere å skrive tester
+
+og vanskeligere å holde dem oppdatert.
+
+---
+
+# Løsning: Subviews
+
+.subviews[![](img/subviews.png)]
+
+---
+
+# Løsning: Subviews
+
+.subviews[![](img/subviews.png)]
+
+ .javascript
+ var projects = personView.$(".projects li");
+ expect(projects.length).toEqual(3);
+
+`$` tilgjengelig på viewet!
+
+---
+
+# Løsning: Subviews
+
+ .javascript
+ View.prototype = {
+ // ...
+
+ $: function(selector) {
+ return $(this.html).find(selector);
+ }
+
+ // ...
+ }
+
+Nå trenger vi til og med ikke DOM-en i testene!
+
+Nå blir koden enklere:
+
+ .javascript
+ // vi kan gå fra
+ $(view.html).find("a.more").click();
+
+ // til
+ view.$("a.more").click();
+
+---
+
+# Uavhengige view
+
+Noen egenskaper ved våre views:
+
+* Hvert view er "ansvarlig for seg selv".
+* Kontakt ut gjennom eventer.
+* Lytter på eventer fra våre modeller (og globale eventer).
+* Mye enklere å teste.
+
+De har blitt mye bedre på grunn av at vi har hatt nok tester til å tørre å
+gjøre større refaktoreringer. Trenger ikke å være redd for at ting feiler.
+
+Og vi kan også fjerne et view uten av app-en feiler.
+
+---
+
+# Modulær kodebase
+
+Hver modul kan eksistere, testes og gjenbrukes uavhengig av andre moduler.
+
+Eksempel: Vi har (nesten) lik paginering på flere sider, som veldig enkelt
+inkluderes:
+
+ .javascript
+ PersonsView.prototype.mixin(Pagination); // eller mixin(PersonsView, Pagination)
+
+Med `mixin`s kan eventer, utvidet initialisering, og så videre dras inn. Altså
+er alt oppsettet på plass med ett funksjonskall.
+
+---
+
+# Modulær kodebase
+
+I denne jobben kom vi også over en god refaktorering for testene våre.
+
+ .javascript
+ var sharedBehaviorForPagination = function(opts) {
+ describe("pagination", function() {
+ it("should show a link to more payments if there are more payments", function() {
+ // test stuff
+ });
+ });
+ }
+
+I testen som drar inn paginering:
+
+ .javascript
+ sharedBehaviorForPagination({
+ firstPage: "responses/persons.json",
+ secondPage: "responses/persons-second.json",
+ errors: "responses/errors",
+ getViewFromResponse: getPersonsViewFromResponse
+ });
+
+---
+
+# Kort om eventer
+
+Alle AJAX-kall var abstrahert bort i modellene våre, som fyrte eventer ved
+start av henting, ved feil, når ferdig, og så videre.
+
+Dermed kunne vi i viewet skrive:
+
+ .javascript
+ model.on("fetch:started", this.addSpinner, this);
+ model.on("fetch:finished", this.removeSpinner, this);
+ model.on("fetch:finished", this.render, this);
+ // navn callback context
+
+I tillegg hadde vi globale eventer:
+
+ .javascript
+ BEKK.events.on('init:complete', this.startApp, this);
+
+ BEKK.events.trigger('init:complete');
+
+---
+
+.middle.center
+
+Testene våre gjorde det enklere å jobbe med bedre abstraksjoner
+
+---
+
+# Veien videre
+
+* Testdekning (A world of pain!)
+* Duplisering, duplisering, duplisering
+* Lav(ere) kompleksitet (se [Simple Made Easy](http://www.infoq.com/presentations/Simple-Made-Easy)!)
+* Ikke behov for en diktator
+
+---
+
+# Hva vi har lært
+
+* Kom igang.
+* Seriøst, bare kom igang!
+* Rammeverkene er ok-ish.
+* Lære JavaScript "på nytt".
+* Stopp opp og prat sammen.
+* Parprogrammering på vanskelige problemer.
+* It ain't easy. Og det tar tid.
+
+---
+
+.middle.center
+
+Men det er verdt det!
+
+---
+
+.middle.center
+
+# Spørsmål?
+
+
+
+
+