formations/qualification/presentation/slides.md

14 KiB
Raw Blame History

Qualification et intégration continue

William Petit - S.C.O.P. Cadoles


Qu'est ce que la qualification ?


Qu'est ce que l'intégration continue ?


Les tests applicatifs

Les tests applicatifs ont pour objectif de valider le bon fonctionnement de l'application d'un point de vue métier, c'est à dire de vérifier que celle ci répond aux attentes et aux contraintes du client d'un point de vue fonctionnel.

Ils permettent également et surtout aux développeurs de détecter les régressions dans le cycle de développement.


La pyramide des tests applicatifs

center 70%


Tests unitaires


Objectifs

Tester le bon fonctionnement des plus petites unités logiques/fonctionnelles de l'application: les fonctions/méthodes d'objets.

Exemple Vérifier qu'une méthode de calcul du prix taxé d'un produit retourne la valeur attendue en fonction des paramètres passés.


Cycle de vie

  1. Définir le comportement attendu de la classe/fonction à implémenter.
  2. Créer le cas de test validant ce comportement.
  3. Exécuter le test. Il ne devrait pas être passant, l'implémentation étant incomplète à ce stade.
  4. Implémenter la fonction/classe pour que le test soit passant.

Caractéristiques clés

  • Ils sont nombreux.
  • Ils sont mis en place dès le début de l'implémentation, voir avant (voir "Test Driven Development").
  • Ils sont rapides à exécuter.
  • Ils permettent de détecter rapidement les régressions.
  • Ils permettent de rassurer le développeur qui doit apporter des évolutions à une base de code existante.
  • Ils s'exécutent en continu sur le poste du développeur et à chaque changement sur le serveur de gestion des sources.
  • Ils ne devraient pas nécessiter de dépendances externes.

Exemple: Tests unitaires en Javascript (Mocha)

// Fichier test/my-test-suite.js

const assert = require('assert');

// Déclaration de la suite de tests
describe('My suite', function() {
  
  // Test synchrone
  it('should not fail', function() {
    assert.equal(true, 1); 	// true == 1 en javascript
  });
  
  // Test asynchrone
  it('should do something async', function() {
  
    return doSomethingAsync()
      .then(result => {
        assert.ok(result);	// On vérifie que le résultat n'est pas null
      })
      .catch(err => {
        assert.ifError(err); 	// On vérifie qu'aucune erreur n'est remontée
      })
    ;
    
  });

});

Et le style dans tout ça ?


Exercice: Tests unitaires d'une fonction de validation d'un numéro de carte bancaire en Javascript

Soit un numéro de carte bancaire donné, implémenter un test pour la fonction validant le code de celle ci PUIS implémenter la fonction qui permet de valider ce code.

Ressources


Tests d'intégration


Objectifs

Tester la bonne communication et interaction des différents "modules" composant une application.

Exemple Vérifier que qu'une transaction est correctement enregistrée dans la base de données lorsque le module de paiement est utilisé.


Caractéristiques clés

  • Ils sont moins nombreux que les tests unitaires.
  • Ils peuvent prendre un peu de temps à s'exécuter.
  • Ils sont mis en place au cours de l'implémentation initiale et maintenus au fur et à mesure de l'évolution de l'application.
  • Ils peuvent nécessiter des dépendances externes, mais généralement pas l'ensemble de l'infrastructure.
  • Ils peuvent nécessiter des données d'amorçage.
  • Ils impliquent souvent la mise en place de composants "factices" pour simuler une partie de l'application.
  • Ils s'exécutent à la demande sur le poste du développeur et sur le serveur d'intégration continue à intervalles réguliers.

Cycle de vie

Les tests d'intégration devraient être abordés (en général) en seconde moitiée d'itération, au moment de la finalisation des modules applicatifs prêts à être livrés.

Ils devraient cibler en premier les modules critiques de l'application, notamment ceux ayant pour rôle de contrôler/valider le cycle de vie des données métiers.

Les tests d'intégration peuvent être un bon témoin/validateur de l'état de réussite d'une itération.


