Szerver oldali JavaScript

12. hét

Unit és integrációs tesztek, mocha, assert struktúrák, TDD / DBB

Tesztelés

Miért tesztelünk?

Tesztelés

http://blog.typemock.com/wp-content/uploads/2014/03/unit_testing_survey1.png

Unit test

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.

Unit test

function osszead(a,b){
   return a + b;
}

Mit teszteljek?

  • osszead(1,2) ?= 3
  • osszead(2,1) ?= 3
  • osszead(-1,2) ?= 1

Unit test

Mi van, ha változik a kód?

function osszead(a,b){
    if (a>20)
        return 20;
   return a + b;
}
  • osszead(1,2) ?= 3
  • osszead(2,1) ?= 3
  • osszead(-1,2) ?= 1

Kell-e működnie 20-nál nagyobb számokra az osszeadas-nak?

Unit test

http://blog.typemock.com/wp-content/uploads/2014/03/unit_testing_survey1.png

Integrációs teszt

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.

End-to-end tesztek

Felhasználó irányából megfogott, teljes folyamatot lefedő tesztek.

Mivel tesztelünk

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.

Mocha + Chai

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!

Mocha + Chai

É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);
  });
});

Mocha + Chai

Lefuttatni parancssorban lehet a teszteket:

mocha --recursive

Mocha + Chai

Ha esetleg hiba lenne:

A fájl és sorszám a tesztesetben lévő expect helyét mondja meg, NEM a hiba helyét!

Mocha + Chai

Mi van, ha async amit tesztelni kell?

function osszead(a, b, cb){
    setTimeout(function(){
        cb(a+b);
    }
}

Ez ugye nem ad vissza semmit sem...

Mocha + Chai

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

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');
    });
});

Chai assert

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

Chai assert

.a('null') - típus ellenőrzés

expect({ foo: 'bar' }).to.be.an('object');
expect(null).to.be.a('null');
expect(undefined).to.be.an('undefined');

.include(...) - string részlet, objekt/lista kulcs ellenőrzés

expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

Chai assert

.exist - nem null vagy undefined

expect(foo).to.exist;
expect(bar).to.not.exist;

.equal - egyenlő

expect({ foo: 'bar' }).to.eql({ foo: 'bar' });
expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]);

Chai assert

.instanceof - egy adott osztály típus ellenőrzése

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);

.property - objektum kulcs/érték ellenőrzés

var obj = { foo: 'bar' };
expect(obj).to.have.property('foo');
expect(obj).to.have.property('foo', 'bar');

Chai assert

.throw - hiba dobásának ellenőrzése

var err = new ReferenceError('This is a bad function.');
var fn = function () { throw err; }
expect(fn).to.throw(ReferenceError);
expect(fn).to.throw(Error);

.satisfy(method) - függvénnyel kiértékelés

expect(1).to.satisfy(function(num) { return num > 0; });

Hogyan is tesztelünk?

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();
    });
  };
};

Mockoljunk!

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!!!

Middleware teszt

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!!!

Mockolni bármit ér

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

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.

Code coverage teljes kódra

Code coverage teljes kódra

Innen látszik, ha már "kilóra" kevés a teszt. Hiányzik egy teszt arra, ha az err igaz.

A tesztelés művészet

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.