12. hét
Unit és integrációs tesztek, mocha, assert struktúrák, TDD / DBB
Miért tesztelünk?
Elkészítünk egy modult, egy funkciót, egy nagyon pici elemi egységet, és kíváncsiak vagyunk, fut-e: manuálisan teszteljük.
Ha a kis egység működése már definiálva van, akkor az egyes szabályokra lehet teszteket írni.
function osszead(a,b){
return a + b;
}
Mit teszteljek?
Mi van, ha változik a kód?
function osszead(a,b){
if (a>20)
return 20;
return a + b;
}
Kell-e működnie 20-nál nagyobb számokra az osszeadas-nak?
Ha már az építőkövek tesztelve vannak, nagyobb folyamatrészleteket / folyamatokat is lehet tesztelni. Ezt még mindig kód szintű segítséggel.
Felhasználó irányából megfogott, teljes folyamatot lefedő tesztek.
Több javascripttesting framework is van, a 3 talán legnépszerűbb:
Mi a Mocha-val tesztelünk, ennek viszont nincs beépített assertation libraryje.
A Mocha mellé én a Chai-t fogom venni mint assert, abból is az expect formát. Talán ennek a legegyszerűbb a szintaxisa.
npm i mocha -g
npm i chai
Írjunk tesztet!
És egy teszteset:
var assert = require('assert');
var expect = require('chai').expect;
describe('osszead', function () {
it('should return 3 when a=1 and b=2', function () {
var c = osszead(1, 2);
expect(c).to.be.equal(3);
});
});
Lefuttatni parancssorban lehet a teszteket:
mocha --recursive
Ha esetleg hiba lenne:
A fájl és sorszám a tesztesetben lévő expect helyét mondja meg, NEM a hiba helyét!
Mi van, ha async amit tesztelni kell?
function osszead(a, b, cb){
setTimeout(function(){
cb(a+b);
}
}
Ez ugye nem ad vissza semmit sem...
describe('osszead', function (done) {
it('should return 3 when a=1 and b=2', function () {
osszead(1, 2, function(c){
expect(c).to.be.equal(3);
done();
});
});
});
Van egy default timeout, 2 sec, ha ez alatt nem hívunk rá a done-ra, akkor jelzi, hogy nem történt meg a callback.
Mocha esetében a szinteket describe-al, a tényleges teszteket it-el definiáljuk.
A root, suite szinten is lehet extra elő/utó műveleteket definiálni. NE tegyük, ha van rá mód, nehezíti az átláthatóságot.
beforeEach(function() {
console.log('before every test in every file');
});
describe('osszead', function (done) {
before(function() {
console.log('before all test in this suite, runs once');
});
it('should return 3 when a=1 and b=2', function () {
//...
});
after(function() {
console.log('after all test in this suite, runs once');
});
});
Az Chai expect része Behavior-driven development (BDD) alapján áll össze: http://chaijs.com/api/bdd/
Az olvashatóság miatt támogatja a "töltelékszavakat": to, be, is, and, has
expect({ foo: 'bar' }).to.be.an('object');
expect(null).to.be.a('null');
expect(undefined).to.be.an('undefined');
expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
expect(foo).to.exist;
expect(bar).to.not.exist;
expect({ foo: 'bar' }).to.eql({ foo: 'bar' });
expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]);
var Tea = function (name) { this.name = name; }
, Chai = new Tea('chai');
expect(Chai).to.be.an.instanceof(Tea);
expect([ 1, 2, 3 ]).to.be.instanceof(Array);
var obj = { foo: 'bar' };
expect(obj).to.have.property('foo');
expect(obj).to.have.property('foo', 'bar');
var err = new ReferenceError('This is a bad function.');
var fn = function () { throw err; }
expect(fn).to.throw(ReferenceError);
expect(fn).to.throw(Error);
expect(1).to.satisfy(function(num) { return num > 0; });
Egyszerű függvényeket könnyű (require + tesztek), de nézzünk meg egy middleware-t:
var requireOption = require('../common').requireOption;
module.exports = function (objectrepository) {
var userModel = requireOption(objectrepository, 'userModel');
return function (req, res, next) {
userModel.find({}, function (err, results) {
if (err) {
return next(err);
}
res.tpl.users = results;
return next();
});
};
};
Helyettesítsünk minden külső objektumot és dependenciát egy végletekig leegyszerűsített változattal!
Mit kell mockolni?
objectrepository, req, res.tpl.users, next, userModel, userModel.find
De legalább tudjuk majd tesztelni adatbázis nélkül!!!
var expect = require('chai').expect;
var getUserListMW = require('../../../middleware/user/getUserList');
describe('getUserList middleware ', function () {
it('should return users', function (done) {
var req = {}; var res = { tpl: {} };
var fakeUserModel = { find: function (some, cb) {
cb(undefined, ['user1', 'user2'])
} };
getUserListMW({
userModel: fakeUserModel
})(req, res, function (err) {
expect(res.tpl.users).to.eql(['user1', 'user2']);
expect(err).to.eql(undefined);
done();
});
});});
Nem is annyira bonyolult ahhoz képest, hogy egy async, modelt használó express alatt lévő middlewaret tesztelünk, mindezen kompenensek nélkül!!!
var req = {
body: {
email: 'user@server.com',
password: 'asdasd'
}
};
var res = {
send: function(){},
tpl: {
valamikomplex: {
alma: "korte",
korte: function(cb){
return this.alma;
}
}
}
};
Code coverage: mennyi sort, ágat, lehetőséget fed le az össze unit test.
Istanbult fogok használni, gyakorlaton megmutatom, nem szükséges a házik esetében code coveraget vizsgálni.
Innen látszik, ha már "kilóra" kevés a teszt. Hiányzik egy teszt arra, ha az err igaz.
Ezt tényleg csinálni kell, hogy meglegyen a rutin, hogy mit érdemes tesztelni, mi törhet / törik össze, mit hogyan tesztelek (hiba meglétét vs hiba szövegét).
Gyakorlaton mindent tesztelni fogunk, nem meglepő módon.