Préparation entrevue

Formation QA + IA

Développeur AQ — Radio-Canada / CBC

18 Sections
4 Frameworks
1 Mini-projet
1
La pyramide de tests
Linting, unitaires, intégration, E2E

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.

E2E (Playwright/Cypress)
Intégration (vraie DB)
Unitaires (PyTest/Jest, mocks)
Linting / Typage (ESLint, mypy)
TypeMock ?Vérifie quoiVitesseQuantité
Linting / TypageNonSyntaxe, conventions, erreurs de typeInstantanéTout le code
UnitaireOui (si dépendances)La logique / les règles métierMillisecondesBeaucoup
IntégrationNonLa communication entre composantsSecondesMoyen
E2ENonLe parcours utilisateur completDizaines de secondesPeu

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.

ÉtapeTypeExemple concret
1LintingESLint détecte une variable non utilisée
2Unitaires (mocks)Le calcul de taxe retourne le bon résultat
3IntégrationL'endpoint POST /users sauvegarde bien en DB
4E2EL'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
SituationTest unitaire ?Mock ?
Tester calcul_taxe(100)OuiNon (pas de dépendance)
Tester sauvegarder_user() sans DBOuiOui (on mock la DB)
Tester avec une vraie DBNon (c'est de l'intégration)Non
En résumé : Le test unitaire est l'objectif (tester une unité isolée). Le mock est l'outil qu'on utilise quand cette unité a des dépendances qu'on ne veut pas déclencher.
Limite des mocks : Un mock ne teste que la logique ("si la DB me donne ça, mon code fait-il le bon traitement ?"). Il ne vérifie PAS si de mauvaises données peuvent réellement rentrer en BD. Pour ça, il faut des tests d'intégration avec une vraie DB.

Où valider les données ?

Utilisateur tape "abc" dans le champ téléphone | v [Frontend] --> test e2e : le formulaire refuse et affiche une erreur | v [API/Backend] --> test d'intégration : l'endpoint retourne 400 Bad Request | v [Base de données] --> contrainte SQL : CHECK (phone ~ '^\+?[0-9]+$')
2
PyTest Python
Framework de tests unitaires et d'intégration

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
Commandes :
pip install pytest
pytest -v (verbose)
pytest --cov=src (avec couverture)
3
Playwright Python JS
Tests E2E avec vrai navigateur

Pilote un vrai navigateur (Chrome, Firefox, Safari) et simule un utilisateur. Vocation principale : tests E2E.

Mais pas que du E2E

UsageExemple
E2E classiqueTester un flow de login complet
Tests d'APIpage.request.get("/api/users")
Visual regressionScreenshot 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

OutilRôle dans l'exemple
PlaywrightPilote le navigateur (clic, remplir, naviguer)
FixturePré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
PyTestLance tout, injecte les fixtures, rapporte les résultats

Quand mocker, quand ne pas mocker

ScénarioApproche
Tester le parcours utilisateur completVrai 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êtMock l'API
CI rapide (pas de DB, pas de serveur)Mock l'API
Commandes :
pip install pytest-playwright
playwright install
pytest -v
4
Cypress JavaScript
Framework E2E JavaScript

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
});
5
Jest JavaScript
Tests unitaires et d'intégration JS

C'est PyTest mais version JS. Même rôle (tests unitaires et d'intégration), syntaxe différente.

PyTest (Python)Jest (JavaScript)
MocksMagicMock()jest.fn()
Parametrize@pytest.mark.parametrizetest.each
Setup@pytest.fixturebeforeEach
Assertionsassert x == yexpect(x).toBe(y)
Coveragepytest-covIntégré (--coverage)
SnapshotsNonOui (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();
6
Fixtures & Mocks en détail
Isolation et prévisibilité des tests

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}
Rappel : Les mocks testent la logique, pas les données réelles. Pour vérifier qu'un email invalide ne rentre pas en BD, il faut un test d'intégration avec une vraie base.
7
Async / Await
Programmation asynchrone

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 ?

