Formation QA + IA
Développeur AQ — Radio-Canada / CBC
Chaque niveau couvre un angle différent. On exécute du bas vers le haut dans un pipeline CI/CD : du plus rapide et isolé au plus lent et réaliste.
| Type | Mock ? | Vérifie quoi | Vitesse | Quantité |
|---|---|---|---|---|
| Linting / Typage | Non | Syntaxe, conventions, erreurs de type | Instantané | Tout le code |
| Unitaire | Oui (si dépendances) | La logique / les règles métier | Millisecondes | Beaucoup |
| Intégration | Non | La communication entre composants | Secondes | Moyen |
| E2E | Non | Le parcours utilisateur complet | Dizaines de secondes | Peu |
Ordre d'exécution dans un pipeline CI/CD
La logique : si un test rapide échoue, inutile de lancer la DB ou le navigateur. On a déjà trouvé le problème.
| Étape | Type | Exemple concret |
|---|---|---|
| 1 | Linting | ESLint détecte une variable non utilisée |
| 2 | Unitaires (mocks) | Le calcul de taxe retourne le bon résultat |
| 3 | Intégration | L'endpoint POST /users sauvegarde bien en DB |
| 4 | E2E | L'utilisateur crée un compte, soumet le formulaire et reçoit sa confirmation |
Test unitaire vs Mock : deux concepts différents
Test unitaire = un type de test (tester une seule fonction, isolée).
Mock = un outil/technique pour simuler des dépendances (DB, API, service email) par une fausse version contrôlée.
Un mock remplace un composant réel par une fausse version qui se comporte comme tu le décides :
# Sans mock — appel RÉEL à la DB
def test_get_user():
user = db.get_user(1) # touche vraiment la base de données
# Avec mock — DB simulée
def test_get_user():
db.get_user = Mock(return_value={"id": 1, "nom": "Nicolas"})
user = db.get_user(1) # ne touche RIEN, retourne ta valeur inventée
| Situation | Test unitaire ? | Mock ? |
|---|---|---|
Tester calcul_taxe(100) | Oui | Non (pas de dépendance) |
Tester sauvegarder_user() sans DB | Oui | Oui (on mock la DB) |
| Tester avec une vraie DB | Non (c'est de l'intégration) | Non |
Où valider les données ?
Framework de tests unitaires et d'intégration pour Python. Le plus utilisé dans l'écosystème.
Concepts clés
Fixtures : préparer les données de test
Une fixture prépare quelque chose dont tes tests ont besoin. PyTest l'injecte automatiquement en regardant le nom du paramètre.
# Sans fixture : répétition partout
def test_un():
db = creer_ma_db() # dupliqué
service = Service(db) # dupliqué
# Avec fixture : PyTest injecte automatiquement
@pytest.fixture
def service():
db = creer_ma_db()
return Service(db)
def test_un(service): # PyTest appelle service() pour toi
...
def test_deux(service): # recréé frais à chaque test
...
MagicMock : objet fantôme
MagicMock() crée un objet sur lequel tu peux appeler n'importe quelle méthode sans erreur. Tu configures ce que chaque méthode retourne :
@pytest.fixture
def mock_db():
db = MagicMock()
# return_value : retourne toujours la même chose
db.find_by_id.return_value = {"id": 1, "name": "Alice"}
# db.find_by_id(1) --> {"id": 1, "name": "Alice"}
# db.find_by_id(999) --> {"id": 1, "name": "Alice"} (pareil)
# side_effect : exécute une fonction au lieu de retourner une valeur fixe
db.insert.side_effect = lambda data: {**data, "id": 42}
# db.insert({"name": "Eve"}) --> {"name": "Eve", "id": 42}
return db
@pytest.mark.parametrize : tester plusieurs cas
# Génère 3 tests à partir d'un seul
@pytest.mark.parametrize("bad_confidence", [-0.1, 1.1, 50])
def test_rejects_out_of_range(validator, valid_record, bad_confidence):
valid_record["confidence"] = bad_confidence
errors = validator.validate_record(valid_record)
assert "Confidence must be between 0 and 1" in errors
pytest.approx : comparer des floats
# 0.1 + 0.2 == 0.3 retourne False en Python (imprécision flottante)
# pytest.approx tolère une petite marge
assert result["error_rate"] == pytest.approx(0.4)
tmp_path : fichiers temporaires
# Fixture fournie par PyTest. Crée un dossier temporaire, supprimé après le test.
@pytest.fixture
def sample_dataset(tmp_path):
filepath = tmp_path / "dataset.json"
filepath.write_text(json.dumps([{"text": "Hello", "label": "positive"}]))
return filepath
pip install pytestpytest -v (verbose)pytest --cov=src (avec couverture)
Pilote un vrai navigateur (Chrome, Firefox, Safari) et simule un utilisateur. Vocation principale : tests E2E.
Mais pas que du E2E
| Usage | Exemple |
|---|---|
| E2E classique | Tester un flow de login complet |
| Tests d'API | page.request.get("/api/users") |
| Visual regression | Screenshot avant/après, comparer les pixels |
| Accessibilité | Intégration avec axe-core |
Exemple : test de login
from playwright.sync_api import Page, expect
def test_successful_login(page: Page):
# Naviguer
page.goto("https://monsite.com/login")
# Remplir le formulaire
page.fill("#username", "student")
page.fill("#password", "Password123")
page.click("#submit")
# Vérifier
expect(page).to_have_url(re.compile(r".*/logged-in-successfully/"))
expect(page.locator("h1")).to_contain_text("Logged In Successfully")
Exemple : app TodoMVC (CRUD complet)
def test_complete_todo(page: Page):
page.goto("https://demo.playwright.dev/todomvc/")
# CREATE — ajouter une tâche
page.locator(".new-todo").fill("Apprendre Playwright")
page.locator(".new-todo").press("Enter")
# READ — vérifier qu'elle s'affiche
expect(page.locator(".todo-list li")).to_have_count(1)
expect(page.locator(".todo-list li").nth(0)).to_contain_text("Apprendre Playwright")
# UPDATE — cocher comme complété
page.locator(".todo-list li").nth(0).locator(".toggle").check()
expect(page.locator(".todo-list li").nth(0)).to_have_class("completed")
# DELETE — supprimer la tâche (hover + clic sur le X)
page.locator(".todo-list li").nth(0).hover()
page.locator(".todo-list li").nth(0).locator(".destroy").click()
expect(page.locator(".todo-list li")).to_have_count(0)
Visual regression intégré
def test_homepage_visual(page: Page):
page.goto("https://monsite.com")
expect(page).to_have_screenshot("homepage.png")
# 1er run : sauvegarde le screenshot de référence
# Runs suivants : compare pixel par pixel
Exemple complet : Playwright + Fixtures + Mock réseau
En vrai projet, Playwright ne tourne jamais seul. Il s'appuie sur des fixtures (préparer l'état) et des mocks réseau (simuler l'API). Voici comment les trois fonctionnent ensemble.
conftest.py : les fixtures partagées
import pytest
from playwright.sync_api import Page
# FIXTURE → prépare une page déjà connectée
@pytest.fixture
def logged_in_page(page: Page):
page.goto("https://monsite.com/login")
page.fill("#username", "testuser")
page.fill("#password", "Test123!")
page.click("#submit")
page.wait_for_url("**/dashboard")
yield page
# FIXTURE + MOCK RÉSEAU → simule l'API sans backend
@pytest.fixture
def page_with_fake_api(page: Page):
# page.route intercepte les appels réseau AVANT qu'ils partent
page.route("**/api/todos", lambda route: route.fulfill(
status=200,
json=[
{"id": 1, "title": "Tâche 1", "completed": False},
{"id": 2, "title": "Tâche 2", "completed": True},
]
))
page.goto("https://monsite.com/todos")
yield page
test_todos.py : les tests
# Test avec VRAI backend (fixture seule, pas de mock)
def test_create_todo(logged_in_page):
logged_in_page.locator(".new-todo").fill("Ma tâche")
logged_in_page.locator(".new-todo").press("Enter")
expect(logged_in_page.locator(".todo-list li")).to_have_count(1)
# Test avec FAUX backend (fixture + mock réseau)
def test_display_todos(page_with_fake_api):
# L'API est mockée → pas besoin de vrai backend
expect(page_with_fake_api.locator(".todo-list li")).to_have_count(2)
expect(page_with_fake_api.locator(".todo-list li").nth(0)).to_contain_text("Tâche 1")
# Test erreur serveur (mock une erreur 500)
def test_api_error(page: Page):
page.route("**/api/todos", lambda route: route.fulfill(
status=500,
json={"error": "Server Error"}
))
page.goto("https://monsite.com/todos")
expect(page.locator(".error-message")).to_contain_text("Erreur")
Qui fait quoi
| Outil | Rôle dans l'exemple |
|---|---|
| Playwright | Pilote le navigateur (clic, remplir, naviguer) |
| Fixture | Prépare l'état (login, config de mock) avant chaque test |
Mock réseau (page.route) | Intercepte les appels API pour ne pas dépendre du vrai backend |
| PyTest | Lance tout, injecte les fixtures, rapporte les résultats |
Quand mocker, quand ne pas mocker
| Scénario | Approche |
|---|---|
| Tester le parcours utilisateur complet | Vrai backend, pas de mock |
| Tester juste le frontend (affichage, boutons) | Mock l'API |
| Tester les cas d'erreur (500, timeout) | Mock l'API avec erreur |
| Backend pas encore prêt | Mock l'API |
| CI rapide (pas de DB, pas de serveur) | Mock l'API |
pip install pytest-playwrightplaywright installpytest -v
Framework E2E JavaScript. Similaire à Playwright mais avec une syntaxe chaînée et l'interception réseau intégrée.
describe("Login Flow", () => {
it("should login successfully", () => {
cy.visit("/login");
cy.get("#username").type("student");
cy.get("#password").type("Password123");
cy.get("#submit").click();
cy.url().should("include", "/logged-in-successfully");
cy.get("h1").should("contain.text", "Logged In Successfully");
});
});
Interception réseau (cy.intercept)
Simuler des réponses API sans dépendre du backend :
// Intercepter l'appel et forcer une erreur 500
cy.intercept("POST", "/api/login", {
statusCode: 500,
body: { error: "Internal Server Error" },
}).as("loginRequest");
// Intercepter avec un délai pour tester le loading
cy.intercept("POST", "/api/login", {
statusCode: 200,
body: { token: "fake-jwt" },
delay: 2000, // simule un réseau lent
});
Tests d'API directement
cy.request("GET", "/api/posts").then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an("array");
expect(response.duration).to.be.lessThan(2000); // perf check
});
C'est PyTest mais version JS. Même rôle (tests unitaires et d'intégration), syntaxe différente.
| PyTest (Python) | Jest (JavaScript) | |
|---|---|---|
| Mocks | MagicMock() | jest.fn() |
| Parametrize | @pytest.mark.parametrize | test.each |
| Setup | @pytest.fixture | beforeEach |
| Assertions | assert x == y | expect(x).toBe(y) |
| Coverage | pytest-cov | Intégré (--coverage) |
| Snapshots | Non | Oui (toMatchSnapshot()) |
// Mocks avec jest.fn()
const mockApiClient = {
get: jest.fn(),
post: jest.fn(),
};
mockApiClient.get.mockResolvedValue({ data: { id: 1, name: "Alice" } });
// test.each : équivalent de parametrize
it.each([
[{ name: "", email: "test@test.com" }, "Name and email are required"],
[{ name: "Bob", email: "not-email" }, "Invalid email format"],
])("should reject: %o", async (input, expectedError) => {
await expect(service.createUser(input)).rejects.toThrow(expectedError);
});
// Snapshot : sauvegarde la structure au 1er run, compare ensuite
expect(result).toMatchSnapshot();
Pourquoi mocker ?
Rapide
Pas de connexion DB, pas de réseau. Les tests tournent en millisecondes.
Isole
Si le test échoue, c'est le code qui a un bug, pas la DB qui est down.
Prévisible
Les données retournées sont toujours les mêmes. Pas de "ça marche sur ma machine".
return_value vs side_effect
# return_value : toujours la même chose
mock.find.return_value = {"id": 1}
mock.find(1) # --> {"id": 1}
mock.find(999) # --> {"id": 1} (pareil)
# side_effect : exécute une fonction (la réponse dépend de l'input)
mock.insert.side_effect = lambda data: {**data, "id": 42}
mock.insert({"name": "Eve"}) # --> {"name": "Eve", "id": 42}
mock.insert({"name": "Bob"}) # --> {"name": "Bob", "id": 42}
Le problème que async résout
# SYNCHRONE : chaque ligne bloque
result1 = appeler_api_google() # attend 500ms...
result2 = appeler_api_weather() # attend 500ms...
result3 = lire_fichier_gros() # attend 200ms...
# Total : ~1200ms
# ASYNCHRONE : tout en parallèle
result1, result2, result3 = await asyncio.gather(
appeler_api_google(),
appeler_api_weather(),
lire_fichier_gros(),
)
# Total : ~500ms
Quand utiliser async ?
| Situation | Async utile ? | Pourquoi |
|---|---|---|
| Appels API / réseau | Oui | Le réseau est lent, on attend beaucoup |
| Requêtes base de données | Oui | Même raison |
| Lecture de fichiers | Oui | Attente disque |
| Calcul mathématique | Non | Le CPU travaille, rien à attendre |
| Script simple | Non | Complexité inutile |
Syntaxe Python
# Fonction normale
def get_user(id):
response = requests.get(f"/users/{id}") # bloque ici
return response.json()
# Version async
async def get_user(id):
response = await httpx.get(f"/users/{id}") # libère le thread
return response.json()
Tester du code async avec PyTest
import pytest
from unittest.mock import AsyncMock
@pytest.mark.asyncio
async def test_get_user():
mock_client = AsyncMock() # pas MagicMock, AsyncMock !
mock_client.get.return_value.json.return_value = {"id": 1, "name": "Alice"}
service = AsyncUserService(mock_client)
result = await service.get_user(1)
assert result["name"] == "Alice"
Test generation avec LLMs
Passer du code à un LLM (Claude, Copilot, Qodo) pour générer les tests. Le rôle QA : piloter et valider ce que l'IA produit, pas accepter aveuglément.
Mutation testing
On casse volontairement le code, on vérifie que les tests détectent la casse. Mesure la vraie qualité des tests.
Visual regression
Screenshot avant/après, comparaison pixel par pixel. Playwright le fait nativement avec to_have_screenshot().
Anomaly detection
ML détecte les déviations : temps d'exécution anormal, taux d'erreur qui monte, mémoire qui dérive. Azure Monitor le fait.
Mutation testing : comment ça marche
# Ton code original
def is_adult(age):
return age >= 18
# Ton test
def test_is_adult():
assert is_adult(18) == True
assert is_adult(17) == False
# mutmut SABOTE ton code temporairement :
# Mutant 1 : return age > 18 → test_is_adult échoue → ✅ test solide
# Mutant 2 : return age >= 19 → test_is_adult échoue → ✅ test solide
# Mutant 3 : return age <= 18 → test_is_adult échoue → ✅ test solide
# OBJECTIF : on ne teste pas is_adult
# on teste si test_is_adult est assez bon pour détecter des bugs
pip install mutmut puis mutmut run
Comparaison des outils IA pour les tests
| Qodo (ex-CodiumAI) | Claude Code | Copilot | |
|---|---|---|---|
| Spécialité | Génération de tests uniquement | Tout (tests, code, debug) | Autocomplétion inline |
| Approche | Analyse la fonction, génère N scénarios auto | Tu prompts, il comprend le contexte large | Complète ligne par ligne |
| Force | Rapide, structuré, clé en main | Comprend l'architecture complexe | Zero friction dans l'IDE |
| Faiblesse | Limité aux tests unitaires | Faut savoir prompter | Rate les edge cases |
C'est quoi ?
Automatisation intégrée à GitHub. A chaque push ou PR, il exécute des tâches automatiquement. Tout est configuré dans un fichier YAML dans le repo.
Concepts clés
| Concept | Description |
|---|---|
| Workflow | Fichier YAML dans .github/workflows/. Déclenché par un événement. |
| Event / Trigger | Ce qui demarre le workflow : push, pull_request, schedule (cron) |
| Job | Un groupe de steps. Par défaut en parallèle. needs: crée des dépendances. |
| Step | Une commande (run:) ou une action réutilisable (uses:) |
| Runner | La machine qui exécute (Ubuntu, Windows, macOS fournis par GitHub) |
| Action | Brique réutilisable : actions/checkout@v4, actions/setup-node@v4, etc. |
| Secret | Variable sensible : ${{ secrets.DOCKER_PASSWORD }} |
Exemple complet : pipeline PetPlanner
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
# Les 3 premiers tournent EN PARALLELE (pas de needs)
frontend-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: cd web-next && npm ci && npm run lint && npm test
backend-tests:
runs-on: ubuntu-latest
# GitHub Actions lance un vrai postgres pour ce job
services:
postgres:
image: postgres:15
env: { POSTGRES_DB: petplanner_test, ... }
steps:
- run: npx prisma migrate deploy
- run: npm run test:ci
security-audit:
runs-on: ubuntu-latest
steps:
- run: npm audit --audit-level=high
- uses: aquasecurity/trivy-action@master # scan vulnérabilités
# Deploy attend que les 3 passent
deploy-production:
needs: [frontend-tests, backend-tests, security-audit]
if: github.ref == 'refs/heads/main'
steps:
- run: docker build && docker push
Flow d'exécution
Quality gates
Le principe du gardien : si la qualité n'est pas au niveau, le code ne passe pas.
- Couverture minimum : le pipeline échoue si coverage < 80%
- Zero vulnerabilite critique :
npm audit+ Trivy bloquent le merge - Linting sans erreur : ruff/eslint doivent passer
- Branch protection : GitHub > Settings > "Require status checks to pass"
needs: qui crée l'ordre dans le CI, pas depends_on.
Au lieu de prier pour que le site tienne, on mesure la fiabilité, on fixe des objectifs, et on automatise la réponse aux problèmes.
SLI / SLO / SLA
SLI (Indicator)
La mesure brute.
"99.2% des requêtes répondent en moins de 200ms"
SLO (Objective)
L'objectif interne.
"On vise 99.5% des requêtes sous 200ms"
SLA (Agreement)
La promesse au client (avec pénalités).
"On garantit 99.0%, sinon remboursement"
Error budget
L'idée la plus contre-intuitive du SRE. Si ton SLO est 99.5% de disponibilité par mois :
30 jours x 24h x 60min = 43 200 minutes par mois
0.5% de 43 200 = 216 minutes = 3h36 de downtime autorisé
Budget restant ? --> on peut déployer, expérimenter, prendre des risques
Budget épuisé ? --> on freeze les déploiements, on corrige la fiabilité
Ça transforme le débat "devs veulent shipper vite vs ops veulent la stabilité" en décision basée sur des données.
Gestion des incidents
La capacité à comprendre ce qui se passe à l'intérieur de ton système en regardant ce qui sort.
Pilier 1 : Logs ("qu'est-ce qui s'est passé ?")
Un événement ponctuel, avec du contexte.
[2026-04-07 14:23:01] ERROR UserService: Failed to create user
email=test@test.com
error="duplicate key constraint"
request_id=abc-123
duration_ms=45
En production, on ne peut pas SSH sur le serveur. Il faut centraliser :
Pilier 2 : Métriques ("comment ça va en ce moment ?")
Des nombres qui évoluent dans le temps, affichés sur des dashboards.
# Ce que ton app expose sur GET /metrics (format Prometheus)
http_requests_total{method="GET", status="200"} 15234
http_requests_total{method="GET", status="500"} 12
http_request_duration_seconds{quantile="0.99"} 0.45
memory_usage_bytes 524288000
Comment Prometheus collecte
C'est du pull : ton app expose un endpoint /metrics avec du texte brut. Toutes les 15 secondes, Prometheus fait un GET dessus et stocke les valeurs.
Pilier 3 : Traces ("pourquoi c'est lent ?")
Une trace suit une requête à travers tous les services. Chaque morceau = un span.
Exemple code (Node.js)
const { trace } = require("@opentelemetry/api");
const tracer = trace.getTracer("petplanner-api");
async function getAppointments(userId) {
return tracer.startActiveSpan("get_appointments", async (span) => {
span.setAttribute("user_id", userId);
const results = await prisma.appointment.findMany({
where: { userId }
});
span.end();
return results;
});
}
Exemple code (Python)
from opentelemetry import trace
tracer = trace.get_tracer("petplanner")
async def get_appointments(user_id):
# Span parent : la requête complète
with tracer.start_as_current_span("get_appointments") as span:
span.set_attribute("user_id", user_id)
# Span enfant : la requête DB (on verra sa durée dans Jaeger)
with tracer.start_as_current_span("db_query"):
results = await db.query(
"SELECT * FROM appointments WHERE user_id = $1", user_id
)
return results
Comment les 3 piliers se complètent
- Métriques seules : "ca va mal" mais tu ne sais pas pourquoi
- Logs seuls : une montagne de texte, impossible de trouver l'aiguille
- Traces seules : tu vois le chemin mais pas le contexte
La stack complète
Radio-Canada est sur Azure. Pas besoin de tout maîtriser, mais comprendre les services principaux et savoir naviguer dans le portail.
Services à connaître
Compute (faire tourner du code)
- App Service : héberger une app web/API. Équivalent managé de "docker run" sans gérer le serveur.
- Azure Functions : code serverless. Tu écris une fonction, Azure la lance quand un événement arrive. Tu paies par exécution.
- ACI : lancer un conteneur Docker sans gérer de serveur.
- AKS : orchestration de conteneurs à grande échelle.
DevOps & CI/CD
- Azure DevOps Repos : dépôt Git (alternative à GitHub)
- Azure Pipelines : CI/CD (alternative à GitHub Actions)
- Azure Boards : gestion de projet (tickets, sprints, kanban)
Monitoring & Observabilité
- Azure Monitor : collecte métriques et logs
- Application Insights : monitoring applicatif (traces, métriques, exceptions)
- Log Analytics : requêter les logs avec KQL
Autres services utiles
- Azure SQL / Cosmos DB : bases de données managées
- Azure Key Vault : stocker les secrets
- Azure CDN : distribuer le contenu statique
- Azure Cognitive Services : APIs IA préconstruites
App Service vs Functions vs ACI vs AKS
| Service | Quand l'utiliser | Analogie |
|---|---|---|
| App Service | Une app web classique qui tourne en permanence | Un serveur dédié, sans le gérer |
| Functions | Du code qui répond à des événements ponctuels | Un employé à la tâche, payé à l'heure |
| ACI | Un conteneur à lancer sans infra | docker run dans le cloud |
| AKS | Dizaines de microservices à orchestrer | Un chef d'orchestre pour conteneurs |
Azure Pipelines vs GitHub Actions
Même concept, syntaxe légèrement différente :
# GitHub Actions
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
# Azure Pipelines
stages:
- stage: Build
jobs:
- job: BuildJob
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
- script: npm test
Scrum en un schéma
Les cérémonies Scrum
| Cérémonie | Quand | Duree | But |
|---|---|---|---|
| Sprint Planning | Début de sprint | 2-4h | Choisir quoi faire pendant le sprint |
| Daily Standup | Chaque jour | 15 min max | Hier / Aujourd'hui / Blocages |
| Sprint Review | Fin de sprint | 1-2h | Montrer ce qui a été fait (demo) |
| Sprint Retro | Fin de sprint | 1-1.5h | Qu'est-ce qu'on améliore ? |
Les roles Scrum
Product Owner
Decide QUOI construire. Priorise le backlog. Représente les utilisateurs.
Scrum Master
Facilite le processus. Enleve les obstacles. Ne dit pas QUOI faire.
Dev Team
Decide COMMENT construire. Auto-organisée. Inclut les devs, QA, designers.
Kanban : l'alternative
Pas de sprints fixes. Un flux continu avec des limites de travail en cours (WIP limits).
| Scrum | Kanban | |
|---|---|---|
| Cadence | Sprints fixes (2-4 semaines) | Flux continu |
| Roles | PO, Scrum Master, Team | Pas de rôles imposés |
| Changements | Pas de changement en cours de sprint | Nouvelles tâches à tout moment |
| Indicateur clé | Velocity (points par sprint) | Lead time (temps de traversée) |
| Idéal pour | Projets avec releases planifiées | Maintenance, support, ops |
Définition of Done (DoD)
Checklist partagée par l'équipe pour définir quand une tâche est VRAIMENT terminée :
- Code écrit et révisé (code review)
- Tests unitaires écrits et passent
- Tests d'intégration passent
- Documentation mise à jour
- Pipeline CI vert
- Déployé en staging et validé
QA en Agile : Shift-Left Testing
Tester un système qui intègre de l'IA est différent de tester du CRUD classique. Le modèle n'est pas déterministe : la même entrée peut donner des résultats légèrement différents.
Les 5 types de tests pour l'IA
1. Data validation tests (données d'entrée)
Vérifier que les données qui alimentent le modèle sont propres.
def test_training_data_schema():
"""Chaque enregistrement a les bons champs et types."""
for record in training_data:
assert "text" in record
assert "label" in record
assert record["label"] in ["positive", "negative", "neutral"]
def test_data_distribution():
"""Les classes sont suffisamment équilibrées."""
counts = Counter(r["label"] for r in training_data)
# Aucune classe ne doit être < 20% du total
for label, count in counts.items():
assert count / len(training_data) > 0.2, \
f"Classe {label} sous-représentée : {count}"
2. Model performance regression tests
Vérifier que le modèle v2 n'est pas moins bon que le v1.
def test_model_accuracy_above_threshold():
"""Le modèle maintient un minimum de performance."""
predictions = model.predict(reference_dataset)
accuracy = compute_accuracy(predictions, reference_labels)
assert accuracy >= 0.85, \
f"Accuracy {accuracy:.2f} en dessous du seuil 0.85"
def test_model_latency():
"""Le modèle répond assez vite pour la production."""
start = time.time()
model.predict(["test input"])
duration = time.time() - start
assert duration < 0.5, \
f"Prédiction trop lente : {duration:.2f}s"
3. Input/Output validation (en production)
Vérifier que les prédictions du modèle sont cohérentes.
def test_prediction_format():
"""La sortie du modèle a la bonne structure."""
result = model.predict("test input")
assert "label" in result
assert "confidence" in result
assert result["label"] in ["positive", "negative", "neutral"]
assert 0.0 <= result["confidence"] <= 1.0
4. A/B testing et Canary deployments
A/B testing : comparer deux versions en parallèle sur des groupes d'utilisateurs différents.
Canary : déployer progressivement (5% puis 25% puis 100%), rollback automatique si les métriques se dégradent.
5. Chaos Engineering
Introduire des pannes volontairement pour vérifier que le système résiste.
Netflix Chaos Monkey
Éteint aléatoirement des serveurs en production. Si le service continue de marcher, l'architecture est résiliente.
Pour un système IA
- Que se passe-t-il si le modèle IA est indisponible ? (fallback ?)
- Que se passe-t-il si le modèle retourne des absurdités ? (validation ?)
- Que se passe-t-il si la latence du modèle passe de 100ms à 10s ? (timeout ?)
def test_graceful_degradation_when_model_down():
"""Si le modèle IA est down, le système retourne un fallback."""
# Simuler le modèle indisponible
with mock.patch("app.ml_service.predict", side_effect=TimeoutError):
response = client.get("/api/recommendations")
# Le système ne doit PAS crasher (pas de 500)
assert response.status_code == 200
# Il retourne des recommendations par défaut
assert response.json()["source"] == "fallback"
Résumé : les tests par couche
Frameworks de test
| Concept | PyTest | Playwright | Cypress | Jest |
|---|---|---|---|---|
| Fixtures / Setup | @pytest.fixture | page: Page | beforeEach | beforeEach |
| Mocks | MagicMock | - | cy.intercept | jest.fn() |
| Multi-cas | parametrize | - | - | test.each |
| Assertions | assert | expect(locator) | .should() | expect() |
| Snapshots | - | Screenshots | - | toMatchSnapshot() |
CI/CD
| Concept | À retenir |
|---|---|
| Pipeline as code | YAML dans le repo, versionné avec git |
| Stages | lint > test > security > deploy (needs: crée l'ordre) |
| Quality gates | Couverture min, zero vuln critique, lint propre |
| Branch protection | Bloquer le merge si un check échoue |
| compose vs CI | compose = QUOI tourner, CI = QUAND et DANS QUEL ORDRE |
SRE & Observabilité
| Concept | À retenir | Exemple |
|---|---|---|
| SLI | La mesure | "99.2% sous 200ms" |
| SLO | L'objectif | "On vise 99.5%" |
| Error budget | Le droit à l'erreur | "3h36 de downtime/mois" |
| Logs | Quoi s'est passé | "duplicate key sur user X" |
| Métriques | Combien, en ce moment | "150 req/s, 0.8% erreurs" |
| Traces | Où et pourquoi | "La requête SQL prend 12s" |
| OpenTelemetry | Le standard | SDK unique pour les 3 piliers |
| Prometheus | Collecte métriques (pull) | GET /metrics toutes les 15s |
| Grafana | Dashboards + alertes | Graphes, seuils, notifications |
| Jaeger | Visualise les traces | Timeline des spans |
Azure
| Service | À retenir |
|---|---|
| App Service | Héberger une app web/API |
| Functions | Code serverless, payé à l'exécution |
| AKS | Kubernetes managé pour microservices |
| Azure DevOps | Repos + Pipelines + Boards (alternative GitHub) |
| Application Insights | Monitoring applicatif (traces + métriques) |
| Key Vault | Stocker les secrets |
Agile
| Concept | À retenir |
|---|---|
| Sprint | Itération de 2-4 semaines |
| Daily standup | 15 min : hier / aujourd'hui / blocages |
| Définition of Done | Checklist partagée : code review, tests, CI vert |
| Shift-Left | Tester le plus tôt possible, QA dès le design |
| Kanban | Flux continu avec WIP limits, pas de sprints |
Tests pour systemes IA
| Type | Vérifie quoi |
|---|---|
| Data validation | Données propres, schéma correct, distribution équilibrée |
| Performance regression | Accuracy et latence au-dessus des seuils |
| I/O validation | Format des prédictions correct et cohérent |
| A/B testing / Canary | Déploiement progressif, comparaison des versions |
| Chaos engineering | Le système résiste si le modèle est down |
Phrase clé pour l'entrevue
OpenTelemetry dans le code pour instrumenter les 3 piliers. Prometheus + Grafana pour les métriques et les alertes. Un collecteur de logs centralisé (ELK ou Azure Monitor). Jaeger pour les traces distribuées. Le tout relié par des trace_id et request_id pour corréler les trois signaux.
Le dossier mini-projet-qa/ contient un projet complet qui démontre toutes les compétences. Tu peux le lancer localement.
Setup en 5 minutes
# 1. Creer l'environnement
cd exemples_qa/mini-projet-qa
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
# 2. Lancer l'API
uvicorn app.main:app --reload --port 8000
# --> http://localhost:8000/docs (Swagger UI)
# 3. Tester
pytest -v # tests unitaires + intégration
pytest --cov=app --cov-report=html -v # avec couverture
pytest tests/e2e/ -v # tests e2e (API doit tourner)
# 4. Mutation testing
mutmut run --paths-to-mutate=app/
mutmut results
# 5. Linting
ruff check .
ruff format --check .
Structure
Ce que ça démontre
| Compétence du poste | Où dans le projet |
|---|---|
| Tests unitaires (PyTest) | tests/test_sentiment.py |
| Tests d'intégration | tests/test_api.py |
| Tests e2e (Playwright) | tests/e2e/test_api_e2e.py |
| Validation de données IA | test_sentiment.py (schéma, distribution) |
| CI/CD (GitHub Actions) | .github/workflows/ci.yml |
| Quality gates | Coverage > 80%, linting, security scan |
| Logs structurés | app/logger.py (JSON, request_id) |
| Métriques | /metrics endpoint |
| Health check | /health endpoint |
| Mutation testing | Config mutmut dans pyproject.toml |
TypeScript = JavaScript + des types. Le compilateur attrape les bugs avant l'exécution. C'est le standard en entreprise pour les projets JS/React/Node.
Typage de base
// Variables typées
let name: string = "Alice";
let age: number = 30;
let active: boolean = true;
let tags: string[] = ["qa", "dev"];
// Fonctions typées : paramètres ET retour
function add(a: number, b: number): number {
return a + b;
}
// Paramètre optionnel (?) et valeur par défaut
function greet(name: string, greeting: string = "Bonjour"): string {
return `${greeting}, ${name}`;
}
Interfaces : décrire la forme d'un objet
C'est le concept central de TypeScript. Une interface décrit quels champs un objet doit avoir et quel type chaque champ a.
// Définir la forme
interface User {
id: number;
name: string;
email: string;
role?: string; // optionnel
}
// TypeScript vérifie que l'objet respecte l'interface
const user: User = {
id: 1,
name: "Alice",
email: "alice@test.com",
};
// Erreur à la compilation si un champ manque ou a le mauvais type
const bad: User = { id: "abc", name: "Bob" };
// ^^^ Type 'string' is not assignable to type 'number'
// Property 'email' is missing
Type vs Interface
interface | type | |
|---|---|---|
| Objets | Oui (usage principal) | Oui |
| Union / Intersection | Non | type Status = "active" | "inactive" |
| Extend | extends | & (intersection) |
| Declaration merging | Oui | Non |
// Type union : une valeur parmi plusieurs
type Status = "pending" | "approved" | "rejected";
// Type intersection : combiner deux types
type Admin = User & { permissions: string[] };
// Interface extension
interface Admin extends User {
permissions: string[];
}
Generics : types réutilisables
Un generic permet de créer des fonctions/types qui marchent avec n'importe quel type tout en gardant la sécurité.
// Sans generic : on perd le type
function first(arr: any[]): any { return arr[0]; }
// Avec generic : le type est préservé
function first<T>(arr: T[]): T { return arr[0]; }
first([1, 2, 3]); // retourne number
first(["a", "b"]); // retourne string
// Generic avec interface
interface ApiResponse<T> {
data: T;
status: number;
error?: string;
}
const res: ApiResponse<User[]> = await fetchUsers();
Comparaison JavaScript vs TypeScript
// JavaScript : aucune vérification
function getUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// Bug possible : getUser("abc") compile sans erreur
// TypeScript : erreur attrapée à la compilation
async function getUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
// getUser("abc") → erreur : Argument of type 'string' is not assignable to 'number'
interface pour les objets, type pour les unions et les alias. En cas de doute, interface.
Dataclass = struct C++
Conteneur de données. Python génère __init__, __repr__, __eq__ automatiquement.
@dataclass
class Article:
id: str
title: str
is_published: bool = False
# Immutable (comme const struct)
@dataclass(frozen=True)
class Point:
x: int
y: int
# p = Point(1, 2)
# p.x = 5 → FrozenInstanceError
| Cas | Outil |
|---|---|
| Stocker des données (struct) | @dataclass |
| Données immuables | @dataclass(frozen=True) |
| Valeurs fixes/constantes | Enum |
Enum = valeurs fixes connues
Set fini de choix possibles. Toujours hérite de Enum.
from enum import Enum
class Category(Enum):
SPORT = "sport"
TECH = "tech"
NEWS = "news"
# Peut ajouter des méthodes
def is_tech(self):
return self == Category.TECH
Enum
Instances fixes définies dans le code
Category("musique") → ValueError
Frozen Dataclass
Instances illimitées, mais immuables
Point(99, 42) → OK
Héritage (Mère → Fille)
class Fille(Mere) = la fille récupère tout de la mère.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
# SURCHARGE → remplace speak()
def speak(self):
return "Woof"
# EXTENSION → nouveau comportement
def fetch(self):
return "Va chercher!"
class Cat(Animal):
def __init__(self, name, indoor):
super().__init__(name) # appelle Animal.__init__
self.indoor = indoor
| Mécanisme | Description |
|---|---|
| Héritage | La fille récupère tout de la mère |
| Surcharge | La fille remplace une méthode |
| Extension | La fille ajoute des méthodes |
| super() | La fille appelle la mère |
class Shape(ABC) + @abstractmethod → les filles doivent implémenter.
Syntaxe class X(Y) = toujours héritage
class X: # hérite de object (implicite)
class X(Animal): # hérite de Animal
class X(Enum): # hérite de Enum → c'est un enum
class X(ABC): # hérite de ABC → classe abstraite
class X(A, B): # héritage multiple (A prioritaire)
@dataclass n'est PAS de l'héritage, c'est un décorateur.
@dataclass class X: → hérite de object, pas de dataclass.
Yield = pause la fonction
Comme Fibonacci : yield met en pause et donne une valeur. next() reprend.
# Générateur classique
def fibonacci():
a, b = 0, 1
while True:
yield a # pause, donne a
a, b = b, a + b # reprend ici au next()
# Dans une fixture → pytest fait le next() pour toi
@pytest.fixture
def db():
conn = create_connection() # SETUP
yield conn # PAUSE → test s'exécute
conn.close() # TEARDOWN (pytest reprend)
Fixture = préparer les données de test
Pas de l'héritage ! C'est de l'injection par nom : pytest voit le nom du paramètre et injecte la fixture correspondante.
@pytest.fixture
def user():
return {"name": "Alice"}
def test_name(user): # "user" → pytest trouve la fixture
assert user["name"] == "Alice"
# Fixture qui utilise une autre fixture
@pytest.fixture
def service(mock_db):
return UserService(db=mock_db)
Mock = objet faux pour tester ta logique
Fixture (vrai)
Prépare un vrai objet
On teste le résultat — "Ça a marché ?"
Mock (faux)
Simule un objet en carton
On teste le comportement — "Ça a été appelé ?"
from unittest.mock import MagicMock, patch
fake_db = MagicMock()
# Retour fixe
fake_db.find.return_value = {"id": 1}
# Retour dynamique
fake_db.save.side_effect = lambda x: x["id"]
# Simule une erreur
fake_db.delete.side_effect = Exception("boom")
# Vérifier les appels
fake_db.find.assert_called_once_with(1)
fake_db.save.assert_called()
# patch = remplace un vrai objet temporairement
@patch("myapp.service.requests.get")
def test_fetch(mock_get):
mock_get.return_value.json.return_value = {"data": 42}
result = fetch_data()
mock_get.assert_called_once()
CRUD = les 4 gestes avec des données
Create Read Update Delete — toute app qui gère des données fait ces 4 opérations.
| Contexte | C | R | U | D |
|---|---|---|---|---|
| Gmail | Écrire | Lire | Modifier brouillon | Supprimer |
| Poster | Voir feed | Éditer description | Supprimer post | |
| SQL | INSERT | SELECT | UPDATE | DELETE |
| API REST | POST | GET | PUT | DELETE |
Test Unitaire (mock) vs Intégration (BDD)
# UNITAIRE → teste ta logique, pas la BDD
def test_create_user_unit():
fake_db = MagicMock()
service = UserService(db=fake_db)
service.create("Alice", age=30)
fake_db.insert.assert_called_once_with(
{"name": "Alice", "age": 30}
)
# INTEGRATION → teste la vraie connexion
def test_create_user_integration(real_db):
service = UserService(db=real_db)
service.create("Alice", age=30)
assert real_db.find("Alice")["age"] == 30
| Unitaire (mock) | Intégration (BDD) | |
|---|---|---|
| Vitesse | Instantané | Lent |
| Teste | Ta logique / ton code | La vraie connexion |
| Quand ça casse | Bug dans ton code | Bug config / schema / réseau |
| Quantité | Beaucoup | Peu |
Rappel du modèle mental OpenTelemetry
OTel est un standard + une boîte à outils pour produire de la télémétrie (traces, métriques, logs). Ce n'est pas un backend, il ne stocke rien.
| Composant | Rôle |
|---|---|
| API | Interface abstraite utilisée dans le code applicatif |
| SDK | Implémentation concrète : crée spans, métriques, logs |
| OTLP | Protocole filaire (gRPC sur 4317, HTTP sur 4318) entre SDK ↔ Collector ↔ backend |
| Collector | Process séparé : receivers → processors → exporters |
Q1 — Métriques, logs, traces : quand utiliser lequel ?
| Type | Question à laquelle ça répond | Coût |
|---|---|---|
| Métriques | "Combien / à quelle vitesse / depuis quand c'est cassé" | Faible (agrégé) |
| Logs | "Qu'est-ce qui s'est passé pour CETTE requête" | Élevé (indexé) |
| Traces | "Où est passé le temps dans la chaîne de services" | Moyen (sampling) |
Q2 — Pull vs push : pourquoi Prometheus a choisi pull ?
Pull (Prometheus)
Le serveur scrape les cibles sur /metrics à intervalle régulier.
+ Découverte centralisée, pas d'auth entrante, debug au navigateur, pas de DoS si 10k instances démarrent en même temps.
− Mauvais pour jobs courts (un cron de 2s ne sera jamais scrapé à temps).
Push (StatsD, OTLP)
Les apps envoient vers un collecteur.
+ Jobs courts/batch, serverless, traversée NAT/firewall.
− Risque de surcharger le backend, auth à gérer dans chaque app.
Pushgateway (exception).
Q3 — Cardinalité : c'est quoi et pourquoi c'est un problème ?
Cardinalité = nombre de séries temporelles uniques générées par une métrique. Produit du nombre de valeurs distinctes de chaque label.
# OK : cardinalité bornée
http_requests_total{method, route, status}
# 4 méthodes × 20 routes × 6 statuts = 480 séries
# CATASTROPHE : cardinalité explosive
http_requests_total{method, route, status, user_id}
# 480 × 1M users = 480M séries → OOM Prometheus
Q4 — Les 4 Golden Signals (Google SRE)
| Signal | Description | Piège |
|---|---|---|
| Latency | Temps de réponse | Séparer succès vs échecs (un 500 ultra-rapide masque la latence réelle) |
| Traffic | Demande sur le système (req/s) | — |
| Errors | Taux d'échec (5xx ou implicite) | Attention aux 200 avec mauvais contenu |
| Saturation | À quel point le système est plein (CPU, RAM, queue, pool) | C'est LE signal qui prédit la casse |
RED (Rate, Errors, Duration) pour les services stateless. USE (Utilization, Saturation, Errors) pour les ressources.
Q5 — Histogram vs Summary ?
Histogram ✅
Buckets prédéfinis côté app. L'app incrémente juste des counters.
Quantile calculé côté Prometheus : histogram_quantile()
+ Agrégeable entre instances (on somme les buckets de 10 pods avant de calculer le p95 global).
− Précision dépend des buckets.
Summary
L'app calcule elle-même p50/p95/p99 et les expose.
+ Précision exacte sans choisir de buckets.
− NON agrégeable — impossible de "moyenner" des p95 de 10 pods, mathématiquement ça n'a pas de sens.
Q6 — Pourquoi un Collector plutôt qu'exporter direct depuis l'app ?
| Raison | Bénéfice |
|---|---|
| Découplage vendor | Changer de backend = modifier une ligne de config, zéro redéploiement |
| Offload | Batch, compression, retry, back-pressure gérés par le Collector. L'app ne bloque pas si le backend est lent. |
| Enrichissement centralisé | Ajouter k8s.cluster.name, filtrer les healthchecks, tail-based sampling — une seule fois |
| Multi-destination | Même donnée vers Prometheus + Tempo + SIEM simultanément |
| Sécurité / réseau | Les apps parlent à un Collector local (sidecar/daemonset), seul le Collector a les credentials du backend externe |
Bonus — PromQL à connaître par cœur
# Taux de requêtes par seconde sur 1 min
rate(http_requests_total[1m])
# Agrégation par label
sum by (status) (rate(http_requests_total[1m]))
# p95 de latence depuis un histogramme
histogram_quantile(0.95,
sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)
# Ratio d'erreurs 5xx
sum(rate(http_requests_total{status=~"5.."}[1m]))
/ sum(rate(http_requests_total[1m]))
rate() ou increase() dessus. Un counter ne fait que monter, ce n'est pas lisible tel quel.
Différence Prometheus UI / Grafana / Tempo
| Outil | Rôle | Ce qu'on y voit |
|---|---|---|
| Prometheus UI | TSDB + requêteur PromQL brut | Graphes de métriques, cibles scrapées, règles d'alerte |
| Grafana | UI unifiée branchée sur des datasources | Dashboards, Explore, alertes, corrélation métriques ↔ traces |
| Tempo | Backend de stockage de traces | Spans individuels d'une requête, durée par étape |
Facile 1. Quelle difference entre assurance qualite et controle qualite ?
Reponse
AQ (preventif) = processus pour empecher les defauts en amont : code reviews, CI/CD, definition of done, linters.
CQ (detectif) = verifier que le produit fini est conforme : tests manuels avant mise en prod, validation visuelle.
Un bon dev AQ pousse l'equipe vers l'AQ pour reduire le besoin de CQ. Le cout de correction d'un bug explose avec le temps : 1x en dev, 10x en staging, 100x en prod.
Facile 2. Decrivez la pyramide de tests et ses anti-patterns.
Pyramide (base au sommet)
Unitaires (majorite) → Integration → E2E (minorite).
Anti-patterns
Ice cream cone : trop de e2e, peu d'unitaires → pipeline lent (30s par e2e vs 5ms par unitaire), tests fragiles, feedback tardif.
Hourglass : beaucoup d'unitaires et e2e, mais rien en integration → les composants fonctionnent seuls mais pas ensemble.
Moyen 3. Vous heritez d'un projet sans tests. Comment priorisez-vous ?
1. Identifier les chemins critiques : quels flux impactent le plus d'utilisateurs (login, lecture video, paiement).
2. Smoke tests e2e sur les 3-5 parcours critiques — protection immediate.
3. Tests unitaires sur le code qui change — ne pas tester le legacy mort.
4. CI bloquant — aucun merge sans tests verts.
Piege : vouloir 80% de coverage sur du code mort. Mieux vaut 30% sur le code critique.
Moyen 4. Qu'est-ce qu'un test flaky ? Comment le detecter et le gerer ?
Flaky = passe parfois, echoue parfois, sans changement de code. Causes : race conditions, etat partage entre tests, dependance reseau, timing (sleep au lieu d'attente).
Gestion
Detection : retry dans le CI (passe au 2e essai = flaky). Quarantaine : deplacer hors du chemin critique, taguer. Fix prioritaire : un flaky erode la confiance dans toute la suite. Si l'equipe prend l'habitude de "re-run le pipeline", on rate les vrais bugs.
Facile 5. Quelles validations au-dela du status 200 pour une API REST ?
Schema : la reponse JSON respecte le contrat (types, champs requis). Business logic : les donnees retournees sont correctes. Headers : Content-Type, CORS, security headers. Performance : temps de reponse sous le SLO. Cas d'erreur : 400 input invalide, 401 sans auth, 404 inexistant, 429 rate limit. Idempotence : PUT/DELETE appele 2x → meme resultat.
Facile 6. Qu'est-ce que le shift-left testing ?
Deplacer les activites de test le plus tot possible dans le cycle. Concretement :
Pre-commit : linters, type check, tests unitaires. Code review : le dev AQ participe pour anticiper les risques. Sprint planning : criteres de test definis avec les stories. TDD/BDD : tests avant le code. Contrats d'API : valider les schemas avant l'implementation.
Moyen 7. Playwright vs Cypress : quand choisir l'un ou l'autre ?
Cypress : s'execute dans le navigateur, excellente DX, hot reload. Limites : un seul navigateur a la fois, pas de multi-onglets.
Playwright : hors navigateur via CDP, multi-navigateurs (Chromium, Firefox, WebKit), multi-onglets, emulation reseau native. Plus verbeux mais plus puissant.
Pour Radio-Canada : Playwright — Safari critique pour iOS/CBC Gem, tests cross-platform, API testing integre.
Difficile 8. Comment testez-vous un lecteur video (CBC Gem) ?
Defis specifiques
Etat asynchrone : buffering → playing → paused → ended. Attendre les evenements, pas des sleeps.
DRM : Widevine/FairPlay empechent la capture d'ecran → tester via l'API du player.
Adaptive bitrate : verifier le switch HLS/DASH sans interruption.
Accessibilite : sous-titres, audio-description, navigation clavier.
Approche
Tester via HTMLMediaElement API. Metriques : time-to-first-frame, buffering ratio. Tests reseau avec throttling Playwright.
Moyen 9. Difference entre mocking, stubbing et faking ?
Stub : retourne une reponse predefinie, sans logique. Mock : comme un stub, mais verifie qu'il a ete appele correctement (assert_called_once). Fake : implementation simplifiee mais fonctionnelle (SQLite au lieu de PostgreSQL).
Danger du sur-mocking
Tests passes mais code reel echoue. Tests couples a l'implementation, pas au comportement. Regle : mockez les frontieres (APIs externes), testez vos composants avec de vraies implementations.
Moyen 10. Qu'est-ce que le mutation testing ?
Introduit des bugs volontaires (mutants) dans le code et verifie que les tests les detectent.
# Original
def is_adult(age): return age >= 18
# Mutant : >= devient >
def is_adult(age): return age > 18 # 18 ans n'est plus adulte
Si tes tests ne detectent pas le mutant = tests insuffisants. Mutation score = mutants tues / total. Outils : mutmut (Python), Stryker (JS/TS). Bien plus fiable que le code coverage.
Facile 11. Qu'est-ce que le Page Object Model (POM) ?
Encapsule la structure d'une page dans une classe. Les tests utilisent des methodes lisibles au lieu de selecteurs CSS bruts.
class LoginPage:
def __init__(self, page):
self.page = page
def login(self, email, password):
self.page.fill("[data-test=email]", email)
self.page.fill("[data-test=password]", password)
self.page.click("[data-test=submit]")
Si le selecteur change, on modifie UN fichier (le POM), pas 50 tests.
Moyen 12. Comment gerez-vous les donnees de test en environnement partage ?
Isolation par test : chaque test cree ses donnees avec des UUID, les nettoie apres. Fixtures pytest avec scope session/module. Transaction rollback : chaque test dans une transaction annulee a la fin. Conteneurs ephemeres : testcontainers avec un PostgreSQL frais par pipeline. Factories : factory_boy pour des donnees uniques et realistes.
Anti-pattern : tests qui dependent de donnees pre-existantes ("l'utilisateur 42 doit exister"). Fragile et impossible a paralleliser.
Difficile 13. Expliquez le contract testing. Quand l'utiliser ?
Verifie que le contrat entre un consommateur (frontend) et un fournisseur (API) est respecte, sans les deployer ensemble.
Outil : Pact. Le consommateur genere un contrat ("j'attends un champ name de type string"). Le fournisseur verifie qu'il respecte ce contrat dans son CI.
Quand : microservices, equipes separees, API publique. Permet de casser le couplage des tests e2e cross-services. Chaque equipe teste de son cote avec le contrat comme reference.
Moyen 14. Quelle est la difference entre regression et smoke test ?
Smoke test : verification rapide que les fonctionnalites principales marchent ("l'app demarre, le login fonctionne, la page d'accueil charge"). Se lance apres chaque deploiement. ~2 minutes.
Regression test : suite complete qui verifie que les changements n'ont rien casse. Plus large, plus lent. ~30-60 minutes. Se lance en CI ou nightly.
Analogie : le smoke test c'est tourner la cle de la voiture. Le regression test c'est l'inspection complete.
Difficile 15. Comment structurez-vous un framework de test pour qu'il survive a votre depart ?
Convention over configuration : structure tests/unit/, tests/integration/, tests/e2e/. Nommage explicite : test_login_with_invalid_password_returns_401. Fixtures reutilisables : conftest.py. CI comme gardien : pas de merge sans tests verts. Pair programming : montrer les patterns, pas juste documenter.
Le vrai test : un nouveau developpeur peut-il ecrire un test en 30 minutes sans vous demander ?
Facile 16. Ecrivez un test pytest parametrise pour valider un email.
import pytest
@pytest.mark.parametrize("email,valid", [
("user@cbc.ca", True),
("nom.prenom@radio-canada.ca", True),
("", False),
("@cbc.ca", False),
("user@", False),
("user space@cbc.ca", False),
])
def test_validate_email(email, valid):
assert validate_email(email) == valid
@pytest.mark.parametrize execute le test pour chaque combinaison. Chaque cas est independant et affiche clairement lequel echoue.
Moyen 17. Expliquez les fixtures pytest : scope, yield, conftest.py.
# conftest.py — partage entre tous les tests
@pytest.fixture(scope="session")
def db_connection():
conn = create_connection()
yield conn # le test utilise conn
conn.close() # teardown apres TOUS les tests
@pytest.fixture
def user(db_connection):
u = create_user(db_connection, name="alice")
yield u
delete_user(db_connection, u.id)
Scopes : function (defaut, chaque test), class, module, session (une fois pour tout). yield = setup avant, teardown apres. conftest.py = fixtures partagees, decouverte automatique par pytest.
Moyen 18. Difference entre async/await en Python et en TypeScript ?
| Python | TypeScript | |
|---|---|---|
| Runtime | asyncio event loop | Event loop V8 natif |
| Execution | await asyncio.gather(a(), b()) | await Promise.all([a(), b()]) |
| Piege | Oublier await → coroutine jamais executee (warning) | Oublier await → Promise non resolue (silencieux) |
En tests Playwright (TS) : chaque interaction est async. En pytest avec pytest-asyncio : marquer les tests @pytest.mark.asyncio.
Facile 19. Qu'est-ce que le type checking statique et pourquoi c'est utile en AQ ?
Python : mypy avec type hints. TypeScript : types natifs a la compilation.
Le type checking attrape des bugs avant meme de lancer les tests : mauvais type d'argument, attribut inexistant, null non gere. C'est du shift-left au maximum — le bug est catch a l'ecriture, pas au runtime.
En CI : mypy --strict ou tsc --noEmit comme premiere etape du pipeline.
Moyen 20. Comment testez-vous du code async qui fait des appels HTTP ?
# Python avec pytest + respx (mock HTTP async)
import respx, httpx
@pytest.mark.asyncio
@respx.mock
async def test_fetch_user():
respx.get("https://api.cbc.ca/user/1").respond(
json={"name": "Alice"}, status_code=200
)
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.cbc.ca/user/1")
assert resp.json()["name"] == "Alice"
On mocke la frontiere HTTP, pas le code interne. Le test verifie le comportement, pas l'implementation.
Difficile 21. Expliquez les generics en TypeScript. Pourquoi c'est utile pour un framework de test ?
// Fonction generique de test factory
function createTestData<T>(factory: () => T, count: number): T[] {
return Array.from({ length: count }, factory);
}
// Usage type-safe
const users = createTestData(() => ({ name: "alice", role: "admin" }), 10);
// TypeScript sait que users est { name: string, role: string }[]
Les generics permettent de creer des utilitaires de test reutilisables tout en gardant le type safety. Pas de any, pas de bugs de type au runtime.
Moyen 22. Difference entre Jest et Vitest ? Quand utiliser lequel ?
Jest : standard de l'industrie, large ecosysteme, config parfois lourde (transformations Babel/TS).
Vitest : compatible Jest (meme API), utilise Vite donc ultra-rapide, support natif TypeScript/ESM sans config.
Choix : nouveau projet → Vitest. Projet existant avec Jest → rester sur Jest sauf si la vitesse est un probleme. Les deux ont le meme describe/it/expect.
Moyen 23. Comment debuggez-vous un test qui passe en local mais echoue en CI ?
Causes frequentes :
Environnement : variables d'env manquantes, versions differentes (Node, Python, navigateur). Timing : la machine CI est plus lente → timeout. Etat : un test precedent laisse un etat en BDD. Reseau : le CI n'a pas acces a un service externe. Fichiers : chemins relatifs, case sensitivity (Linux CI vs macOS local).
Approche : reproduire l'env CI en local (Docker), ajouter des logs au test, verifier l'ordre d'execution (pytest -p no:randomly).
Facile 24. A quoi sert conftest.py en pytest ?
Fichier special decouvert automatiquement par pytest. Contient les fixtures partagees entre tous les tests du meme repertoire et ses sous-repertoires. Pas besoin d'import explicite.
On peut avoir plusieurs conftest.py a differents niveaux : tests/conftest.py (global), tests/e2e/conftest.py (seulement e2e). Les fixtures cascadent.
Moyen 25. Ecrivez un test Playwright qui verifie le login CBC Gem.
import { test, expect } from '@playwright/test';
test('login CBC Gem avec identifiants valides', async ({ page }) => {
await page.goto('https://gem.cbc.ca/login');
await page.fill('[data-test=email]', 'user@test.ca');
await page.fill('[data-test=password]', 'SecurePass123');
await page.click('[data-test=submit]');
// Verifier la redirection vers l'accueil
await expect(page).toHaveURL(/\/accueil/);
await expect(page.locator('[data-test=user-menu]')).toBeVisible();
});
Utilise des data-test attributes (stables) au lieu de classes CSS (fragiles). expect auto-retry avec timeout.
Moyen 26. 3 cas d'usage concrets d'un LLM pour ameliorer le testing.
1. Generation de cas de test : a partir d'une spec, generer les cas limites qu'un humain oublierait.
2. Analyse de logs : alimenter un LLM avec les stack traces pour identifier les root causes.
3. Review de code : detecter les patterns fragiles (race conditions, injections SQL) dans les PRs.
Limite : un LLM peut generer des tests qui compilent mais testent la mauvaise chose. Revue humaine obligatoire.
Difficile 27. Comment tester la qualite d'un chatbot IA integre au site Radio-Canada ?
Metriques : pertinence (LLM-as-judge), factualite (comparaison sources), coherence linguistique, latence (TTFT < 500ms), taux de fallback, guardrails (pas de contenu inapproprie).
Approche : golden dataset 200+ paires Q/R. Regression auto a chaque changement prompt/modele. Red teaming (injection, contenu sensible). Monitoring prod (satisfaction, taux d'abandon).
Radio-Canada : le bot ne doit jamais etre partisan (mandat public). Test de conformite reglementaire indispensable.
Difficile 28. Qu'est-ce que la detection d'anomalies en AQ ? Comment l'implementer ?
Au lieu de seuils fixes ("alerte si latence > 500ms"), on detecte les ecarts par rapport au comportement normal appris.
Collecter : historique des metriques par endpoint. Modeliser : baseline (moyenne mobile, ecart-type) sur 7 jours. Detecter : z-score > 3 = anomalie. Alerter : contexte (quel endpoint, quel deploy recent).
Avantage : s'adapte aux variations naturelles (plus de trafic le mardi que le dimanche).
Difficile 29. Comment valider qu'un modele ML ne s'est pas degrade apres re-entrainement ?
Golden dataset : jeu labelise manuellement, jamais utilise pour l'entrainement. Metriques : precision, recall, F1 — nouveau ≥ ancien. Tests de slice : performance par segment (FR vs EN, genres). Shadow mode : deployer en parallele, comparer.
Pipeline : eval → comparaison A/B → canary 5% → rollout 100%.
Moyen 30. Quels risques a utiliser l'IA pour generer des tests ?
Tautologie : l'IA copie le code dans le test. Assertions faibles : tests qui passent toujours. Dette invisible : 500 tests generes que personne ne comprend. Confiance excessive : "90% coverage" mais tests inutiles. Donnees sensibles : l'IA peut generer des tests avec des vraies donnees.
Mitigation : review humaine, mutation testing pour valider l'utilite reelle des tests.
Difficile 31. Comment testez-vous la transcription automatique (speech-to-text) a Radio-Canada ?
WER (Word Error Rate) : standard de l'industrie. Tests specifiques : accents regionaux (quebecois, acadien), noms propres (politiciens, villes autochtones), code-switching FR/EN, bruit de fond (reportages terrain), latence temps reel.
Golden set d'extraits audio annotes. Regression auto a chaque update. Tests par segment (accent, bruit, type d'emission).
Moyen 32. Comment integrer des validations IA dans un CI/CD sans le ralentir ?
Pre-merge (bloquant, <5min) : schema validation, smoke tests modele, latence. Post-merge/nightly (non-bloquant, 30min-2h) : eval complete golden set, tests de biais, mutation testing. Post-deploy (continu) : drift detection, metriques prod.
Les tests IA lourds ne bloquent pas le merge. Ils tournent en parallele et alertent si regression.
Difficile 33. Qu'est-ce que le "LLM-as-judge" ? Limites ?
Utiliser un LLM pour evaluer la qualite d'un autre LLM. Ex : Claude evalue si la reponse de GPT est pertinente/factuelle.
Avantage : scalable, pas besoin d'annotateurs humains pour chaque test. Limites : biais du juge (prefere les reponses longues, son propre style), pas fiable sur les faits precis, cout API, reproductibilite (meme prompt, reponse differente).
En pratique : LLM-as-judge pour le screening, humain pour la validation finale du golden set.
Moyen 34. Qu'est-ce que le drift d'un modele ML ? Comment le detecter ?
Drift = les donnees en production divergent de celles d'entrainement. Le modele fait des predictions de plus en plus mauvaises sans que le code change.
Data drift : la distribution des inputs change (ex : nouveaux genres de contenu). Concept drift : la relation input/output change (ex : les preferences des utilisateurs evoluent).
Detection : comparer les distributions (KL divergence, PSI) entre donnees d'entrainement et donnees recentes. Alerter quand le score depasse un seuil.
Difficile 35. Comment testez-vous un systeme de recommandation (CBC Gem "Suggestions pour vous") ?
Non-regression : golden set, score ≥ ancien. Diversite : recommandations pas toutes du meme genre/langue. Fraicheur : contenu recent doit apparaitre. Biais : equite FR/EN. Latence : < 200ms. Fallback : si modele down, recommandations par defaut. A/B testing : taux de clic, temps de visionnage.
CRTC : le contenu canadien doit etre mis en avant. Test de conformite reglementaire indispensable.
Moyen 36. Qu'est-ce que le red teaming pour un systeme IA ?
Tester intentionnellement les limites et failles d'un systeme IA : injections de prompt, contenus inappropries, manipulation pour obtenir des reponses biaisees/fausses.
Pour Radio-Canada : essayer de faire dire au chatbot des opinions politiques, du contenu offensant, des fausses nouvelles. Tester les guardrails. Documenter chaque faille trouvee et ajouter au golden set de regression.
Moyen 37. Comment evaluez-vous le cout/benefice d'automatiser un test ?
Automatiser si : execute souvent (>1x/semaine), deterministe, stable (pas de changement UI frequent), critique pour la regression.
Garder manuel si : exploratoire, execute rarement, UI tres volatile, necessite un jugement subjectif (UX, esthetique).
Formule simplifiee : cout_auto = temps_dev + maintenance. gain = temps_manuel * frequence * duree_de_vie. Si gain > cout → automatiser.
Difficile 38. Expliquez le concept de "AI safety testing" dans le contexte d'un diffuseur public.
Guardrails de contenu : le systeme ne doit jamais generer de desinformation, contenu haineux, opinions partisanes. Equite : pas de biais culturel, linguistique ou regional. Transparence : l'utilisateur doit savoir quand il interagit avec de l'IA. Vie privee : l'IA ne doit pas reveler de donnees personnelles d'utilisateurs.
Tests : jeux de donnees adversariaux, red teaming systematique, audits de biais reguliers, monitoring des reponses en prod.
Moyen 39. Qu'est-ce que le visual regression testing ?
Compare des screenshots pixel par pixel entre deux versions. Detecte les regressions CSS/layout invisibles aux tests fonctionnels.
Outils : Playwright (expect(page).toHaveScreenshot()), Percy, Chromatic.
Limites : sensible aux anti-aliasing, polices, animations. Necessite des baselines approuvees. Utile pour Radio-Canada : verifier que le redesign n'a pas casse l'affichage sur les anciennes pages.
Moyen 40. Comment testez-vous l'accessibilite automatiquement ?
// Playwright + axe-core
import AxeBuilder from '@axe-core/playwright';
test('page accueil respecte WCAG 2.1 AA', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toHaveLength(0);
});
Radio-Canada a une obligation legale d'accessibilite (service public). Tests axe-core dans le CI = non-negociable. Couvre ~57% des criteres WCAG. Le reste necessite du test manuel (navigation clavier, lecteur d'ecran).
Moyen 41. Decrivez un pipeline CI/CD ideal. Quelles etapes, dans quel ordre ?
git push
↓
[Lint + Type check] ~30s
↓
[Tests unitaires] ~1-2min
↓
[Build] ~2-3min
↓
[Tests integration] ~3-5min
↓
[Tests e2e] ~5-10min
↓
[Security scan] ~2min (Snyk, Trivy)
↓
[Deploy staging]
↓
[Smoke tests]
↓
[Deploy prod] (rolling/canary/blue-green)
↓
[Health check + monitoring]
Principe : fail fast. Les etapes rapides et susceptibles d'echouer en premier.
Moyen 42. Difference entre rolling update, blue-green et canary deployment ?
Rolling : remplacement progressif instance par instance. Simple, zero downtime, mais rollback lent.
Blue-green : deux environnements identiques. On switch le traffic d'un coup. Rollback instantane (re-switch), mais couteux (double infra).
Canary : deployer la nouvelle version sur 5% du trafic, monitorer, puis augmenter progressivement. Le plus sur pour detecter les problemes avant impact large.
Radio-Canada : canary pour les changements risques (nouveau modele IA), rolling pour les mises a jour standard.
Facile 43. Qu'est-ce que le semantic versioning (semver) ?
MAJOR.MINOR.PATCH — ex : 3.2.1
MAJOR : breaking changes (API incompatible). MINOR : nouvelles fonctionnalites, backward compatible. PATCH : bug fixes.
En AQ : un bump MAJOR necessite des tests de regression complets. Un PATCH ne necessite que les tests lies au fix.
Moyen 44. Comment securisez-vous un pipeline CI/CD ?
Secrets : jamais en clair dans le code, utiliser le vault du CI (GitHub Secrets, Azure Key Vault). Dependances : scan automatique (Snyk, Dependabot). Images Docker : scan de vulnerabilites (Trivy). SAST : analyse statique du code (SonarQube, Semgrep). Principe du moindre privilege : le CI a uniquement les permissions necessaires. Audit trail : qui a deploye quoi, quand.
Difficile 45. Expliquez le trunk-based development vs GitFlow. Lequel favorisez-vous et pourquoi ?
GitFlow : branches develop, feature, release, hotfix. Lourd, merges complexes, adapte aux releases planifiees.
Trunk-based : tout le monde commit sur main (ou branches tres courtes <1 jour). Feature flags pour le code pas pret. CI/CD oblige.
Pour Radio-Canada : trunk-based. Deploiements frequents, feedback rapide, moins de merge conflicts. Les feature flags permettent de deployer du code inactif et de l'activer quand il est pret.
Moyen 46. Qu'est-ce qu'un feature flag ? Avantages et risques ?
Un booleen en config qui active/desactive une fonctionnalite sans redeployer. Ex : if (flags.new_player) { ... }
Avantages : deploy decouple du release, A/B testing, rollback instantane, dark launching.
Risques : dette technique (flags oublies), combinatoire d'etats explose (5 flags = 32 combinaisons a tester), code plus complexe a lire.
Regle : supprimer le flag des que la feature est stable. Un flag de plus de 3 mois est une dette.
Facile 47. Qu'est-ce que Docker et pourquoi c'est utile pour les tests ?
Docker emballe une application et ses dependances dans un conteneur reproductible. Pour les tests :
Reproductibilite : "ca marche sur ma machine" n'existe plus. Isolation : chaque pipeline a son propre environnement. Dependances : lancer PostgreSQL, Redis, Kafka en une commande pour les tests d'integration (testcontainers). Parite dev/prod : meme image en dev, CI et prod.
Moyen 48. Qu'est-ce que les testcontainers ? Donnez un exemple.
import pytest
from testcontainers.postgres import PostgresContainer
@pytest.fixture(scope="session")
def db():
with PostgresContainer("postgres:16") as pg:
engine = create_engine(pg.get_connection_url())
yield engine
# le conteneur est detruit automatiquement
def test_create_user(db):
db.execute("INSERT INTO users (name) VALUES ('alice')")
result = db.execute("SELECT name FROM users").fetchone()
assert result[0] == "alice"
Un vrai PostgreSQL dans Docker, demarre par le test, detruit apres. Pas de mock, pas de "ca marchait avec SQLite mais pas en prod".
Moyen 49. Comment parallélisez-vous vos tests pour accelerer le pipeline ?
Pytest : pytest-xdist (pytest -n auto). Playwright : workers: 4 dans la config. CI : matrix strategy (GitHub Actions), split par type de test.
Pre-requis : les tests doivent etre independants. Pas d'etat partage, pas d'ordre de dependance. Si un test ecrit en BDD et un autre lit, ils vont entrer en conflit en parallele.
Difficile 50. Comment implementez-vous le chaos testing ?
Injecter des pannes volontaires en production pour valider la resilience : tuer des pods, ajouter de la latence reseau, saturer le CPU/disque.
Outils : Chaos Monkey (Netflix), Litmus (Kubernetes), Gremlin.
Approche : hypothese ("si un pod meurt, le service reste disponible"), experience (tuer le pod), observation (les metriques et alertes reagissent correctement ?), apprentissage (documenter, ameliorer).
Pour Radio-Canada : que se passe-t-il si le CDN tombe pendant un match de hockey ? Le fallback fonctionne-t-il ?
Facile 51. Qu'est-ce qu'un artifact dans un pipeline CI/CD ?
Un fichier produit par le pipeline qui persiste entre les etapes : image Docker, rapport de test (JUnit XML), coverage report, bundle JS, binary.
Exemple : l'etape "build" produit une image Docker. L'etape "deploy" utilise cette meme image. Sans artifacts, il faudrait rebuilder a chaque etape.
Moyen 52. Comment gerez-vous les secrets (cles API, tokens) dans vos tests ?
Jamais en clair dans le code ou les fichiers de test. CI : variables d'environnement secretes (GitHub Secrets, Azure Key Vault). Local : fichier .env (dans .gitignore). Tests : utiliser des valeurs de test/mock, jamais des vrais tokens de production.
Si un secret fuite dans git : le revoquer immediatement, pas juste supprimer le commit (il reste dans l'historique).
Moyen 53. Expliquez Infrastructure as Code (IaC). Lien avec la QA ?
Definir l'infrastructure (serveurs, BDD, reseau) en code versionne : Terraform, Bicep (Azure), Pulumi.
Lien QA : l'infra peut etre testee comme du code. terraform plan = dry run, terraform validate = lint. On peut ecrire des tests qui verifient que la config respecte les regles de securite (pas de ports ouverts, chiffrement active).
Outils : Checkov, tfsec pour le scan de securite de l'IaC.
Moyen 54. Qu'est-ce que le shift-right testing ?
Complement du shift-left : tester en production. Pas "deployer sans tester", mais ajouter de l'observation post-deploy.
Synthetic monitoring : des bots qui simulent des parcours utilisateur en prod 24/7. Canary analysis : comparer les metriques de la nouvelle version vs l'ancienne. Feature flags : activer progressivement. Observabilite : metriques, traces, logs pour detecter les problemes que les tests pre-prod ne peuvent pas simuler (charge reelle, donnees reelles).
Difficile 55. Concevez une strategie de test pour un deploiement zero-downtime d'un nouveau modele ML.
Pre-deploy : eval sur golden set, regression metriques ≥ ancien, tests de latence.
Deploy : shadow mode (nouveau modele en parallele, sans servir) pendant 24h. Comparer predictions ancien vs nouveau.
Canary : 5% du trafic sur le nouveau. Monitoring : latence p99, taux d'erreur, metriques business (CTR, engagement).
Rollout : 5% → 25% → 50% → 100%, avec seuils d'arret automatiques.
Post-deploy : drift monitoring continu, alertes si les metriques degradent.
Moyen 56. Quels sont les 3 piliers de l'observabilite ? Comment les utiliser ensemble ?
Metriques (Prometheus) : "QUOI" — taux d'erreurs, latence, saturation. Traces (Tempo/Jaeger) : "OU" — l'arbre d'appels, quel service est lent. Logs (Loki/ELK) : "POURQUOI" — le detail, le stack trace.
Scenario : alerte taux 5xx (metrique) → trace montre /api/recommendations timeout → log revele "connection refused" vers service ML → root cause : mauvaise config au deploy.
Moyen 57. Expliquez SLI, SLO, SLA et error budget avec un exemple CBC Gem.
SLI (indicateur) : latence du endpoint /api/stream. SLO (objectif interne) : 99.5% des requetes en <300ms sur 30 jours. SLA (contrat) : engagement envers les partenaires, consequences financieres si non respecte.
Error budget = 100% − 99.5% = 0.5% = ~3.6h de degradation/mois. Budget consomme → freeze des deploiements, on stabilise.
# Mesure Prometheus
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[30d]))
/ sum(rate(http_request_duration_seconds_count[30d]))
# Resultat ≥ 0.995 → SLO respecte
Moyen 58. Pull (Prometheus) vs Push (OTel) : avantages et inconvenients.
| Pull (Prometheus) | Push (OTLP) | |
|---|---|---|
| Decouverte | Prometheus sait qui scraper | L'app doit connaitre le collector |
| Firewall | Prometheus doit acceder aux cibles | L'app initie (plus facile) |
| Jobs courts | Difficile (finit avant le scrape) | Naturel (push avant de mourir) |
| Health check | Gratuit (scrape echoue = down) | Separe |
En pratique : OTel pour les traces (push), Prometheus pour les metriques (pull).
Facile 59. Qu'est-ce que rate() dans Prometheus ? Pourquoi c'est obligatoire sur un Counter ?
rate() calcule le taux moyen par seconde d'augmentation d'un Counter sur une fenetre. Un Counter ne fait que monter — sans rate(), le graphe est une ligne qui monte toujours, sans pic visible.
# Counter brut : 10, 13, 14, 14, 15 (inutilisable)
# rate() : 0.05, 0.016, 0, 0.016 (on voit les pics)
rate(login_attempts_total[1m])
rate() gere aussi les counter resets (redemarrage app).
Moyen 60. Qu'est-ce que le p95 ? Pourquoi le preferer a la moyenne ?
p95 = 95% des requetes sont plus rapides que cette valeur. La moyenne cache les outliers : 99 requetes a 10ms + 1 a 10s = moyenne 110ms (semble OK), mais p99 = 10s (1% des users souffrent).
p50 = experience normale. p95 = standard SLO. p99 = pire experience (1%). En prod sur 1M requetes, 1% = 10 000 requetes lentes.
Moyen 61. Processus de gestion d'incident post-deploiement.
1. DETECTER — alerte Prometheus/PagerDuty
2. EVALUER — impact ? combien d'utilisateurs ?
3. COMMUNIQUER — canal incident, notifier l'equipe
4. MITIGER — rollback immediat
5. DIAGNOSTIQUER — metriques → traces → logs
6. CORRIGER — fix + deployer
7. POST-MORTEM — timeline, root cause, actions preventives
Mitiger d'abord, diagnostiquer ensuite. Post-mortem blameless : causes systemiques, pas coupables.
Difficile 62. Qu'est-ce que la cardinalite et pourquoi c'est dangereux ?
Cardinalite = nombre de series temporelles uniques. Produit des valeurs distinctes de chaque label.
# OK : 4 methodes x 20 routes x 6 statuts = 480 series
http_requests_total{method, route, status}
# CATASTROPHE : 480 x 1M users = 480M series
http_requests_total{method, route, status, user_id}
Jamais en label : user_id, request_id, email, UUID. Ces infos vont dans les logs/traces, pas les metriques.
Facile 63. Les 4 Golden Signals de Google SRE.
| Signal | Mesure | Piege |
|---|---|---|
| Latency | Temps de reponse | Separer succes vs echecs (un 500 rapide masque la latence reelle) |
| Traffic | req/s | — |
| Errors | Taux 5xx | Attention aux 200 avec mauvais contenu |
| Saturation | CPU, RAM, queue | Le signal qui predit la casse |
Variantes : RED (Rate, Errors, Duration) pour les services. USE (Utilization, Saturation, Errors) pour les ressources.
Moyen 64. Histogram vs Summary dans Prometheus ?
Histogram : buckets predefinies cote app, quantile calcule cote Prometheus. Agregeable entre instances (on somme les buckets de 10 pods).
Summary : l'app calcule elle-meme p50/p95/p99. Precision exacte mais NON agregeable (impossible de "moyenner" des p95 de 10 pods).
En pratique : toujours Histogram. OTel a introduit les exponential histograms qui reglent le probleme du choix de buckets.
Moyen 65. Pourquoi un OTel Collector plutot qu'exporter directement depuis l'app ?
Decouplage vendor : changer de backend = modifier une config. Offload : batch, compression, retry geres par le Collector. Enrichissement : ajouter k8s.cluster.name, filtrer les healthchecks. Multi-destination : Prometheus + Tempo + SIEM simultanement. Securite : seul le Collector a les credentials du backend.
Difficile 66. Comment dimensionnez-vous les buckets d'un Histogram ?
Concentrer les buckets la ou les requetes tombent reellement. Si toutes les requetes font 1-10ms, les buckets par defaut (5ms, 10ms, 25ms, ..., 10s) sont trop espaces dans la zone utile.
Approche : observer la distribution reelle, ajouter des buckets dans la zone p50-p99, garder quelques buckets larges pour les outliers.
Trop de buckets = cardinalite qui explose (chaque bucket = une serie par combinaison de labels). C'est un compromis precision vs cout.
Moyen 67. Qu'est-ce qu'un exemplar dans Prometheus ?
Un pointeur d'un bucket d'histogramme vers un trace ID specifique. Permet de passer d'un spike de latence dans Grafana directement au trace dans Tempo.
Workflow : dashboard montre p95 eleve → clic sur un point du graphe → exemplar pointe vers le trace → on voit exactement quelle requete a ete lente et pourquoi.
C'est le lien entre metriques et traces — sans exemplars, il faut chercher manuellement dans Tempo.
Moyen 68. Qu'est-ce que le sampling de traces ? Head-based vs tail-based ?
On ne peut pas stocker 100% des traces en prod (trop cher). Le sampling selectionne lesquelles garder.
Head-based : decision au debut de la requete (ex : garder 10%). Simple mais on rate les erreurs rares.
Tail-based : decision apres la requete complete. On garde les traces lentes ou en erreur. Plus utile mais necessite un buffer (le Collector garde tout en memoire le temps de decider).
Ideal : tail-based dans le Collector — garder 100% des erreurs et traces lentes, 1% du trafic normal.
Difficile 69. Concevez un dashboard Grafana pour monitorer un deploiement en canary.
Panneau 1 : taux d'erreurs 5xx — canary vs stable (2 courbes). Panneau 2 : latence p95 — canary vs stable. Panneau 3 : trafic split (% sur canary). Panneau 4 : metriques business (CTR, conversions) par version. Panneau 5 : logs d'erreur du canary en temps reel.
Seuil automatique : si le taux d'erreur du canary depasse 2x le stable pendant 5min → rollback automatique.
Moyen 70. Difference entre logs structures et non structures. Pourquoi c'est important ?
# Non structure (inutilisable a grande echelle)
"Login failed for alice from 1.2.3.4"
# Structure (cherchable, filtrable)
{"ts": "2026-04-13T14:30:00Z", "event": "login_failed",
"user": "alice", "ip": "1.2.3.4", "ua": "curl/7.68"}
Les logs structures permettent de chercher dans Loki/ELK : "tous les login_failed de l'IP 1.2.3.4 dans les 10 dernieres minutes". Impossible avec du texte libre.
Facile 71. Differences entre Scrum et Kanban ?
| Scrum | Kanban | |
|---|---|---|
| Cadence | Sprints fixes (2 semaines) | Flux continu |
| Roles | PO, SM, equipe | Pas de roles prescrits |
| Planning | Sprint planning | Pas de planning formel |
| WIP | Engagement par sprint | Limites WIP par colonne |
| Changements | Pas pendant le sprint | A tout moment |
Radio-Canada AQ : probablement Scrumban (mix) — sprints pour la cadence, WIP limits pour le flux.
Moyen 72. Comment integrez-vous l'AQ dans un sprint Scrum ?
Sprint planning : le dev AQ participe, estime l'effort de test, definit les criteres d'acceptation testables.
Pendant le sprint : tests ecrits en parallele du dev (pas apres). Code review inclut la review des tests. Les bugs trouves sont corriges dans le sprint, pas reporte.
Definition of Done : code + tests + review + CI vert + documentation mise a jour. Une story sans test n'est pas "done".
Moyen 73. L'equipe refuse d'ecrire des tests. "Ca ralentit le sprint." Comment reagissez-vous ?
Quantifier : "le bug X a pris 3 jours a debugger en prod. Un test de 5 lignes l'aurait catch." Reduire la friction : ecrire les fixtures, templates, helpers. Montrer : ecrire un test qui catch un vrai bug devant l'equipe. Commencer petit : 1 test par PR, pas 80% de coverage.
L'AQ n'est pas un frein. Les tests permettent de deployer plus souvent avec confiance — c'est ca qui accelere le sprint.
Facile 74. Qu'est-ce que la Definition of Done (DoD) ?
Checklist que chaque story doit remplir pour etre consideree terminee. Empeche le "c'est presque fini".
Exemple : code merge + tests unitaires + test integration + review par 2 personnes + CI vert + pas de regression d'accessibilite + documentation a jour.
La DoD evolue : au debut d'un projet sans tests, exiger 80% coverage est irrealiste. On augmente progressivement.
Moyen 75. Comment estimez-vous l'effort de test dans un sprint ?
L'effort de test fait partie de l'estimation de la story, pas une tache separee. Facteurs :
Complexite : nouvelle fonctionnalite vs modification mineure. Risque : code de paiement vs changement cosmetique. Testabilite : API bien definie vs UI dynamique. Dependances : services externes a mocker/integrer.
Regle : si l'equipe estime une story a 5 points sans les tests, c'est probablement 8 avec. Ne pas sous-estimer le test.
Difficile 76. Comment mesurez-vous la qualite d'une equipe de dev ? Quelles metriques ?
DORA metrics (standard de l'industrie) :
| Metrique | Elite | Moyen |
|---|---|---|
| Frequence de deploiement | Multiple/jour | 1x/semaine |
| Lead time (commit → prod) | <1h | 1 semaine |
| Taux d'echec des deploys | <5% | 15% |
| Temps de restauration (MTTR) | <1h | 1 jour |
Ne jamais utiliser le nombre de bugs comme metrique individuelle — ca pousse a cacher les bugs au lieu de les reporter.
Moyen 77. Qu'est-ce qu'un post-mortem blameless ? Pourquoi c'est important ?
Analyse d'incident sans chercher de coupable. On cherche les causes systemiques : "pourquoi le systeme a permis ce bug en prod" pas "qui a ecrit ce code".
Structure : timeline, impact, root cause, actions correctives (process, pas personnes).
Si on blame, les gens cachent les erreurs → les incidents se repetent. Si on ne blame pas, les gens reportent les near-misses → on previent les incidents.
Moyen 78. Comment priorisez-vous les bugs dans un backlog ?
| Severite | Impact | Action |
|---|---|---|
| Critique | App down, perte de donnees, securite | Fix immediat, hotfix hors sprint |
| Majeur | Feature cassee pour beaucoup d'users | Prochain sprint, haute priorite |
| Mineur | Bug cosmetique, contournement existe | Backlog, quand il y a de la place |
| Trivial | Typo, alignement CSS | Fix opportuniste en passant |
Facteurs : nombre d'users impactes, frequence, contournement possible, visibilite (page d'accueil vs page admin).
Difficile 79. Bug critique un vendredi 16h. Le fix est pret, les tests e2e prennent 45min. Que faites-vous ?
1. Evaluer l'impact : bloquant (paiement, login) ou cosmetique ? 2. Si critique : deployer avec tests unitaires + integration (rapides), lancer e2e en parallele. Si e2e echouent apres → rollback. 3. Si non-critique : attendre les e2e ou lundi.
Dans tous les cas : prevenir l'on-call, preparer un rollback, monitorer post-deploy.
Piege : ne jamais repondre "je skip tous les tests" ni "j'attends lundi". La bonne reponse est nuancee et depend de l'impact.
Moyen 80. Comment documentez-vous vos cas de test pour qu'ils soient comprehensibles par un non-technique ?
BDD (Behavior-Driven Development) avec Gherkin :
Scenario: Login avec identifiants valides
Given je suis sur la page de connexion
When je saisis "alice@cbc.ca" et "MonMotDePasse"
And je clique sur "Se connecter"
Then je vois la page d'accueil
And mon nom s'affiche dans le menu
Lisible par le PO, le designer, le QA. Executable avec Cucumber/behave. Le PO peut valider les scenarios avant l'implementation.
Moyen 81. Top 5 OWASP a tester en priorite pour une app web.
Injection (SQL, XSS) : tester avec des payloads malveillants. Broken Authentication : brute force, session fixation, tokens faibles. Sensitive Data Exposure : HTTPS, headers de securite, pas de donnees en clair. Broken Access Control : un user normal ne peut pas acceder aux endpoints admin. Security Misconfiguration : headers par defaut, endpoints de debug exposes.
Outils automatises : OWASP ZAP, Semgrep, Snyk.
Moyen 82. Comment testez-vous la securite dans un pipeline CI/CD ?
SAST (Static Analysis) : Semgrep, SonarQube — analyse le code source. DAST (Dynamic Analysis) : OWASP ZAP — attaque l'app deployee. SCA (Software Composition) : Snyk, Dependabot — vulnerabilites dans les dependances. Container scanning : Trivy — CVE dans les images Docker.
SAST en pre-merge (rapide, bloquant), DAST en post-deploy staging (lent, non-bloquant).
Facile 83. Difference entre SAST et DAST ?
SAST (Static) : analyse le code source sans l'executer. Rapide, trouve les patterns dangereux (SQL injection, secrets hardcodes). Faux positifs frequents.
DAST (Dynamic) : attaque l'app en cours d'execution. Trouve les vrais exploits mais plus lent, necessite un deploiement. Moins de faux positifs.
Les deux sont complementaires : SAST dans le CI, DAST en staging.
Moyen 84. Qu'est-ce qu'Azure DevOps ? Comment l'utilisez-vous pour la QA ?
Azure Pipelines : CI/CD (build, test, deploy). Azure Boards : gestion des bugs et stories (comme Jira). Azure Test Plans : gestion des cas de test manuels et automatises. Azure Repos : git hosting.
Workflow QA : PR → pipeline lance tests → merge → deploy staging → smoke tests → approval gate → deploy prod.
Moyen 85. Comment testez-vous une app deployee sur Azure App Service ?
Deployment slots : deployer sur un slot staging, tester, puis swap avec prod (zero downtime). Health check : Azure ping un endpoint /health, redmarre si down. Application Insights : monitoring integre (metriques, traces, logs). Load testing : Azure Load Testing pour simuler du trafic.
Tests automatises dans le pipeline : apres deploy sur slot staging, lancer Playwright contre le slot, si OK swap vers prod.
Difficile 86. Expliquez le principe du "zero trust" et son impact sur les tests.
Ne jamais faire confiance, toujours verifier — meme au sein du reseau interne. Chaque requete est authentifiee et autorisee.
Impact sur les tests : chaque appel inter-service doit etre teste avec et sans token valide. Les tests d'integration doivent verifier les politiques RBAC. Les tests de securite deviennent des tests fonctionnels (pas un "plus").
Azure : Managed Identity, Azure AD, Key Vault — les tests doivent utiliser ces mecanismes, pas les contourner.
Moyen 87. Comment testez-vous les performances d'une application ?
Load test : charge normale attendue, verifier que les SLO sont respectes. Stress test : au-dela de la capacite, verifier la degradation gracieuse (pas de crash). Spike test : pic soudain (ex : debut d'emission). Soak test : charge moderee pendant longtemps, detecter les memory leaks.
Outils : k6, Locust (Python), Azure Load Testing. Metriques : latence p50/p95/p99, throughput, taux d'erreur, utilisation CPU/RAM.
Moyen 88. Qu'est-ce que la LPRPDE et son impact sur le testing ?
Loi canadienne sur la Protection des Renseignements Personnels. Impact :
Donnees de test : ne jamais utiliser de vraies donnees d'utilisateurs pour les tests. Generer des donnees synthetiques. Logs : les tests ne doivent pas logger de PII (noms, emails, IPs). Retention : les captures d'ecran de tests e2e peuvent contenir des donnees sensibles — les supprimer apres le pipeline. Consentement : tester que les bannieres de consentement fonctionnent correctement.
Difficile 89. Comment testez-vous la resilience d'un systeme distribue sur Azure ?
Azure Chaos Studio : injecter des pannes (kill VM, latence reseau, DNS failure). Circuit breaker : tester que le pattern se declenche quand un service est down. Retry policies : verifier que les retries fonctionnent avec backoff exponentiel.
Tests : couper un service downstream, verifier que l'app retourne une reponse degradee (pas un 500). Tester le timeout : si le service met 30s, l'app ne doit pas attendre 30s.
Moyen 90. Comment testez-vous une application multi-tenant ?
Isolation des donnees : le tenant A ne doit JAMAIS voir les donnees du tenant B. Tester avec 2 tenants simultanement. Performance : un tenant gourmand ne doit pas degrader les autres. Configuration : chaque tenant peut avoir des settings differents.
Test critique : se connecter en tant que tenant A, essayer d'acceder aux ressources de tenant B via manipulation d'URL/API. Doit retourner 403.
Moyen 91. Radio-Canada a un mandat de service public. Comment ca influence votre approche AQ ?
Accessibilite (WCAG) : obligation legale. Tests axe-core dans le CI. Bilinguisme : tester en FR et EN. Disponibilite : accessible a tous, partout — tester sur connexions lentes, appareils anciens. Contenu canadien : CRTC — tester que les recommandations IA mettent en avant le contenu canadien. Vie privee : LPRPDE. Transparence : tracabilite des decisions IA.
Un bug a Radio-Canada affecte la confiance du public envers une institution nationale. L'enjeu depasse le revenu.
Facile 92. Pourquoi voulez-vous travailler a Radio-Canada ?
Points a mentionner (adaptez a votre profil) :
Impact : toucher des millions de Canadiens. Mission : contribuer a un service public essentiel a la democratie. Innovation : IA appliquee aux medias, pas juste du e-commerce. Diversite des defis : streaming, accessibilite, temps reel, multi-plateforme. Culture : equipe collaborative, experimentation encouragee.
Evitez les reponses generiques. Montrez que vous connaissez les produits (CBC Gem, Tou.tv, Radio-Canada.ca).
Moyen 93. Decrivez une situation ou vous avez du convaincre une equipe technique de changer d'approche.
Methode STAR : Situation (contexte), Task (votre role), Action (ce que vous avez fait), Result (le resultat mesurable).
Points a demontrer : diplomatie, arguments bases sur des donnees (pas des opinions), empathie pour les contraintes de l'equipe, patience, compromis acceptable.
Ex : "L'equipe deploiait manuellement. J'ai propose un pipeline CI/CD. Resistance initiale. J'ai fait un POC sur un seul service, montre le gain de temps. L'equipe a adopte pour tous les services."
Moyen 94. Comment gerez-vous l'incertitude dans un projet innovant (IA, nouveau produit) ?
Iteratif : petits increments, feedback rapide, pivoter si necessaire. Spike/POC : investir 2-3 jours pour valider une hypothese avant de commiter un sprint entier. Accepter l'echec : un POC qui echoue est un succes (on a appris vite et pas cher). Documenter : ce qui a marche, ce qui n'a pas marche, pourquoi.
L'offre de poste mentionne "a l'aise avec l'incertitude" — montrez que vous n'avez pas besoin d'un plan parfait pour avancer.
Facile 95. Comment restez-vous a jour sur les nouvelles technologies ?
Veille active : newsletters (TLDR, Changelog), conferences (PyCon, NDC), blogs techniques, repos GitHub trending. Experimentation : side projects, labs personnels, contributions open source. Communaute : meetups, mentorat, partage de connaissances interne (brown bags).
Mentionnez des exemples concrets recents, pas juste "je lis des articles".
Moyen 96. Comment vulgarisez-vous un concept technique pour un non-technique ?
Utiliser des analogies du quotidien. Eviter le jargon. Partir du "pourquoi" avant le "comment".
Exemple : "Le CI/CD c'est comme une chaine de montage automobile. Chaque voiture (deploy) passe par des postes de controle automatiques. Si un defaut est detecte, la chaine s'arrete avant que la voiture arrive chez le client."
L'offre mentionne "documenter et vulgariser" — c'est une competence evaluee.
Moyen 97. Un collegue deploie sans faire tourner les tests. Comment reagissez-vous ?
Ne pas blamer : comprendre pourquoi (urgence ? pipeline trop lent ? pas au courant du process ?). Resoudre la cause : si le pipeline est trop lent, l'optimiser. Si c'est un manque de formation, faire un onboarding. Systemiser : mettre en place un merge gate CI qui bloque le deploy sans tests verts.
Le probleme n'est jamais la personne, c'est le systeme qui permet de contourner les protections.
Difficile 98. Comment formez-vous une equipe qui n'a aucune culture de test ?
Semaine 1-2 : pair programming, ecrire les premiers tests ensemble sur du vrai code de l'equipe. Mois 1 : templates, fixtures partagees, CI basique (lint + unitaires). Mois 2-3 : integration tests, coverage visible. Mois 3-6 : e2e sur les parcours critiques, monitoring.
Cles : ne pas imposer, montrer la valeur. Celebrer le premier bug catch par un test. Rendre les tests faciles a ecrire (la friction tue l'adoption).
Moyen 99. Quel est votre plus gros echec professionnel et qu'avez-vous appris ?
Methode : etre honnete, montrer la reflexion et l'apprentissage, pas le blame.
Bonne reponse : "J'ai deploye un changement qui a casse la prod parce que les tests e2e etaient desactives suite a un flaky non resolu. J'ai appris que les flaky tests sont une urgence, pas un inconvenient. Depuis, j'ai un process de quarantaine systematique."
Mauvaise reponse : "Je n'ai jamais echoue" ou blamer quelqu'un d'autre.
Moyen 100. Quelle est votre vision de la QA dans 5 ans avec l'essor de l'IA ?
L'IA ne remplacera pas les QA, elle transformera le role :
Generation de tests : l'IA genere 80% des tests de regression, l'humain se concentre sur les cas exploratoires et la strategie. Analyse : detection d'anomalies automatique, root cause analysis assistee. Self-healing tests : les tests s'adaptent automatiquement aux changements d'UI mineurs.
Le dev QA devient un strategiste : choisir quoi tester, definir les SLO, concevoir les guardrails IA, evaluer les risques. L'execution est de plus en plus automatisee.
Pour Radio-Canada : l'IA est un levier pour tester a l'echelle (multi-langues, multi-plateformes, contenu genere) avec une equipe raisonnable.