Lab 4 - Version Control, Build Systems, and Automated Testing

Objectifs

Le but de ce lab est d’acquérir une expérience pratique avec les outils DevOps essentiels :

  • Gérer du code avec Git et collaborer via GitHub
  • Configurer un build system avec NPM
  • Écrire des tests automatisés avec Jest et SuperTest
  • Tester du code d’infrastructure avec OpenTofu

Section 1 : Gestion de version avec Git

Nous avons initialisé un dépôt Git local, effectué des commits, créé des branches et effectué des merges.

mkdir /tmp/git-practice
cd /tmp/git-practice
echo 'Hello, World!' > example.txt
git init
git add example.txt
git commit -m "Initial commit"

Exercise 1 : Créer un tag v1.0

git tag v1.0
git tag  # vérifie que le tag a bien été créé

Exercise 2 : git rebase vs git merge

git checkout -b feature
echo 'Feature work' >> example.txt
git commit -am "Feature commit"
git checkout main
git rebase feature
  • git merge crée un commit de fusion qui préserve l’historique des deux branches
  • git rebase réécrit l’historique pour un historique linéaire plus propre

Section 2 : Collaboration avec GitHub

Nous avons poussé notre dépôt local sur GitHub et travaillé avec des pull requests.

git remote add origin https://github.com/<username>/devops-lab.git
git push -u origin main

Exercise 3 : Branch protection

  • Settings → Branches → Add branch protection rule
  • “Require a pull request before merging”
  • “Require approvals” avec au moins 1 reviewer

Exercise 4 : Signed commits

gpg --full-generate-key
git config --global user.signingkey <KEY_ID>
git config --global commit.gpgsign true

Section 3 : Build System avec NPM

Configuration de NPM pour une app Node.js avec Express.

"scripts": {
    "start": "node server.js",
    "dockerize": "./build-docker-image.sh",
    "test": "jest --verbose",
    "test:coverage": "jest --coverage"
}

Exercise 5 : Pinning des versions dans le Dockerfile

FROM node:21.7.3-alpine

Exercise 6 : Script pour lancer l’app dans Docker

"scripts": {
    "run-docker": "docker run -p 8080:8080 app:1.0.0"
}

Usage :

npm run run-docker
# Puis accéder à http://localhost:8080

Section 4 : Gestion des dépendances avec NPM

Exercise 7 : Endpoint /name/:name

app.get('/name/:name', (req, res) => {
  res.send(`Hello, ${req.params.name}!`);
});

Exercise 8 : dependencies vs devDependencies

npm install express --save          # dependency
npm install --save-dev jest nodemon # devDependency
  • dependencies : packages nécessaires en production
  • devDependencies : packages pour le développement uniquement

Section 5 : Tests automatisés

Installation de Jest et SuperTest, séparation du code en deux fichiers :

  • app.js : logique de l’app
  • server.js : démarre le serveur

Exercise 9 : Endpoint /add/:a/:b avec tests

app.get('/add/:a/:b', (req, res) => {
  const a = parseFloat(req.params.a);
  const b = parseFloat(req.params.b);
  if (isNaN(a) || isNaN(b)) {
    return res.status(400).send('Invalid input: a and b must be numbers');
  }
  res.send(`${a + b}`);
});

Tests associés :

describe('GET /add/:a/:b', () => {
  it('should return the sum of two numbers', async () => {
    const res = await request(app).get('/add/2/3');
    expect(res.statusCode).toBe(200);
    expect(res.text).toBe('5');
  });
  it('should return 400 for invalid inputs', async () => {
    const res = await request(app).get('/add/abc/3');
    expect(res.statusCode).toBe(400);
  });
  it('should handle negative numbers', async () => {
    const res = await request(app).get('/add/-5/3');
    expect(res.statusCode).toBe(200);
    expect(res.text).toBe('-2');
  });
  it('should handle decimal numbers', async () => {
    const res = await request(app).get('/add/1.5/2.5');
    expect(res.statusCode).toBe(200);
    expect(res.text).toBe('4');
  });
});

Exercise 10 : Code coverage

npm run test:coverage

Section 6 : Tests automatisés pour le code OpenTofu

Tests d’infrastructure pour le module Lambda avec l’API Gateway.

Exercise 11 : Réponse JSON

exports.handler = (event, context, callback) => {
  callback(null, {
    statusCode: 200,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ message: "Hello, World!" })
  });
};

Exercise 12 : Test négatif (404)

Test qui vérifie qu’une route inexistante retourne bien un 404.

Section 7 : Bonnes pratiques de test

Exercise 13 : TDD (Test-Driven Development)

Test écrit avant l’implémentation :

describe('GET /multiply/:a/:b', () => {
  test('It should return the product of two numbers', async () => {
    const response = await request(app).get('/multiply/3/4');
    expect(response.statusCode).toBe(200);
    expect(response.text).toBe('12');
  });
});

Implémentation :

app.get('/multiply/:a/:b', (req, res) => {
  const a = parseFloat(req.params.a);
  const b = parseFloat(req.params.b);
  if (isNaN(a) || isNaN(b)) {
    return res.status(400).send('Invalid input');
  }
  res.send(`${a * b}`);
});

Exercise 14 : Analyse du coverage

Après avoir exécuté npm run test:coverage, nous avons obtenu un coverage proche de 100% sur app.js.

Conclusion

Ce lab nous a permis de comprendre et mettre en pratique :

  • La gestion de version avec Git (branches, tags, rebase)
  • La collaboration via GitHub (pull requests, branch protection)
  • L’automatisation des tâches avec NPM (build, test, Docker)
  • Les tests automatisés avec Jest et SuperTest pour une app Node.js
  • Les tests d’infrastructure avec OpenTofu sur AWS

La combinaison de ces pratiques améliore la qualité du code, facilite la collaboration en équipe et sécurise les déploiements.