SituationAsync utile ?Pourquoi
Appels API / réseauOuiLe réseau est lent, on attend beaucoup
Requêtes base de donnéesOuiMême raison
Lecture de fichiersOuiAttente disque
Calcul mathématiqueNonLe CPU travaille, rien à attendre
Script simpleNonComplexité inutile
Règle simple : Si ton code attend le réseau ou le disque, async est pertinent. Si ton code calcule, non.

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"
8
IA appliquée à la QA
Génération de tests, mutation testing, outils IA

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
Commandes : pip install mutmut puis mutmut run

Comparaison des outils IA pour les tests

Qodo (ex-CodiumAI)Claude CodeCopilot
SpécialitéGénération de tests uniquementTout (tests, code, debug)Autocomplétion inline
ApprocheAnalyse la fonction, génère N scénarios autoTu prompts, il comprend le contexte largeComplète ligne par ligne
ForceRapide, structuré, clé en mainComprend l'architecture complexeZero friction dans l'IDE
FaiblesseLimité aux tests unitairesFaut savoir prompterRate les edge cases
9
CI/CD & GitHub Actions
Automatisation, pipelines, quality gates

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

ÉVÉNEMENT (push, PR) | v WORKFLOW (.github/workflows/ci.yml) | +-- JOB 1 : lint <-- tourne sur un runner Ubuntu | +-- Step 1 : checkout le code | +-- Step 2 : installer les deps | +-- Step 3 : lancer ruff | +-- JOB 2 : test <-- tourne en parallèle (ou après lint) | +-- Step 1 : checkout | +-- Step 2 : lancer pytest | +-- Step 3 : publier le rapport | +-- JOB 3 : deploy <-- seulement si test passe +-- Step 1 : déployer
ConceptDescription
WorkflowFichier YAML dans .github/workflows/. Déclenché par un événement.
Event / TriggerCe qui demarre le workflow : push, pull_request, schedule (cron)
JobUn groupe de steps. Par défaut en parallèle. needs: crée des dépendances.
StepUne commande (run:) ou une action réutilisable (uses:)
RunnerLa machine qui exécute (Ubuntu, Windows, macOS fournis par GitHub)
ActionBrique réutilisable : actions/checkout@v4, actions/setup-node@v4, etc.
SecretVariable 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

git push | v frontend-tests --------+ | backend-tests ----------+---> deploy-production (si main) | security-audit ---------+---> deploy-staging (si develop) | +---> notify (toujours)

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"
docker-compose.yml vs ci.yml : Le compose définit QUOI faire tourner (tes conteneurs). Le workflow CI définit QUAND et DANS QUEL ORDRE. C'est needs: qui crée l'ordre dans le CI, pas depends_on.
10
SRE (Site Reliability Engineering)
SLI, SLO, SLA, error budget, incidents

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"

Toujours : SLA < SLO < 100%. On se fixe un objectif plus strict que la promesse client pour avoir une marge.

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