Exemple: Test d'une API REST avec Symfony3

  1. Implémenter une API JSON/REST basique avec Symfony3 permettant d'enregistrer/lister dans une base de données (sqlite3) une "annonce" avec les propriétés suivantes:
    • Titre
    • Description
    • Type
    • Prix
  2. Implémenter avec la librairie standard de Symfony3 les tests suivants:
    • Ajout d'une nouvelle annonce et vérification du bon enregistrement de la nouvelle annonce via un appel sur l'URL d'affichage des annonces

Ressources


Tests fonctionnels


Objectif

Tester le bon fonctionnement de l'application d'un point de vue utilisateur.


Caractéristiques clés

  • Ils devraient être les moins nombreux de l'ensemble des tests applicatifs.
  • Ils devraient être représentatifs des cinématiques d'action des utilisateurs.
  • Ils devraient s'exécuter sur un environnement identique à la production.
  • Ils sont souvent complexes à mettre en place/maintenir.
  • Ils ont une couverture fonctionnelle très large mais ne permettent pas d'identifier directement les sources de dysfonctionnement.
  • Ils devraient couvrir les procédures faisant intervenir les dépendances externes de l'application (serveur de courriel, API externes, base de données...).

Cycle de vie

Les tests fonctionnels devraient être implémentés une fois que l'itération a atteint une certaine stabilité en terme d'interface utilisateur.

Ils ne devraient être modifiés que lorsque le client demande une évolution de l'interface utilisateur et/ou des cinématiques d'action.


Exemple: Tests fonctionnels Web avec NightmareJS

Voir l'exemple tests-fonctionnels


Exercice: Simuler un utilisateur avec le client web KiwiIRC

Via NightmareJS et avec le client KiwiIRC, se connecter au serveur irc.freenode.net, rejoindre le canal #cadoles en tapant la commande /join <canal> puis envoyer un message sur le canal nouvellement rejoint.


Les tests "techniques"

Les tests techniques ont pour objectif de valider le bon fonctionnement de l'infrastructure exécutant l'application, c'est à dire que l'application et son infrastructure seront capables de répondre au contexte d'usage en production.


Tests de charge


Objectif

Les tests de charge doivent permettre de valider le niveau de stabilité de l'infrastructure et de l'application par rapport à la volumétrie d'utilisateurs en production et son contexte technique d'utilisation.

Pour ce faire, la mise en place d'une stratégie concrète de métrologie est obligatoire.


Caractéristiques clés

  • Ils nécessitent la mise en place d'une infrastructure identique (ou au plus proche) à la production.
  • Les données produites sont souvent peu fiables car les tests de charge véritablement représentatifs d'un comportement d'un utilisateur sont complexes à mettre en place.
  • Ils doivent couvrir toutes les briques techniques de l'infrastructure et de l'application: disques, réseau, mémoire vive, CPU, serveur HTTPS, serveur applicatif, base de données...

Cycle de vie

