What we need?
What we need?
Behavior-driven development SYNTAX
describe
: identifica la suiteit
: identifica la specificaexpect
: identifica l'assertiondescribe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});
Jasmine fornisce i seguenti hook globali:
beforeEach
afterEach
beforeAll
afterAll
expect(foo).toEqual(bar);
expect(true).not.toEqual(false);
Un matcher può valutare un'assertion negativa incatenando un not
alla chiamata ad expect
prima di chiamare il matcher
Included matchers
toBe
describe("the toBe Matcher", function() {
it("should compare both types and values", function() {
var actual = "123";
var expected = "123";
expect(actual).toBe(expected);
});
});
Included matchers
toEqual
describe("the toEqual Matcher", function() {
it("should be able to compare arrays", function() {
var actual = [1, 2, 3];
var expected = [1, 2, 3];
expect(actual).toEqual(expected);
});
});
Included matchers
toBeDefined and toBeUndefined
describe("the toBeDefined and toBeUndefined Matchers", function() {
it("should be able to check defined and undefined objects", function() {
var object1 = {prop1: "value1"};
var object2;
expect(object1).toBeDefined();
expect(object2).toBeUndefined();
expect(object2).not.toBeDefined();
});
});
Included matchers
toBeNull
describe("the toBeNull Matcher", function() {
it("should be able to check if an object value is null",
function() {
var object = null;
expect(object).toBeNull();
});
});
Included matchers
toContain
describe("the toContain Matcher", function() {
it("should check if a String contains a substring", function() {
expect("Hello World from Cairo").toContain("Cairo");
});
it("should check if an Array contains a specific item", function() {
expect(["TV", "Watch", "Table"]).toContain("Watch");
});
});
Included matchers
toBeLessThan and toBeGreatherThan
describe("the toBeLessThan Matcher", function() {
it("should be able to perform the less than operation", function() {
expect(4).toBeLessThan(5);
});
});
Included matchers
toMatch
describe("the toMatch Matcher", function() {
it("should be able to match the value with a regular expression",
function() {
expect("foo bar baz").toMatch(/bar/);
});
});
Custom matchers
var customMathers = {
// factory constructor
toBeMultipleOf: function (util, customEqualityTester) {
return {
compare: function(actual, expected) {
var result = {
pass: ((actual % expected) == 0)
}
if (!result.pass) {
result.message = "Expected " + actual +
" to be multiple of " + expected + ", but it isn't";
}
return result;
}
}
}
};
Insieme a compare
si può specificare negativeCompare
nel caso in cui l'utilizzo di not
si reputi insufficente.
Custom matchers...
beforeEach(function() {
jasmine.addMatchers(customMathers);
});
....
expect(6).toBeMultipleOf(3);
Eventuali criticità troppo complesse o importanti refactoring vanno considerati come debiti tecnici, da rimandare ad una successiva iterazione se la loro risoluzione mette a rischio l'esito dell'iterazione
La creazione di una suite di test automatizzata rende le fasi di refactoring, enhancement e manutenzione (correttiva e adattativa) molto confortevoli, assicurando la rapida scoperta di eventuali problemi di regressione
Ci fornisce una misura di quanto il nostro codice è stato coperto dalla suite di test creata.
Chiaramente più alto è il grado di copertura del codice più basse sono le possibilità che il software contenga bug.
Istanbul
Agisce in tre differenti fasi:
Code coverage reports: servono a farci un'idea di quanto codice è testato rispetto al totale scritto
Ogni test deve testare le singole unità in maniera indipendente dalle altre, in modo che se c'è un problema in una unità dovrebbe fallire solo il test relativo a tale unità e non gli altri.
I test doubles sono degli oggetti in grado di 'impersonare' sistemi di terze parti o componenti allo scopo del testing
Risolvono entrambi i problemi: isolation e test setup
Person.js
var Person = function() {};
Person.prototype.sayHello = function() {
return "Hello";
};
Person.prototype.helloSomeone = function(toGreet) {
return this.sayHello() + " " + toGreet;
};
Call a fake sayHello() method
it("calls the sayHello() dummy function", function() {
var fakePerson = new Person();
spyOn(fakePerson, "sayHello");
fakePerson.helloSomeone("world");
expect(fakePerson.sayHello).toHaveBeenCalled();
expect(fakePerson.helloSomeone("world")).toEqual("undefined world");
});
Call a fake sayHello() but propagete to the real sayHello()
it("calls the sayHello() real function but tracks the call", function() {
var fakePerson = new Person();
spyOn(fakePerson, "sayHello").and.callThrough();
fakePerson.helloSomeone("world");
expect(fakePerson.sayHello).toHaveBeenCalled();
expect(fakePerson.helloSomeone("world")).toEqual("Hello world");
});
Check passed-in parameters
it("greets the world", function() {
var fakePerson = new Person();
spyOn(fakePerson, "helloSomeone");
fakePerson.helloSomeone("world");
expect(fakePerson.helloSomeone).toHaveBeenCalledWith("world");
expect(fakePerson.helloSomeone).not.toHaveBeenCalledWith("foo");
});
Spy on sayHello() and return a fixed value (stub)
it("says hello with fixed value", function() {
var fakePerson = new Person();
fakePerson.sayHello = jasmine.createSpy('"Say hello" spy')
.and.returnValue("Super hello");
fakePerson.helloSomeone("world");
expect(fakePerson.sayHello).toHaveBeenCalled();
expect(fakePerson.helloSomeone("world")).toEqual("Super hello world");
});
Spy on sayHello() and call a fake function
it("says hello with a fake function", function() {
var fakePerson = new Person();
fakePerson.sayHello = jasmine.createSpy('"Say hello" spy')
.and.callFake(function() {
return "Buongiorno"
});
fakePerson.helloSomeone("mondo");
expect(fakePerson.sayHello).toHaveBeenCalled();
expect(fakePerson.helloSomeone("mondo")).toEqual("Buongiorno mondo");
});
Tipologie di test doubles:
Come scrivere codice di qualità e quindi testabile?
Rimuovere le dipendenze implicite e richiedere ad un oggetto (detto injector) di fornirci l'istanza corretta che utilizzeremo nel nostro componente come dipendenza.
Benefici sul codice:
le funzionalita' richieste spesso non corrispondono direttamente a singole classi, metodi e blocchi di codice
L'approccio BDD è essenzialmente una metodologia test-first in cui la specifica in test è visibile all'utente finale (o allo stakeholder)
Nel gergo BDD i test sono chiamati specifiche (specs)
Partire con test E2E (requisito minimo!), di solito espressi in BDD, a cui affiancare test di unità sulle componenti chiave:
Partire con test di unità sulle componenti sviluppate, a cui affiancare test E2E sull'intera architettura del sistema.
All'interno di ogni browser Web sono presenti dei Tool di Sviluppo integrati
I Tool di Sviluppo ci danno una visione privilegiata di alcuni dei meccanismi interni al browser Web
Selezionare dal "Menu Chrome
" nell'angolo in alto a destra della finestra del browser, e selezionare "Strumenti > Strumenti per lo sviluppatore" (o "Tools > Developer Tools")
oppure
Premere il tasto destro del mouse su un qualunque elemento nella pagina e selezionare "Ispeziona Elemento" (o "Inspect Element")
Selezionare dal "Menu Firefox
" nell'angolo in alto a destra della finestra del browser, e selezionare "Sviluppatore > Abilita/Disabilita Strumenti" (o "Developer > Toggle Developer Tools")
oppure
Premere il tasto destro del mouse su un qualunque elemento nella pagina e selezionare "Ispeziona Elemento" (o "Inspect Element")
Esercitazione
Jasmine
describe
: identifica la suiteit
: identifica la specificaexpect
: identifica l'assertionHook globali:
beforeEach
afterEach
beforeAll
afterAll
Esercitazione 01
Scrivere i test mancanti.
Soluzione (1/2)
describe("when used to find factorial", function() {
...
it("should find factorial for zero", function() {
expect(simpleMath.getFactorial(0)).toBe(1);
});
it("should find factorial for positive number", function() {
expect(simpleMath.getFactorial(3)).toBe(6);
});
it("should throw an exception when the number is negative", function() {
expect(function() {
simpleMath.getFactorial(-10)
}).toThrowError('There is no factorial for negative numbers');
});
});
Soluzione (2/2)
describe("when used to find the sign of a number", function() {
it("should return 0 for zero", function() {
expect(simpleMath.signum(0)).toBe(0);
});
it("should return 1 for positive numbers", function() {
expect(simpleMath.signum(3)).toBe(1);
expect(simpleMath.signum(5)).toBe(1);
});
it("should return -1 for negative numbers", function() {
expect(simpleMath.signum(-3)).toBe(-1);
expect(simpleMath.signum(-5)).toBe(-1);
});
});
Esercitazione 02 - Web Storage API
Permettono di accedere allo Storage di tipo chiave-valore del browser
Due diversi tipi di storage:
La differenza è che nel sessionStorage è possibile settare una scadenza alle chiavi
Esercitazione 02 - Web Storage API
Le API sono molto semplici:
setItem()
getItem()
removeItem()
clear()
window.localStorage.setItem('mykey', 'a value');
window.localStorage.getItem('mykey');
Esercitazione 02
Scrivere i test mancanti.
Soluzione (1/2)
it('#addProduct should add a product to an empty shopping list', function() {
var product = {
id: 1,
title: "This is a title",
price: 5.99,
quantity: 0
};
shoppingCart.addProduct(product);
var data = JSON.parse(window.localStorage.getItem("shoppingcart"));
expect(data.summary).toBeDefined();
expect(data.summary.total).toBe(5.99);
expect(data.shoppingList['1']).toBeDefined();
expect(data.shoppingList['1'].title).toBe("This is a title");
expect(data.shoppingList['1'].price).toBe(5.99);
expect(data.shoppingList['1'].quantity).toBe(1);
});
Soluzione (2/2)
it('#addProduct should add a product to an existing shopping list and increment the summary', function() {
var product1 = {
id: 1,
title: "This is a title 1",
price: 5,
quantity: 0
};
var product2 = {
id: 2,
title: "This is a title 2",
price: 10,
quantity: 0
};
shoppingCart.addProduct(product1);
shoppingCart.addProduct(product2);
var data = JSON.parse(window.localStorage.getItem("shoppingcart"));
expect(data.summary).toBeDefined();
expect(data.summary.total).toBe(15.0);
expect(data.shoppingList['1']).toBeDefined();
expect(data.shoppingList['1'].title).toBe("This is a title 1");
expect(data.shoppingList['2']).toBeDefined();
expect(data.shoppingList['2'].title).toBe("This is a title 2");
});
Esercitazione 03
Scriverne l'implementazione.
Soluzione (1/2)
ShoppingCart.prototype.removeProduct = function(product) {
this.shoppingList[product.id].quantity--;
if (this.shoppingList[product.id].quantity <= 0) {
delete this.shoppingList[product.id];
}
this.summary.total -= product.price;
// save to localStorage
window.localStorage.setItem("shoppingcart", JSON.stringify({
summary: this.summary,
shoppingList: this.shoppingList
}));
};
Soluzione (2/2)
var Catalog = function() {
this.products = [
{ id: 1, title: "AngularJS in 21 giorni", price: 9.99 },
{ id: 2, title: "AngularJS: ora o mai piu'", price: 19.99 },
{ id: 3, title: "JavaScript - The Good Parts", price: 24.99 }
];
}
Catalog.prototype.getProductsList = function() {
return this.products;
}
Alca Società Cooperativa http://alcacoop.it
Released under CC BY-NC-SA 3.0