DÉTECTER --> TRIER --> ATTENUER --> RÉSOUDRE --> POSTMORTEM (monitoring) (sévérité ?) (fix temporaire) (fix définitif) (blameless, qu'a-t-on appris ?)
11
Observabilité : les 3 piliers
Logs, métriques, traces

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 :

Tes conteneurs --> Collecteur --> Stockage --> Recherche (stdout/stderr) (Fluentd) (Elasticsearch) (Kibana) ou Azure ou Azure Monitor Logs Monitor Logs

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.

14:00:00 --> GET /metrics --> requests=4521, errors=3 14:00:15 --> GET /metrics --> requests=4538, errors=3 14:00:30 --> GET /metrics --> requests=4550, errors=7 <-- 4 erreurs en 15s 14:00:45 --> GET /metrics --> requests=4571, errors=12 <-- ça monte ! --> ALERTE : taux d'erreur > 1%
Ton app --> Prometheus (collecte toutes les 15s) | v Grafana (dashboards + alertes) | v Slack/PagerDuty "Taux d'erreur > 1%"

Pilier 3 : Traces ("pourquoi c'est lent ?")

Une trace suit une requête à travers tous les services. Chaque morceau = un span.

Requête : GET /api/appointments [850ms] ==================== | +-- check_auth [ 5ms] = +-- redis_cache_lookup [ 2ms] +-- postgres_query (row_count=2400) [800ms] ================== ^ COUPABLE : 2400 lignes retournées Solution : paginer ou ajouter un index

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

ALERTE : "Taux d'erreur > 1% depuis 5 min" | | Les MÉTRIQUES détectent le problème v DASHBOARD : le pic vient de /api/appointments | | Les TRACES montrent où ça coince v TRACE : la requête PostgreSQL prend 12s au lieu de 45ms | | Les LOGS expliquent pourquoi v LOG : "ERROR: deadlock detected on table appointments" | v Tu sais exactement quoi corriger
Sans les trois, tu navigues à l'aveugle :
  • 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

+---------------+ | Grafana | <-- dashboards + alertes +-------+-------+ | +-------------+-------------+ v v v Prometheus Jaeger Elasticsearch (métriques) (traces) (logs) ^ ^ ^ | | | +---------+-------------+-------------+---------+ | OpenTelemetry SDK | +----------+-----------+----------+-------------+ | web | api | admin-api| postgres | | (Next.js)| (Node.js) |(Node.js) | | +----------+-----------+----------+-------------+
12
Azure Cloud Atout
Services cloud, pipelines, monitoring

Radio-Canada est sur Azure. Pas besoin de tout maîtriser, mais comprendre les services principaux et savoir naviguer dans le portail.

Microsoft Learn (gratuit) : parcours "Azure Fundamentals" (AZ-900). Pas besoin de passer la certif, le contenu couvre les bases.

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

ServiceQuand l'utiliserAnalogie
App ServiceUne app web classique qui tourne en permanenceUn serveur dédié, sans le gérer
FunctionsDu code qui répond à des événements ponctuelsUn employé à la tâche, payé à l'heure
ACIUn conteneur à lancer sans infradocker run dans le cloud
AKSDizaines de microservices à orchestrerUn 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
Pour l'entrevue : Si on te demande "connaissez-vous Azure ?", tu peux répondre que tu connais les concepts (App Service, Functions, AKS, Monitor, Application Insights) et que tu as travaillé avec des pipelines CI/CD similaires (GitHub Actions). La transition est naturelle.
13
Agile / Scrum / Kanban
Méthodologies, cérémonies, rôles

Scrum en un schéma

Product Backlog Sprint (2-4 semaines) +------------------+ +--------------------------------+ | Feature A | | Sprint Backlog | | Feature B | -----> | +-- Tâche 1 [Done] | | Bug fix C | | +-- Tâche 2 [In Progress] | | Improvement D | | +-- Tâche 3 [To Do] | | ... | +--------------------------------+ +------------------+ | ^ v | Sprint Review | (demo au client) | | +---- Sprint Retro <---------------+ (qu'améliorer ?)

Les cérémonies Scrum

CérémonieQuandDureeBut
Sprint PlanningDébut de sprint2-4hChoisir quoi faire pendant le sprint
Daily StandupChaque jour15 min maxHier / Aujourd'hui / Blocages
Sprint ReviewFin de sprint1-2hMontrer ce qui a été fait (demo)
Sprint RetroFin de sprint1-1.5hQu'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).

| To Do (max 10) | In Progress (max 3) | Review (max 2) | Done | |----------------|---------------------|----------------|------| | Feature E | Feature B | Feature A | ... | | Bug fix F | Bug fix C | | | | Feature G | Feature D | | | | ... | | | | ^ | WIP limit = 3 : on ne peut pas commencer Feature E tant qu'un slot ne se libère pas
ScrumKanban
CadenceSprints fixes (2-4 semaines)Flux continu
RolesPO, Scrum Master, TeamPas de rôles imposés
ChangementsPas de changement en cours de sprintNouvelles tâches à tout moment
Indicateur cléVelocity (points par sprint)Lead time (temps de traversée)
Idéal pourProjets avec releases planifiéesMaintenance, 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

Approche traditionnelle : Design --> Dev --> Dev --> Dev --> QA (à la fin, trop tard) ^ bugs découverts tard = coûteux Shift-Left : Design + QA --> Dev + QA --> Dev + QA --> Deploy ^ ^ ^ tests des tests en tests auto le design continu dans le CI
Shift-Left = tester le plus tôt possible. Le QA participe dès le design (questions sur les edge cases, critères d'acceptation). Les tests automatisés tournent à chaque commit, pas à la fin du sprint.
14
Stratégies de test pour systèmes IA
Data validation, regression, chaos engineering

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.

On ne teste PAS le modèle lui-même (c'est le travail des data scientists). On teste tout ce qui est autour : les données qui entrent, les prédictions qui sortent, et comment le système réagit.

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 : +------------------+ +------------------+ | 90% des users | | 10% des users | | Model v1 | | Model v2 | | (contrôle) | | (test) | +------------------+ +------------------+ | | v v Comparer les métriques : clics, satisfaction, erreurs Canary Deployment : +-----------+ OK? +-----------+ OK? +-----------+ | 5% trafic | ----> | 25% trafic| ----> | 100% | | sur v2 | | sur v2 | | sur v2 | +-----------+ +-----------+ +-----------+ | | | Problème ? | Problème ? v v Rollback Rollback

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

+-------------------------------------------------------+ | TESTS E2E | | Le parcours utilisateur fonctionne avec l'IA | +-------------------------------------------------------+ | TESTS D'INTÉGRATION | | L'API communique correctement avec le modèle | +-------------------------------------------------------+ | INPUT/OUTPUT | PERFORMANCE REGRESSION | | VALIDATION | accuracy >= seuil | | format correct ? | latence < seuil | +-------------------------------------------------------+ | DATA VALIDATION | | Les données d'entraînement sont propres | | Schema correct, distribution équilibrée | +-------------------------------------------------------+
15
Résumé pour l'entrevue
Tableaux récapitulatifs de tous les sujets

Frameworks de test

ConceptPyTestPlaywrightCypressJest
Fixtures / Setup@pytest.fixturepage: PagebeforeEachbeforeEach
MocksMagicMock-cy.interceptjest.fn()
Multi-casparametrize--test.each
Assertionsassertexpect(locator).should()expect()
Snapshots-Screenshots-toMatchSnapshot()

CI/CD

ConceptÀ retenir
Pipeline as codeYAML dans le repo, versionné avec git
Stageslint > test > security > deploy (needs: crée l'ordre)
Quality gatesCouverture min, zero vuln critique, lint propre
Branch protectionBloquer le merge si un check échoue
compose vs CIcompose = QUOI tourner, CI = QUAND et DANS QUEL ORDRE

SRE & Observabilité

ConceptÀ retenirExemple
SLILa mesure"99.2% sous 200ms"
SLOL'objectif"On vise 99.5%"
Error budgetLe droit à l'erreur"3h36 de downtime/mois"
LogsQuoi s'est passé"duplicate key sur user X"
MétriquesCombien, en ce moment"150 req/s, 0.8% erreurs"
TracesOù et pourquoi"La requête SQL prend 12s"
OpenTelemetryLe standardSDK unique pour les 3 piliers
PrometheusCollecte métriques (pull)GET /metrics toutes les 15s
GrafanaDashboards + alertesGraphes, seuils, notifications
JaegerVisualise les tracesTimeline des spans

Azure

ServiceÀ retenir
App ServiceHéberger une app web/API
FunctionsCode serverless, payé à l'exécution
AKSKubernetes managé pour microservices
Azure DevOpsRepos + Pipelines + Boards (alternative GitHub)
Application InsightsMonitoring applicatif (traces + métriques)
Key VaultStocker les secrets

Agile

ConceptÀ retenir
SprintItération de 2-4 semaines
Daily standup15 min : hier / aujourd'hui / blocages
Définition of DoneChecklist partagée : code review, tests, CI vert
Shift-LeftTester le plus tôt possible, QA dès le design
KanbanFlux continu avec WIP limits, pas de sprints

Tests pour systemes IA

TypeVérifie quoi
Data validationDonnées propres, schéma correct, distribution équilibrée
Performance regressionAccuracy et latence au-dessus des seuils
I/O validationFormat des prédictions correct et cohérent
A/B testing / CanaryDéploiement progressif, comparaison des versions
Chaos engineeringLe système résiste si le modèle est down

Phrase clé pour l'entrevue

"Comment mettriez-vous en place de l'observabilité ?"

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.
16
Mini-projet pratique
Projet complet démontrant toutes les compétences

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

mini-projet-qa/ +-- app/ | +-- main.py # API FastAPI + health + métriques | +-- sentiment.py # "Modèle IA" analyse de sentiments | +-- logger.py # Logs structurés JSON +-- tests/ | +-- test_sentiment.py # Tests unitaires du modèle | +-- test_api.py # Tests d'intégration de l'API | +-- e2e/ | +-- test_api_e2e.py # Tests e2e avec Playwright +-- .github/workflows/ | +-- ci.yml # Pipeline CI/CD complet +-- requirements.txt +-- pyproject.toml

Ce que ça démontre

Compétence du posteOù dans le projet
Tests unitaires (PyTest)tests/test_sentiment.py
Tests d'intégrationtests/test_api.py
Tests e2e (Playwright)tests/e2e/test_api_e2e.py
Validation de données IAtest_sentiment.py (schéma, distribution)
CI/CD (GitHub Actions).github/workflows/ci.yml
Quality gatesCoverage > 80%, linting, security scan
Logs structurésapp/logger.py (JSON, request_id)
Métriques/metrics endpoint
Health check/health endpoint
Mutation testingConfig mutmut dans pyproject.toml
17
TypeScript JavaScript
Syntaxe de base, types, interfaces, generics

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

interfacetype
ObjetsOui (usage principal)Oui
Union / IntersectionNontype Status = "active" | "inactive"
Extendextends& (intersection)
Declaration mergingOuiNon
// 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'
Règle simple : interface pour les objets, type pour les unions et les alias. En cas de doute, interface.
18
Rappels Python Python
Dataclass, Enum, héritage, yield, fixtures, mocks, CRUD

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
CasOutil
Stocker des données (struct)@dataclass
Données immuables@dataclass(frozen=True)
Valeurs fixes/constantesEnum

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écanismeDescription
HéritageLa fille récupère tout de la mère
SurchargeLa fille remplace une méthode
ExtensionLa fille ajoute des méthodes
super()La fille appelle la mère
Classe abstraite = force la surcharge. 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)
Attention : @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)
Flow : 1. Setup → 2. yield (pause) → 3. Test tourne → 4. Teardown

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.

ContexteCRUD
GmailÉcrireLireModifier brouillonSupprimer
InstagramPosterVoir feedÉditer descriptionSupprimer post
SQLINSERTSELECTUPDATEDELETE
API RESTPOSTGETPUTDELETE

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)
VitesseInstantanéLent
TesteTa logique / ton codeLa vraie connexion
Quand ça casseBug dans ton codeBug config / schema / réseau
QuantitéBeaucoupPeu
En pratique : plein de tests unitaires mockés (rapides), quelques tests d'intégration avec la vraie BDD (lents, souvent en CI).
19
Q&A Observabilité Entrevue
OpenTelemetry, Prometheus, Grafana — questions types d'entretien

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.

ComposantRôle
APIInterface abstraite utilisée dans le code applicatif
SDKImplémentation concrète : crée spans, métriques, logs
OTLPProtocole filaire (gRPC sur 4317, HTTP sur 4318) entre SDK ↔ Collector ↔ backend
CollectorProcess séparé : receiversprocessorsexporters
Pitch d'entretien : OTel découple l'instrumentation du backend. On change de vendor (Datadog → Grafana) sans retoucher le code — on change juste l'exporter du Collector.

Q1 — Métriques, logs, traces : quand utiliser lequel ?

TypeQuestion à laquelle ça répondCoû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)
Règle pratique : alerte sur les métriques, investigue avec traces + logs. Les métriques disent qu'il y a un problème, les traces disent , les logs disent pourquoi.

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.

Réponse courte : Prometheus vient de Borgmon (Google). Infra interne, stable, pull simplifie l'opérationnel via service discovery. Pour les jobs courts : 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
Jamais en label : user_id, request_id, email, UUID, timestamp, URL complète avec paramètres. Ces infos vont dans les logs ou les traces, pas dans les métriques. Pour relier un spike de p95 à une requête précise → exemplars (pointeur de trace attaché à un bucket d'histogramme).

Q4 — Les 4 Golden Signals (Google SRE)

SignalDescriptionPiège
LatencyTemps de réponseSéparer succès vs échecs (un 500 ultra-rapide masque la latence réelle)
TrafficDemande sur le système (req/s)
ErrorsTaux 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
Variantes : 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.

En pratique : toujours Histogram. OTel a introduit les exponential histograms qui règlent le problème du choix de buckets.

Q6 — Pourquoi un Collector plutôt qu'exporter direct depuis l'app ?

RaisonBénéfice
Découplage vendorChanger de backend = modifier une ligne de config, zéro redéploiement
OffloadBatch, 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-destinationMême donnée vers Prometheus + Tempo + SIEM simultanément
Sécurité / réseauLes 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]))
Piège classique : ne jamais afficher un counter brut — toujours rate() ou increase() dessus. Un counter ne fait que monter, ce n'est pas lisible tel quel.

