462 lines
14 KiB
Markdown
462 lines
14 KiB
Markdown
<style>pre, table { font-size: 0.5em !important; }</style>
|
||
|
||
# Qualification et intégration continue
|
||
|
||
William Petit - S.C.O.P. Cadoles
|
||
|
||
---
|
||
<!-- page_number: true -->
|
||
|
||
## 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%](./img/pyramidetestsapplicatifs.png)
|
||
|
||
---
|
||
|
||
## 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)
|
||
|
||
```javascript
|
||
// 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 ?
|
||
|
||
- Javascript: https://eslint.org/
|
||
- PHP: https://github.com/phpro/grumphp
|
||
|
||
---
|
||
|
||
## 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**
|
||
|
||
- [Mocha - Librairie de test](https://mochajs.org/)
|
||
- [Module `assert`](https://nodejs.org/api/assert.html)
|
||
- [La formule de Luhn](https://fr.wikipedia.org/wiki/Formule_de_Luhn)
|
||
|
||
---
|
||
|
||
## 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**
|
||
- [Symfony3 - Working with the Test client](http://symfony.com/doc/current/testing.html#working-with-the-test-client)
|
||
|
||
---
|
||
|
||
## 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](https://kiwiirc.com/nextclient/), 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%](./img/cycle_dintgration_continue.png?1)
|
||
|
||
---
|
||
|
||
## 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`
|
||
|
||
```yaml
|
||
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`
|
||
|
||
```yaml
|
||
# 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
|
||
|
||
<pre style="font-size:0.8em!important">
|
||
gitlab-runner exec docker [job]
|
||
</pre>
|
||
|
||
> 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 d’Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 France](https://creativecommons.org/licenses/by-nc-sa/3.0/fr/)
|