class: center, middle, inverse # Cours NoSQL: Node.js et les tests ## Haïkel Guémar - haikel.guemar@gmail.com --- class: center, middle # Introduction ![logo](img/nodejs-logo.png) --- class: middle ## Structure d'un projet Node.js --- class: middle ## Fichier package.json (rappel) ```json { "name": "miniblog", "version": "1.0.0", "description": "mini blog", "main": "index.js", "dependencies": { "body-parser": "^1.18.2", "chai": "^4.1.2", "consolidate": "^0.15.1", "express": "^4.16.3", "pug": "^2.0.3", "supertest": "^3.0.0" }, "devDependencies": { "mocha": "^5.0.5" }, "scripts": { "start": "node server.js", "test": "./node_modules/mocha/bin/mocha tests/*" }, "keywords": [ "blog" ], "author": "nobody", "license": "ISC" } ``` --- class: middle, center, inverse # Tester, c'est douter --- class: middle
--- class: middle ## Pourquoi tester? * Personne n'écrit du code sans bugs * Écrire les tests permet de fixer les pré-requis et les comportements * Automatiser les tests permet de vérifier le respect du "contrat" * Créer de la confiance * Une suite de tests automatisés sont le filet de sécurité du développeur (aka suite de non-régression) --- class: middle ## Différents types de tests * Unitaires: tester le bon fonctionnement d'un composant logiciel en isolation. * Intégration: tester les interactions entre des composants logiciels. * Fonctionnels: tester les fonctionnalité d'un logiciel. --- class: middle ## Des tests unitaires de première classe * Fast: rapide à exécuter * Isolated: permet de tester un comportement unitaire * Repeatable: des résultats déterministes * Self-validating: pas besoin de repasser derrière le code pour comprendre d'où vient le problème * Timely: tester au bon moment --- class: middle ## Test-Driven Development (TDD) * Technique popularisé par Extreme Programming * Commencer par écrire les tests puis le code * Les tests servent alors de guide et de contrat * Encourage un couplage faible des composants logiciels --- class: middle ## Cycle de TDD: Red, Green, Refactor * Red: j'écris mon test et m'assure qu'il échoue * Green: j'écris le minimum de code pour faire passer mon test * Refactor: j'améliore mon code tout en m'assurant que le test passe toujours Puis on recommence!
--- class: middle ## Différence avec le Test First
--- class: middle ## Chicago vs London school of TDD * Chicago est le style classique développé par Kent Beck * La première école se concentre sur le résultat, la seconde sur le comportement * Chicago: inside => outside; London: outside => inside * L'école londonienne encourage l'utilisation de mocks ou test doubles * L'école de Chicago utilise aussi les tests doubles --- class: middle ## Tests doubles * Permettant de remplacer des composants logiciels réels lors des tests Différents type de doubles * stub: objet/fonction implémentant une interface renvoyant toujours la même réponse * fake: implémentation plus simple de l'objet (ex: base de données en mémoire) * spy: comme le stub, mais permet d'enregister des informations que l'on pourra exploiter (ex: est-ce que la fonction a été appelée? combien de fois?) * mock: substitution compléte de l'objet, vise à tester le comportement. --- class: middle, inverse, center # Tester avec Node.js --- class: middle ## Différents outils * Mocha: framework de tests (écrit par l'auteur d'Express) * Chai: bibliothèque d'assertions (supporte différents styles d'assertions) * Supertest --- class: middle ## Mon premier test unitaire tests/test_capitalize.js ```javascript var chai = require('chai'); var expect = chai.expect; var capitalize = require('../capitalize'); describe('test capitalize', function() { it('should return Bob', function() { expect(capitalize.capitalize('bob')).to.equal('Bob') }) }); ``` --- class: middle ## Exécution ```bash % npm test test capitalize ✓ should return Bob 1 passing (6ms) ``` --- class: middle ## Second test tests/test_capitalize.js ```javascript var chai = require('chai'); var expect = chai.expect; var assert = chai.assert; var capitalize = require('../capitalize'); describe('test capitalize', function() { it('should return Bob', function() { expect(capitalize.capitalize('bob')).to.equal('Bob') }); it('should keep empty strings as-is', function() { assert(capitalize.capitalize("") == "") }); }); ``` --- class: middle ## Tester des API avec supertest tests/test_api.js ```javascript var app = require("../server"); var supertest = require("supertest"); describe("plain text response", function() { it("returns a plain text response", function(done) { supertest(app) .get("/bob") .set("User-Agent", "API testing") .set("Accept", "text/plain") .expect("Content-Type", "text\/plain; charset=utf-8") .expect(200) .end(done) }); it("returns \"Hello Bob\"", function(done) { supertest(app) .get("/bob") .set("User-Agent", "API testing") .set("Accept", "text/plain") .expect(function(res) { if (res.text !== "Hello Bob") { throw new Error("Does not return \"Hello Bob\" but " + res.text); } }) .end(done) }); }); ``` --- class: middle ## Le code server.js ```javascript var express = require('express'); var capitalize = require('./capitalize'); app = express() app.get('/:name', function(req, res) { var name = capitalize.capitalize(req.params.name); res.setHeader('Content-Type', 'text/plain'); return res.send("Hello " + name); }); app.listen(8000); module.exports = app; ``` --- class: middle ## Refactoring du code de test avec une fixture ```javascript var app = require("../server"); var supertest = require("supertest"); describe("plain text response", function() { var request; beforeEach(function() { request = supertest(app) .get("/bob") .set("User-Agent", "API testing") .set("Accept", "text/plain"); }); it("returns a plain text response", function(done) { request .expect("Content-Type", "text\/plain; charset=utf-8") .expect(200) .end(done) }); it("returns \"Hello Bob\"", function(done) { request .expect(function(res) { if (res.text !== "Hello Bob") { throw new Error("Does not return \"Hello Bob\" but " + res.text); } }) .end(done) }); }); ``` --- class: center, inverse, middle # TD --- class: middle ## Code moi un blog 1. comment modéliseriez-vous la base de données avec un SGBDR 2. comment le feriez-vous avec MongoDB ? --- class: middle ## Avec MongoDB ```javascript posts: { 'title': 'cours nosql', 'body': '...', 'author': '...', 'date': '...', 'comments': [{'name': 'Joe', 'email': 'joe@doe.com', 'comment': '...'}, ... ], 'tags': ['cours', 'nosql', ...] } authors: {_id: '...', 'password': '...'} ``` --- class: middle, center, inverse # Q/A