File size: 16,107 Bytes
5a84604
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd61df2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Microservices Testing — TicTacToe</title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gradient-to-br from-indigo-50 to-sky-100 min-h-screen flex items-center justify-center p-6">
  <div class="w-full max-w-5xl bg-white rounded-2xl shadow-xl p-6">
    <h1 class="text-3xl md:text-4xl font-extrabold text-center text-indigo-700">Microservices Testing — TicTacToe</h1>
    <p class="text-center text-sm text-gray-600 mt-2">
      Source:
      <a class="text-blue-600 underline" target="_blank" rel="noopener"
         href="https://www.linkedin.com/pulse/automated-testing-microservices-from-concept-practice-michael-lively-gamte/">
        Automated Testing in Microservices: From Concept to Practice — Michael Lively
      </a>
    </p>

    <div id="banner" class="hidden bg-indigo-100 border border-indigo-300 text-indigo-900 font-semibold text-center py-2 rounded mt-4"></div>

    <div class="mt-6 flex flex-col md:flex-row md:space-x-6 space-y-4 md:space-y-0">
      <!-- Board -->
      <div id="board" class="grid grid-cols-3 gap-2 p-2 bg-gray-50 rounded-xl border-4 border-gray-200 mx-auto"></div>

      <!-- Question Panel -->
      <div id="questionPanel" class="md:w-1/2 w-full bg-gray-50 rounded-xl shadow-inner p-4">
        <h2 id="panelQuestion" class="text-lg md:text-xl font-semibold text-gray-800">
          Select a square to view the question
        </h2>
        <ul id="panelChoices" class="mt-4 space-y-2"></ul>
        <p id="panelHint" class="mt-3 text-sm text-gray-600 hidden"></p>
        <div class="mt-4 flex items-center space-x-2">
          <button id="hintBtn" class="px-3 py-1.5 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">Show Hint</button>
          <button id="clearBtn" class="px-3 py-1.5 bg-white border rounded hover:bg-gray-100">Clear Panel</button>
        </div>
      </div>
    </div>

    <div class="flex flex-col md:flex-row md:items-center md:justify-between mt-6 space-y-3 md:space-y-0">
      <p id="status" class="text-lg font-medium text-gray-800"></p>
      <div class="flex items-center space-x-2">
        <label class="text-sm text-gray-600">Mode:</label>
        <select id="mode" class="border rounded px-2 py-1 text-sm">
          <option value="two">Two Players (X vs O)</option>
          <option value="solo">Solo (You are X)</option>
        </select>
        <button id="restartBtn" class="px-4 py-2 bg-indigo-700 text-white rounded hover:bg-indigo-800">Restart</button>
      </div>
    </div>
  </div>

  <script>
    // --- Utilities ---
    const randShuffle = (arr) => arr
      .map(x => ({ x, r: Math.random() }))
      .sort((a,b) => a.r - b.r)
      .map(o => o.x);

    // --- Question bank (answers identified by 'answer' index BEFORE shuffle) ---
    // Built from your provided content: automation benefits, test layers, CI/CD, tools, trends, and best practices.
    const QUESTION_BANK = [
      {
        question: 'Why is manual testing insufficient for microservices at scale?',
        choices: [
          'Microservices never change once deployed',
          'Manual testing is slow, error-prone, and can’t keep pace with highly distributed systems',
          'Because UIs don’t need testing',
          'It costs more than writing the entire system again'
        ],
        answer: 2,
        hint: 'Think speed + distribution + repeatability.'
      },
      {
        question: 'A core benefit of automated testing is that tests are…',
        choices: ['Randomized', 'Deterministic and reproducible', 'UI-only', 'Manual by default'],
        answer: 2,
        hint: 'Same inputs → same results.'
      },
      {
        question: 'Which layer validates a single small unit like a function or method?',
        choices: ['Integration testing', 'End-to-end testing', 'Unit testing', 'Contract testing'],
        answer: 3,
        hint: 'Foundation of the pyramid.'
      },
      {
        question: 'Which testing ensures services communicate correctly together?',
        choices: ['Integration testing', 'Accessibility testing', 'Static analysis', 'Exploratory testing'],
        answer: 1,
        hint: 'Service-to-service behavior.'
      },
      {
        question: 'Which approach guarantees a service still fulfills expectations when it changes?',
        choices: ['Contract testing', 'Chaos testing', 'Snapshot testing', 'Golden master testing'],
        answer: 1,
        hint: 'Inputs/outputs pact.'
      },
      {
        question: 'Which is best described as simulating user interactions across the system, including dependencies?',
        choices: ['Functional testing', 'Linting', 'Schema migration testing', 'Fuzzing only'],
        answer: 1,
        hint: 'User-focused workflows.'
      },
      {
        question: 'Black-box validation of the whole architecture against business goals after deployment is…',
        choices: ['End-to-end testing', 'Mutation testing', 'Smoke testing', 'Code coverage'],
        answer: 1,
        hint: 'System-level validation.'
      },
      {
        question: 'Where does automation fit in a modern pipeline?',
        choices: [
          'Only after production',
          'Embedded in CI/CD with automated gates and rapid feedback',
          'Only on developer laptops',
          'Only in long quarterly test cycles'
        ],
        answer: 2,
        hint: 'Shift earlier, fail faster.'
      },
      {
        question: 'Which tool is commonly used for load/performance testing in microservices?',
        choices: ['Selenium', 'Apache JMeter or Gatling', 'Pa11y', 'Axe-core'],
        answer: 2,
        hint: 'Think throughput and latency.'
      },
      {
        question: 'Which tool focuses on distributed tracing for service interactions?',
        choices: ['Jaeger', 'Pact', 'Hoverfly', 'Testcontainers'],
        answer: 1,
        hint: 'Trace requests across services.'
      },
      {
        question: 'Which tool supports contract testing for APIs and messaging?',
        choices: ['Pact', 'Jaeger', 'Axe-core', 'Katalon'],
        answer: 1,
        hint: 'Consumer–provider agreements.'
      },
      {
        question: 'Which helps create ephemeral, repeatable dependencies (DBs, queues) for tests via containers?',
        choices: ['Hoverfly', 'Testcontainers', 'Selenium Grid', 'Pa11y'],
        answer: 2,
        hint: 'Spin up what you need on demand.'
      },
      {
        question: 'Which helps with service virtualization and integration testing when dependencies are unavailable?',
        choices: ['Hoverfly', 'SBOM', 'Gitleaks', 'Snyk Code'],
        answer: 1,
        hint: 'Simulate services.'
      },
      {
        question: 'Which category accelerates test creation without heavy scripting?',
        choices: ['Codeless platforms (e.g., ACCELQ, Katalon)', 'Shell scripts only', 'Makefiles only', 'SQL migrations'],
        answer: 1,
        hint: 'Bridge dev and non-dev testers.'
      },
      {
        question: 'AI-driven testing in 2025 commonly does what?',
        choices: [
          'Ignores failing tests',
          'Prioritizes cases, generates scripts, and self-heals tests',
          'Removes CI entirely',
          'Eliminates the need for version control'
        ],
        answer: 2,
        hint: 'Agentic assist + resilience.'
      },
      {
        question: 'Which supports accessibility automation against WCAG?',
        choices: ['Axe / Pa11y', 'curl / jq', 'Terraform', 'Prometheus'],
        answer: 1,
        hint: 'Inclusive UX checks.'
      },
      {
        question: 'Shift-left security in pipelines commonly includes:',
        choices: [
          'SAST/DAST, SBOM generation, and image signing',
          'Only manual penetration tests',
          'Turning off TLS for speed',
          'Ignoring dependency vulnerabilities'
        ],
        answer: 1,
        hint: 'Security as code, early.'
      },
      {
        question: 'Why use ephemeral environments for testing?',
        choices: [
          'They are slower but cheaper',
          'They ensure clean states per run and avoid “works on my machine”',
          'They replace production entirely',
          'They require manual setup every time'
        ],
        answer: 2,
        hint: 'Spin up, test, tear down.'
      },
      {
        question: 'A key best practice to avoid tight coupling in tests is to…',
        choices: [
          'Always test with every real dependency',
          'Mock or virtualize dependencies when appropriate',
          'Skip integration tests',
          'Use only UI tests'
        ],
        answer: 2,
        hint: 'Minimize brittle cross-service reliance.'
      },
      {
        question: 'Prioritization guidance in complex systems suggests testing…',
        choices: [
          'Low-value edge features first',
          'Only the UI',
          'High-risk, business-critical services first',
          'Only the database layer'
        ],
        answer: 3,
        hint: 'Risk-driven focus.'
      },
      {
        question: 'Which tool pairing is most appropriate: performance metrics store?',
        choices: ['InfluxDB', 'Selenium IDE', 'Kibana only', 'Cypress'],
        answer: 1,
        hint: 'Time-series for perf data.'
      },
      {
        question: 'Functional automation frameworks like Selenium are strongest for…',
        choices: ['API contract validation', 'UI interaction simulation', 'Tracing spans', 'Generating SBOMs'],
        answer: 2,
        hint: 'Clicks, forms, flows.'
      },
      {
        question: 'Scalable fuzzing in microservices helps by…',
        choices: [
          'Generating random inputs to uncover edge cases across services',
          'Replacing unit tests',
          'Eliminating the need for logs',
          'Forcing 100% code coverage automatically'
        ],
        answer: 1,
        hint: 'Probe the unknowns.'
      },
      {
        question: 'In CI, a typical sequence is:',
        choices: [
          'Deploy to prod → write tests → run tests',
          'Build → test (unit/integration/contract) → gate → stage deploy',
          'Write docs → ship',
          'Only manual QA after release'
        ],
        answer: 2,
        hint: 'Feedback before promotion.'
      }
    ];

    // --- Game State ---
    let currentPlayer, boardState, selectedCell, cellQuestions, mode;
    const boardEl = document.getElementById('board');
    const statusEl = document.getElementById('status');
    const bannerEl = document.getElementById('banner');
    const hintBtn = document.getElementById('hintBtn');
    const clearBtn = document.getElementById('clearBtn');
    const panelQuestion = document.getElementById('panelQuestion');
    const panelChoices = document.getElementById('panelChoices');
    const panelHint = document.getElementById('panelHint');
    const restartBtn = document.getElementById('restartBtn');
    const modeSel = document.getElementById('mode');

    function initGame() {
      mode = modeSel.value;
      currentPlayer = 'X';
      boardState = Array(9).fill('');
      bannerEl.classList.add('hidden');
      statusEl.innerText = '';
      selectedCell = null;

      // Pick 9 questions at random, then for each, shuffle choices and remap the correct answer index
      const picked = randShuffle(QUESTION_BANK).slice(0, 9).map(q => {
        const original = q.choices.map((c, i) => ({ text: c, idx: i + 1 }));
        const shuffled = randShuffle(original);
        const newChoices = shuffled.map(o => o.text);
        const newAnswerIndex = shuffled.findIndex(o => o.idx === q.answer) + 1;
        return {
          question: q.question,
          choices: newChoices,
          answer: newAnswerIndex,
          hint: q.hint || ''
        };
      });

      cellQuestions = picked;
      panelQuestion.innerText = 'Select a square to view the question';
      panelChoices.innerHTML = '';
      panelHint.classList.add('hidden');
      panelHint.innerText = '';

      renderBoard();
    }

    function renderBoard() {
      boardEl.innerHTML = '';
      boardState.forEach((mark, idx) => {
        const btn = document.createElement('button');
        let cls = 'bg-white h-24 w-24 md:h-28 md:w-28 flex items-center justify-center text-3xl font-extrabold rounded-xl shadow hover:bg-gray-100 transition';
        if (mark === 'X') cls += ' text-indigo-700';
        if (mark === 'O') cls += ' text-sky-700';
        btn.className = cls;
        btn.innerText = mark;
        btn.disabled = mark !== '';
        btn.addEventListener('click', () => openQuestion(idx));
        boardEl.appendChild(btn);
      });
      statusEl.innerText = `Current: ${currentPlayer}`;
    }

    function openQuestion(idx) {
      selectedCell = idx;
      const q = cellQuestions[idx];
      panelQuestion.innerText = q.question;
      panelChoices.innerHTML = '';
      panelHint.classList.add('hidden');
      panelHint.innerText = q.hint || '';
      q.choices.forEach((c, i) => {
        const li = document.createElement('li');
        const btn = document.createElement('button');
        btn.innerText = c;
        btn.className = 'w-full text-left px-4 py-2 bg-gray-100 rounded hover:bg-gray-200';
        btn.addEventListener('click', () => handleAnswer(i + 1));
        li.appendChild(btn);
        panelChoices.appendChild(li);
      });
    }

    function handleAnswer(choice) {
      const q = cellQuestions[selectedCell];
      if (choice === q.answer) {
        boardState[selectedCell] = currentPlayer;
        renderBoard();
        if (checkWin(currentPlayer)) return endGame(`${currentPlayer} wins!`);
        if (boardState.every(c => c)) return endGame('Stalemate!');
        currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
        statusEl.innerText = `Current: ${currentPlayer}`;
        if (mode === 'solo' && currentPlayer === 'O') {
          setTimeout(aiMove, 450);
        }
      } else {
        alert('Incorrect! Turn missed.');
        currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
        statusEl.innerText = `Current: ${currentPlayer}`;
        if (mode === 'solo' && currentPlayer === 'O') {
          setTimeout(aiMove, 450);
        }
      }
    }

    function checkWin(player) {
      const wins = [
        [0,1,2],[3,4,5],[6,7,8],
        [0,3,6],[1,4,7],[2,5,8],
        [0,4,8],[2,4,6]
      ];
      return wins.some(combo => combo.every(i => boardState[i] === player));
    }

    function endGame(msg) {
      bannerEl.innerText = `🎉 ${msg}`;
      bannerEl.classList.remove('hidden');
      document.querySelectorAll('#board button').forEach(b => b.disabled = true);
    }

    // Simple AI: pick a random open square; 55% chance to "answer" correctly.
    function aiMove() {
      const open = boardState.map((v,i) => v === '' ? i : null).filter(v => v !== null);
      if (open.length === 0) return;
      const pick = open[Math.floor(Math.random() * open.length)];
      const q = cellQuestions[pick];
      const aiCorrect = Math.random() < 0.55;
      if (aiCorrect) {
        boardState[pick] = 'O';
        renderBoard();
        if (checkWin('O')) return endGame('O wins!');
        if (boardState.every(c => c)) return endGame('Stalemate!');
        currentPlayer = 'X';
        statusEl.innerText = `Current: ${currentPlayer}`;
      } else {
        currentPlayer = 'X';
        statusEl.innerText = `Current: ${currentPlayer}`;
      }
    }

    hintBtn.addEventListener('click', () => panelHint.classList.toggle('hidden'));
    clearBtn.addEventListener('click', () => {
      panelQuestion.innerText = 'Select a square to view the question';
      panelChoices.innerHTML = '';
      panelHint.classList.add('hidden');
      panelHint.innerText = '';
    });
    restartBtn.addEventListener('click', initGame);
    modeSel.addEventListener('change', initGame);

    // Start
    initGame();
  </script>
</body>
</html>