Les tests de charge devraient être effectués avant tout primo déploiement d'une application et réactualisés lorsque le contexte d'usage de celle ci change (exemple: bascule d'un usage local à un usage national).

Les tests de charge devraient être exécutés régulièrement afin de vérifier qu'aucun "goulot d'étranglement" n'a été introduit dans l'itération en cours.


Exemple: Utilisation de Siege pour vérifier les temps de réponse d'une application Web

https://www.joedog.org/siege-home/


Tests de sécurité


Objectif

Valider la conformité de l'infrastructure et de l'application d'un point de vue sécurité. Découvrir des vulnérabilités dans la conception de l'application.


Caractéristiques clés

  • Ils sont complexes à mettre en place
  • Ils doivent fonctionner sur un environnement identique à la production.
  • Ils ne peuvent remplacer un audit de sécurité "manuel".
  • Ils sont souvent peu efficaces pour détecter les failles qui nécessites un premier niveau d'accréditation.

Cycle de vie

Ils doivent être mis en place au plus tôt dans le cycle de développement.

Suivant le type de cible, ils peuvent être exécutés de différentes manières: à chaque fin d'itération, de manière règulière, etc...

Ils doivent être réactualisés lors de tout changement d'infrastructure et de socle technologique de l'application.


Exemple: Vérification de sécurité pour les dépendances PHP

https://github.com/sensiolabs/security-checker


Exemple: Audit automatisé sur les applications Web avec w3af

https://github.com/andresriancho/w3af/


Le cycle (simplifié) d'intégration continue

center 60%


Automatisation, virtualisation et conteneurisation


Principes généraux sur les conteneurs

  • Les conteneurs sont un mécanisme de virtualisation basée sur l'isolation des processus au niveau du système d'exploitation.
  • Ils bénéficient d'une très faible perte de performances brutes par rapport à de la virtualisation classique utilisation des mécanismes d'émulation.

Présentation de Docker


Manipuler des images de conteneur


Exécuter un conteneur


Gérer les conteneurs


Partager des répertoires


Configurer le réseau d'un conteneur


Construire une image


Publier une image sur le Hub


Utiliser docker-compose


Le fichier docker-compose.yml

version: '2' 		# Version du fichier docker-compose
services: 		# Déclaration des services
  faketools:		# Service 'faketools'
    build:		# Création de l'image du conteneur depuis un dossier du projet
      context: .
      dockerfile: containers/faketools/Dockerfile
      args:		# Déclaration d'arguments de construction de l'image
        HTTP_PROXY: ${HTTP_PROXY}
        HTTPS_PROXY: ${HTTPS_PROXY}
        http_proxy: ${http_proxy}
        https_proxy: ${https_proxy}
    ports:		# Déclaration des ports à exposer sur la machine hôte
      - "8080:8080"
      - "8081:8081"
      - "2525:2525"
      - "3389:3389"
      - "8443:8443"
      - "2222:22"
    volumes: 		# Déclaration des montages de volumes
      - ./data:/faketools/data
    environment: 	# Déclaration de variables d'environnement
      HTTP_PROXY: ${HTTP_PROXY}
      HTTPS_PROXY: ${HTTPS_PROXY}
      http_proxy: ${http_proxy}
      https_proxy: ${https_proxy}


Exercice

Créer un environnement de développement docker-compose à partir de l'image ubuntu:xenial pour une application Symfony3 et une base de données MySQL avec l'image mysql:latest.

Le répertoire des sources locales devra être partagé avec le conteneur d'application et le port du conteneur exposé sur la machine hôte.


Les serveurs d'intégration continue

https://docs.gitlab.com/ce/ci/ https://jenkins.io/ https://drone.io/


Exemple: Gitlab et Gitlab Runner


Le fichier .gitlab-ci.yml

# Déclaration de variables utilisables dans le fichier
variables:
  MY_VAR: Hello World
  
# Nom de l'image Docker à utiliser pour les différentes phases du pipeline
image: <image_docker>

# Listes de commandes à exécuter dans le conteneur 
# avant le lancement des différents jobs
before_script:
  - echo ${MY_VAR}

# Déclaration des phases du pipeline
stages:
  - test
  - dist

# Déclaration d'un job
unit-tests:
  stage: test 	# Ce job est attaché à la phase "test"
  script:	# Commandes à exécuter dans le conteneur
    - ./script/test

# Déclaration d'un autre job
dist:
  stage: dist 	# Ce job est attaché à la phase "dist" 
  only: 	# Ce job ne doit s'exécuter que lorsque la branche est "master"
    refs:
      - master
  script:
    - ./script/dist
  artifacts: # Artefacts à conserver à l'issue de la phase
    paths:
      - dist/*.tar.gz

Exécuter un pipeline en local

gitlab-runner exec docker [job]

La fonctionnalité est dépréciée depuis la version 10.0. Voir la discussion https://gitlab.com/gitlab-org/gitlab-runner/issues/2797 pour le futur remplacement.


Exercice: Mise en application générale

Sélectionner une application existante (ou laissez le formateur vous proposer un sujet) et:

  1. Mettre en place l'exécution des tests unitaires sur le projet et écrire une première suite de tests.
  2. Mettre en place les tests d'intégration sur le projet et écrire une première suite de tests.
  3. Mettre en place les tests fonctionnels et écrire une première suite de tests.
  4. Concevoir un "pipeline" d'intégration continue avec Gitlab et Gitlab Runner et tester une activation complète du pipeline.

Licence

CC BY-NC-SA 3.0 FR

Creative Commons - Attribution - Pas dUtilisation Commerciale - Partage dans les Mêmes Conditions 3.0 France