Différence Prometheus UI / Grafana / Tempo

OutilRôleCe qu'on y voit
Prometheus UITSDB + requêteur PromQL brutGraphes de métriques, cibles scrapées, règles d'alerte
GrafanaUI unifiée branchée sur des datasourcesDashboards, Explore, alertes, corrélation métriques ↔ traces
TempoBackend de stockage de tracesSpans individuels d'une requête, durée par étape
Flow type d'investigation : alerte déclenchée (Prometheus) → dashboard Grafana montre un spike p95 → clic sur un exemplar → ouverture du trace dans Tempo → identification du span lent (ex: requête SQL) → correction.
20
100 Questions d'entretien Radio-Canada
Premier dev AQ IA — Cliquez pour reveler les reponses
A. Assurance qualite fondamentale (1-15)
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) → IntegrationE2E (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 ?

B. Python et TypeScript (16-25)
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 ?
PythonTypeScript
Runtimeasyncio event loopEvent loop V8 natif
Executionawait asyncio.gather(a(), b())await Promise.all([a(), b()])
PiegeOublier 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.

C. IA appliquee a l'assurance qualite (26-40)
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).

D. CI/CD et DevOps (41-55)
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.

E. SRE et observabilite (56-70)
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)
DecouvertePrometheus sait qui scraperL'app doit connaitre le collector
FirewallPrometheus doit acceder aux ciblesL'app initie (plus facile)
Jobs courtsDifficile (finit avant le scrape)Naturel (push avant de mourir)
Health checkGratuit (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.
SignalMesurePiege
LatencyTemps de reponseSeparer succes vs echecs (un 500 rapide masque la latence reelle)
Trafficreq/s
ErrorsTaux 5xxAttention aux 200 avec mauvais contenu
SaturationCPU, RAM, queueLe 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.

F. Agile et methodologie (71-80)
Facile 71. Differences entre Scrum et Kanban ?
ScrumKanban
CadenceSprints fixes (2 semaines)Flux continu
RolesPO, SM, equipePas de roles prescrits
PlanningSprint planningPas de planning formel
WIPEngagement par sprintLimites WIP par colonne
ChangementsPas pendant le sprintA 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) :

MetriqueEliteMoyen
Frequence de deploiementMultiple/jour1x/semaine
Lead time (commit → prod)<1h1 semaine
Taux d'echec des deploys<5%15%
Temps de restauration (MTTR)<1h1 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 ?
SeveriteImpactAction
CritiqueApp down, perte de donnees, securiteFix immediat, hotfix hors sprint
MajeurFeature cassee pour beaucoup d'usersProchain sprint, haute priorite
MineurBug cosmetique, contournement existeBacklog, quand il y a de la place
TrivialTypo, alignement CSSFix 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.

G. Securite et Azure Cloud (81-90)
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.

H. Soft skills et culture Radio-Canada (91-100)
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.