·

Tiếng Việt: Risk-based exploration

Risk-based exploration

Một trong những câu hỏi đau đầu nhất của mọi QA có kinh nghiệm không phải là "làm sao để test kỹ hơn", mà là "test cái gì trước, khi thời gian chỉ có 2 giờ mà sprint có 40 file thay đổi". Exploratory testing (kiểm thử khám phá) truyền thống dựa vào trực giác và kinh nghiệm cá nhân để trả lời câu hỏi này. Nhưng trực giác có giới hạn: nó không scale khi codebase lớn lên, khi team luân chuyển người, hoặc khi một junior QA mới vào chưa có đủ "cảm giác" về hệ thống.

Risk-based exploration (khám phá dựa trên rủi ro) là cách tiếp cận dùng dữ liệu thay đổi thực tế — diff, changelog, lịch sử bug — làm nền tảng để quyết định nơi cần đào sâu. Và đây chính là chỗ AI phát huy giá trị rõ rệt nhất: agent có thể đọc hàng trăm dòng diff, đối chiếu với kiến trúc hệ thống, và đưa ra bản đồ rủi ro trong vài giây — việc mà con người phải mất hàng giờ để làm thủ công. Bài này sẽ đi từ việc dùng diff làm input, đến việc xây dựng coverage map (bản đồ độ phủ) sống, cập nhật liên tục theo từng sprint.

Làm Sao Dùng Code Diff Và Change Log Để Khoanh Vùng Session Khám Phá?

Vì Sao Code Diff Là Input Giá Trị Nhất Để Xác Định Phạm Vi Khám Phá

Khi bắt đầu một session exploratory testing, câu hỏi đầu tiên luôn là: "hôm nay tôi nên test module nào?". Rất nhiều QA trả lời câu hỏi này bằng cách nhìn vào ticket trên Jira, đọc mô tả feature, rồi đoán xem chỗ nào "có thể" bị ảnh hưởng. Cách làm này có một lỗ hổng lớn: mô tả ticket chỉ nói về intent (mục đích) của thay đổi, không nói về implementation (cách triển khai) thực tế — và chính implementation mới là nơi bug ẩn nấp.

Code diff thì khác. Diff là sự thật tuyệt đối về những gì đã thay đổi trong codebase — không có khoảng cách giữa "được yêu cầu làm" và "thực sự đã làm". Một ticket ghi "sửa lỗi hiển thị số dư âm" có thể chỉ đổi 3 dòng, nhưng diff thực tế lại cho thấy dev đã refactor luôn cả hàm tính toán chung, ảnh hưởng đến 5 chỗ gọi khác trong hệ thống. Nếu chỉ đọc ticket, QA sẽ bỏ sót hoàn toàn 5 điểm rủi ro đó.

Đây là lý do tại sao trong các team QA trưởng thành, câu hỏi mở đầu buổi kiểm thử khám phá không còn là "feature này làm gì" mà là "diff này chạm vào những gì". Khi đưa agent vào vai trò đọc diff, ta có được một trợ lý phân tích rủi ro tức thời, không bị cảm xúc hay áp lực deadline chi phối như con người.

Mẹo: Luôn yêu cầu AI phân tích diff ở cấp độ pull request đầy đủ (git diff main...feature-branch), không phải diff của một commit lẻ. Diff một commit có thể thiếu ngữ cảnh của các thay đổi trước đó trong cùng nhánh, dẫn đến đánh giá rủi ro sai lệch.

Prompt Pattern: Diff-to-Risk Analysis

Prompt pattern (mẫu prompt) hiệu quả nhất cho việc này gồm ba phần: (1) cung cấp diff thô, (2) cung cấp một chút ngữ cảnh về nghiệp vụ/kiến trúc, và (3) yêu cầu output có cấu trúc rõ ràng (điểm rủi ro, lý do, khu vực cần test). Dưới đây là prompt tôi thường dùng khi review diff trước một session khám phá:

You are a senior QA engineer performing risk analysis on a code diff
before an exploratory testing session.

CONTEXT:
- Application: e-commerce checkout service (Node.js + PostgreSQL)
- This diff is part of ticket CHK-482: "Support partial refunds for
  split-payment orders"
- Production traffic: ~50k checkout attempts/day
- This module has had 3 payment-related incidents in the last 6 months

TASK:
1. Read the diff below.
2. For EACH changed file, identify:
   - What behavior actually changed (not just what the ticket says)
   - Risk level: HIGH / MEDIUM / LOW
   - Why (technical reason: state mutation, shared function, no test
     coverage, touches money/auth/data-integrity, etc.)
3. Flag any change that touches shared/utility code used elsewhere,
   since blast radius is larger there.
4. Output a table: File | Risk | Reason | Suggested exploration focus
5. End with a ranked list of the top 3 areas I should spend my limited
   exploratory testing time on, and why those 3 over the rest.

DIFF:
<paste diff here>

Điểm quan trọng trong prompt này: (1) cung cấp bối cảnh về lịch sử incident (giúp AI hiểu "khu vực nhạy cảm" theo lịch sử, không chỉ theo code), (2) yêu cầu rõ output dạng bảng để dễ scan nhanh, (3) yêu cầu AI tự giới hạn xuống top 3 — buộc nó phải ưu tiên thật, không chỉ liệt kê chung chung "mọi thứ đều có rủi ro".

Làm Việc Với Diff Output Thực Tế

Để thấy rõ giá trị, hãy xem một ví dụ diff thực tế và output phân tích của AI. Giả sử đây là diff cho ticket refund một phần nói trên:

diff --git a/src/services/payment/refundService.js b/src/services/payment/refundService.js
index 3a1f2e0..9c7b4d1 100644
--- a/src/services/payment/refundService.js
+++ b/src/services/payment/refundService.js
@@ -12,15 +12,28 @@ class RefundService {
-  async processRefund(orderId, amount) {
-    const order = await this.orderRepo.findById(orderId);
-    await this.paymentGateway.refund(order.paymentId, amount);
-    order.status = 'refunded';
+  async processRefund(orderId, amount, options = {}) {
+    const order = await this.orderRepo.findById(orderId);
+    const isPartial = amount < order.totalAmount;
+    if (order.paymentMethod === 'split') {
+      return this.processSplitRefund(order, amount, options);
+    }
+    await this.paymentGateway.refund(order.paymentId, amount);
+    order.status = isPartial ? 'partially_refunded' : 'refunded';
+    order.refundedAmount = (order.refundedAmount || 0) + amount;
     await this.orderRepo.save(order);
+    this.eventBus.emit('order.refunded', { orderId, amount, isPartial });
     return order;
   }
+
+  async processSplitRefund(order, amount, options) {
+    // new: distributes refund proportionally across payment splits
+    const splits = await this.splitPaymentRepo.findByOrderId(order.id);
+    ...
+  }

diff --git a/src/utils/currency.js b/src/utils/currency.js
index 8e21ab3..1d9f003 100644
--- a/src/utils/currency.js
+++ b/src/utils/currency.js
@@ -4,7 +4,7 @@ function roundToCents(value) {
-  return Math.round(value * 100) / 100;
+  return Math.round((value + Number.EPSILON) * 100) / 100;
 }

Output phân tích rủi ro mà AI trả về (đây là ví dụ thực tế điển hình, không phải kịch bản lý tưởng hóa):

| File                  | Risk   | Reason                                                                                                     | Suggested exploration focus                                                                 |
|-----------------------|--------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
| refundService.js      | HIGH   | New branching logic (isPartial, split payment) directly mutates order.status and refundedAmount. Money-related state changes with no visible new unit test in diff. Event `order.refunded` payload changed shape — any listener expecting old shape may break silently. | Refund flow for split-payment orders, especially multiple partial refunds on the same order (does refundedAmount accumulate correctly? can it exceed totalAmount?) |
| refundService.js      | HIGH   | processSplitRefund is new and untested in this diff; "distributes proportionally" is a rounding-prone calculation touching money. | Edge cases: refund amount that doesn't divide evenly across splits, refund larger than one split's share, refund on an order with only 1 split. |
| currency.js            | MEDIUM | roundToCents is a SHARED utility — likely used across pricing, tax, refund, invoice. A change to floating-point rounding behavior, even a "bug fix", can shift totals by fractions of a cent in many unrelated flows simultaneously. | Regression-style spot check on invoice totals, tax calculation, and any place displaying currency values — not just refund. |

TOP 3 PRIORITY AREAS FOR YOUR LIMITED TIME:
1. Split-payment partial refund correctness (refundedAmount accumulation,
   over-refund prevention) — highest business/money risk, newest code.
2. currency.js rounding change blast radius — small change, but shared
   utility means it can silently affect totals in modules NOT mentioned
   in this ticket at all.
3. order.refunded event payload change — check any downstream consumer
   (notifications, analytics, reconciliation jobs) that reads isPartial
   or the new payload shape.

Chú ý cách AI tự phát hiện currency.js là rủi ro MEDIUM dù đây chỉ là "one-line fix" — vì nó là hàm dùng chung (shared function). Đây chính là loại insight (nhận định) mà một QA mới vào nghề hoặc không quen đọc code dễ bỏ sót, nhưng lại là nguồn gốc của rất nhiều bug "ở nơi không ai ngờ tới".

Đọc Change Log Để Đánh Giá Rủi Ro Tích Lũy

Diff của một PR đơn lẻ cho ta rủi ro tại một thời điểm. Nhưng rủi ro thực sự thường tích lũy qua nhiều PR liên tiếp trong cùng một khu vực code — mỗi thay đổi riêng lẻ trông "an toàn", nhưng cộng dồn lại thì khu vực đó đã biến đổi hoàn toàn so với lần cuối được test kỹ.

Cách xử lý: định kỳ (cuối sprint, hoặc trước một đợt release) đưa AI đọc change log tích lũy — có thể lấy từ git log --oneline kết hợp git diff giữa hai điểm mốc, hoặc từ changelog tool nội bộ team đang dùng.

Below is the commit history for the `payment/` directory over the last
14 days (git log --stat). Analyze this as a CUMULATIVE change pattern,
not individual commits.

Identify:
1. Which files changed most frequently (churn) — high churn + no
   corresponding test file changes is a red flag.
2. Any file that changed in 3+ separate commits by different authors —
   suggests unclear ownership or a contentious/fragile area.
3. Net behavioral drift: comparing the state 14 days ago to now, what
   has meaningfully changed about how this module behaves, even if no
   single commit looks risky?

<paste git log --stat output>

Cách nghĩ này đặc biệt hữu ích khi bạn không có mặt để review từng PR nhỏ hàng ngày — điều rất phổ biến với QA làm việc across nhiều team hoặc nhiều dự án song song.

Chuyển Đổi Phân Tích Diff Thành Phạm Vi Session

Có phân tích rủi ro rồi, bước cuối cùng là biến nó thành một session brief (bản mô tả phạm vi session) cụ thể, có thể giao cho bản thân hoặc đồng nghiệp thực hiện trong một khoảng thời gian xác định (ví dụ 90 phút). Tôi thường yêu cầu AI tự soạn luôn bản brief này từ output phân tích ở trên:

Based on the risk analysis above, draft a 90-minute exploratory testing
session charter with this structure:
- Session goal (1 sentence)
- In-scope areas (max 3, ranked by risk)
- Out-of-scope (explicitly state what we're deliberately NOT covering
  this session, and why — so it's a conscious decision, not an omission)
- Suggested testing techniques per area (e.g. boundary values, state
  transition, concurrency)
- Time budget per area

Việc này giúp session có mục tiêu rõ ràng ngay từ đầu, thay vì "khám phá tự do" mông lung — vốn là điểm yếu lớn nhất khiến exploratory testing bị stakeholder đánh giá là "không đo lường được".

AI Nhận Diện Khu Vực Thay Đổi Có Rủi Ro Cao Cần Khám Phá Trước Như Thế Nào?

Các Yếu Tố Rủi Ro AI Có Thể Đánh Giá

Khi AI phân tích một thay đổi, nó không chỉ nhìn vào "số dòng code thay đổi" — đó là chỉ số nông và dễ gây hiểu nhầm (một dòng sửa điều kiện if có thể nguy hiểm hơn 200 dòng thêm một trường log mới). Thay vào đó, cần hướng dẫn AI đánh giá theo nhiều chiều rủi ro khác nhau, tương tự cách một QA senior lâu năm "cảm" được rủi ro:

Yếu tố rủi ro Vì sao quan trọng Ví dụ
Blast radius (bán kính ảnh hưởng) Thay đổi ở shared code/utility ảnh hưởng nhiều module cùng lúc Sửa hàm formatCurrency() dùng ở 15 nơi
Độ phức tạp logic mới Logic rẽ nhánh, tính toán, xử lý concurrency dễ sinh edge case Thêm state machine mới cho quy trình duyệt đơn
Lịch sử bug của khu vực Khu vực từng có nhiều bug thường tiếp tục là điểm yếu Module thanh toán từng có 3 incident trong 6 tháng
Độ phủ test hiện có Thay đổi không kèm test mới là rủi ro; xóa/sửa test cũ càng đáng ngờ PR sửa logic nhưng không chạm file test nào
Loại dữ liệu bị ảnh hưởng Tiền, dữ liệu cá nhân (PII), quyền truy cập luôn nhạy cảm hơn UI thuần Thay đổi liên quan đến số dư, quyền admin
Tính năng mới vs. refactor Tính năng hoàn toàn mới thiếu "lịch sử ổn định" để tham chiếu Feature vừa launch tuần trước, chưa qua real traffic
Số lượng người review PR chỉ có 1 approval nhanh trong 10 phút thường ít được soi kỹ hơn So với PR có thảo luận dài, nhiều round review

Mẹo: Đừng để AI tự bịa ra "lịch sử bug của khu vực" — nếu team có bug tracker (Jira, Linear), hãy trích xuất thật danh sách bug đã đóng liên quan đến module đó trong 3-6 tháng gần nhất và đưa vào prompt như dữ liệu thật. AI suy luận từ dữ liệu thật luôn đáng tin hơn AI "đoán" dựa trên tên module.

Risk Scoring Prompt

Khi cần một con số cụ thể để so sánh và xếp hạng nhiều khu vực thay đổi cùng lúc (ví dụ đầu sprint có 8 PR cần merge), risk scoring prompt (prompt tính điểm rủi ro) giúp chuẩn hóa việc đánh giá, tránh cảm tính:

You are scoring risk for exploratory testing prioritization. Score each
change area on a scale of 1-10 (10 = highest risk) using this rubric:

SCORING FACTORS (weight each, then combine into a single score):
- Blast radius: does this touch shared/utility code? (1-10)
- Logic complexity: new branching, state, concurrency, calculations? (1-10)
- Historical fragility: has this file/module had bugs before? (1-10)
- Test coverage gap: were tests added/updated for this change? (1-10,
  10 = no tests at all for a behavioral change)
- Data sensitivity: does this touch money, auth, or PII? (1-10)

For each area, show your factor scores AND a final weighted score
(weight data sensitivity and blast radius at 1.5x, others at 1x).
Then rank all areas from highest to lowest risk.

CHANGE AREAS THIS SPRINT:
1. refundService.js + currency.js (partial refund feature) — see diff A
2. UserProfileCard.tsx (avatar upload UI redesign) — see diff B
3. authMiddleware.js (added rate-limit check) — see diff C
4. reportExportJob.js (new CSV column for internal report) — see diff D

<paste diffs A-D>

Output điển hình sẽ trông như sau:

| Area                        | Blast radius | Logic complexity | Fragility | Test gap | Data sensitivity | Weighted score |
|------------------------------|:---:|:---:|:---:|:---:|:---:|:---:|
| refundService.js/currency.js | 8   | 8   | 9   | 7   | 10  | 8.9 |
| authMiddleware.js (rate-limit)| 6   | 5   | 4   | 6   | 8   | 6.4 |
| UserProfileCard.tsx           | 2   | 3   | 2   | 4   | 1   | 2.4 |
| reportExportJob.js            | 2   | 2   | 1   | 3   | 3   | 2.2 |

RANKING: refundService/currency (8.9) > authMiddleware (6.4) >
UserProfileCard (2.4) > reportExportJob (2.2)

RATIONALE: Refund logic combines the highest data sensitivity (real
money, over-refund potential) with genuinely new, untested branching
logic and a documented incident history — this deserves the majority
of exploratory time. Auth rate-limiting is second because a
mis-configured limit can lock out legitimate users at scale even
though the code itself is simple. UI and reporting changes are lower
risk: cosmetic/UI change and an additive, read-only export field.

Con số 8.9 hay 6.4 ở đây không phải "khoa học chính xác" — nó là công cụ hỗ trợ ra quyết định, không phải chân lý tuyệt đối. Giá trị thật nằm ở việc AI buộc phải giải trình (rationale) tại sao lại xếp hạng như vậy, giúp bạn phản biện và điều chỉnh nếu thấy sai.

Ưu Tiên Khi Thời Gian Có Hạn

Thực tế phổ trong các team QA là: bạn không bao giờ có đủ thời gian để explore mọi thứ. Với risk score trong tay, quy tắc ưu tiên tôi áp dụng là "70-20-10": dành 70% thời gian cho khu vực risk cao nhất (điểm 7+), 20% cho khu vực risk trung bình (4-7), và chỉ 10% dạng "smoke check" nhanh cho khu vực risk thấp — không bỏ qua hoàn toàn, vì đôi khi chính khu vực "chắc chắn an toàn" lại ẩn chứa bất ngờ, nhưng không đầu tư sâu vào đó.

Một sai lầm phổ biến là dùng risk score để loại bỏ hoàn toàn khu vực điểm thấp khỏi kế hoạch test. Đừng làm vậy — hãy giữ lại ít nhất một lượt kiểm tra nhanh (5-10 phút) cho mọi khu vực có thay đổi, vì risk score dựa trên phân tích tĩnh của diff, nó không thể biết trước những tương tác bất ngờ giữa các module mà chỉ lộ ra khi chạy thực tế.

Mẹo: Ghi lại risk score thực tế và so sánh với bug thực tế tìm được sau mỗi sprint. Nếu khu vực điểm thấp liên tục phát sinh bug thật, đó là dấu hiệu prompt rubric của bạn thiếu một yếu tố rủi ro quan trọng — hãy tinh chỉnh lại rubric, đừng chỉ đổ lỗi cho AI đoán sai.

Kết Hợp Scripted Testing Và Exploratory Testing Dựa Trên Tín Hiệu Rủi Ro Từ AI Như Thế Nào?

Vai Trò Bổ Trợ Của Từng Phương Pháp

Một hiểu lầm phổ biến là coi exploratory testing và scripted testing (kiểm thử theo script, gồm cả automation) là hai lựa chọn đối lập — chọn một thì bỏ cái kia. Thực tế chúng bổ trợ cho nhau theo hai chiều rất tự nhiên khi nhìn qua lăng kính rủi ro:

  • Scripted/automated testing phù hợp cho khu vực rủi ro đã biết, lặp lại: luồng chính (happy path), quy tắc nghiệp vụ ổn định, regression test cho bug đã từng xảy ra. Đây là nơi automation trả giá trị cao nhất vì chạy lại nhiều lần với chi phí gần như 0.
  • Exploratory testing phù hợp cho khu vực rủi ro mới, chưa biết: code vừa thay đổi, tương tác phức tạp giữa nhiều thành phần, những gì "chưa ai từng nghĩ tới để viết test case".

Risk score mà AI tạo ra ở phần trước chính là cầu nối để quyết định khu vực nào rơi vào nhóm nào. Khu vực điểm rủi ro cao NHƯNG đã có test tự động chạy pass — vẫn nên explore, vì automation chỉ test được cái đã được viết ra, không test được cái chưa ai nghĩ tới. Khu vực điểm rủi ro thấp và ổn định lâu dài — nên để automation lo, con người không cần ngồi test lại tay mỗi sprint.

Workflow Ở Cấp Độ Sprint

Một workflow thực tế mà tôi áp dụng cho team 4-5 QA, chạy sprint 2 tuần:

  1. Đầu sprint (ngày 1-2): Chạy risk scoring prompt cho toàn bộ PR/diff dự kiến trong sprint (dựa trên backlog đã refine). Kết quả là danh sách xếp hạng rủi ro.
  2. Phân bổ (ngày 2): Khu vực risk 7+ → lên kế hoạch exploratory session (90-120 phút, có charter rõ ràng). Khu vực risk 4-7 → viết thêm test case bổ sung (có thể tự động hóa sau) + explore nhanh 30 phút. Khu vực risk dưới 4 → dựa vào regression suite hiện có, không cần thêm effort.
  3. Trong sprint (ngày 3-8): Khi code thực sự được merge, chạy lại risk scoring cho diff thật (không phải ước tính từ backlog) — vì code thực tế luôn khác kế hoạch ban đầu ít nhiều.
  4. Cuối sprint (ngày 9-10): Review lại: session exploratory nào tìm ra bug, session nào "sạch". Feed thông tin này ngược vào phần lịch sử fragility cho lần đánh giá risk tiếp theo.

Mẹo: Đưa risk score vào ngay trong PR description hoặc comment tự động (qua CI bot) thay vì để riêng trong một tool khác — QA sẽ thấy risk score ngay khi PR sẵn sàng để review, không phải đi tìm ở nơi khác rồi quên mất.

Quyết Định Cái Gì Không Nên Tự Động Hóa

Ngược lại với câu hỏi "test gì trước", có một câu hỏi ít được hỏi hơn nhưng quan trọng không kém: "cái gì KHÔNG nên viết thành automated test?". Dùng chính risk signal để trả lời câu này giúp tránh lãng phí effort automation vào chỗ không đáng.

Những dấu hiệu cho thấy một khu vực KHÔNG nên (hoặc chưa nên) tự động hóa:
- Logic nghiệp vụ còn đang thay đổi liên tục (feature mới ra mắt, còn nhiều thay đổi dựa trên feedback) — viết test tự động lúc này sẽ phải sửa lại liên tục, tốn công hơn giá trị mang lại.
- Rủi ro chủ yếu nằm ở trải nghiệm chủ quan (UI/UX, cảm nhận hiệu năng) — máy khó đánh giá "cảm giác mượt" hay "màu sắc có hợp lý không".
- Tương tác one-off, hiếm khi lặp lại (ví dụ migration dữ liệu chạy một lần).

Given the risk analysis above for this sprint's changes, recommend for
EACH area: "automate now", "explore only for now", or "automate later
once stable". Justify based on: change volatility (how often does this
logic change?), whether the risk is objective/measurable or subjective/
experiential, and repeat frequency of this scenario in real usage.

Kích Hoạt Session Khám Phá Từ Automated Test Failure

Một tín hiệu rủi ro cực kỳ giá trị nhưng thường bị bỏ qua: khi một automated test bất ngờ fail ở khu vực tưởng chừng ổn định lâu nay, đó chính là lời cảnh báo rằng có gì đó vừa thay đổi ngầm — và đáng để mở một session exploratory ngay, thay vì chỉ fix test rồi cho qua.

Quy trình đề xuất: khi CI báo fail, đừng chỉ hỏi "làm sao fix test cho pass lại". Hỏi AI:

This automated test has been stable for 8 months and just started
failing after this diff (below). Before I decide whether this is a
test bug or a real regression:

1. Explain what changed that could plausibly cause this specific
   assertion to fail now.
2. Does this failure suggest a narrow, isolated issue, or a change
   in behavior that likely affects OTHER untested scenarios nearby?
3. If it's the latter, suggest 3 manual exploratory checks I should
   run around this area before just patching the test expectation.

FAILING TEST: <paste test>
DIFF: <paste diff>
FAILURE OUTPUT: <paste error>

Cách làm này biến mỗi lần automated test fail thành một cơ hội để phát hiện rủi ro mới thay vì chỉ "sửa cho xanh lại" — điều mà rất nhiều team vô tình làm khi áp lực deadline, và chính là nguồn gốc của không ít bug production nghiêm trọng.

Làm Sao Theo Dõi Những Khu Vực Đã Được Khám Phá Theo Thời Gian?

Cấu Trúc Coverage Map

Nếu risk scoring giúp trả lời "test cái gì hôm nay", thì coverage map (bản đồ độ phủ) giúp trả lời câu hỏi lớn hơn: "toàn hệ thống, khu vực nào lâu rồi chưa ai chạm tới, khu vực nào đang được test kỹ". Không có coverage map, các session exploratory dễ bị lặp lại vào những khu vực "vui", "dễ test", "hay được nhắc tới" — trong khi những góc ít ai để ý dần trở thành điểm mù (blind spot).

Một coverage map hiệu quả không cần phức tạp — một bảng đơn giản, cập nhật thường xuyên, đã đủ giá trị:

Khu vực chức năng Lần khám phá gần nhất Risk score gần nhất Số bug tìm được (3 lần gần nhất) Trạng thái
Thanh toán / Refund 2026-07-08 8.9 2 Đang theo dõi sát
Đăng ký / Đăng nhập 2026-06-15 4.2 0 Ổn định
Xuất báo cáo (export) 2026-05-02 2.2 1 Cần review lại (>60 ngày)
Quản lý người dùng (admin) 2026-03-20 6.0 0 Quá hạn (>90 ngày) — rủi ro tăng dần
Upload avatar 2025-11-10 3.1 1 Chưa từng khám phá lại sau launch

Bảng này thực chất là một dạng risk register (sổ theo dõi rủi ro) áp dụng riêng cho exploratory testing coverage — nó biến "cảm giác lâu rồi không test khu vực này" thành dữ liệu cụ thể, có ngày tháng, có thể báo cáo.

Tạo Và Cập Nhật Coverage Map Với AI

Duy trì bảng này bằng tay rất dễ bị bỏ quên. Cách thực tế hơn: để AI tự tổng hợp từ nhiều nguồn dữ liệu đã có sẵn — log session testing (ghi trong Notion/Confluence/Jira), bug tracker, và git history.

I'm maintaining a coverage map for exploratory testing across our
application's functional areas. Below is:
(1) A log of exploratory sessions from the last 90 days (area, date,
    bugs found)
(2) A list of functional areas in our app (from our feature map)
(3) Current risk scores from this week's diff analysis

Generate/update the coverage map table with columns: Functional Area |
Last Explored Date | Days Since Last Explored | Latest Risk Score |
Bugs Found (last 3 sessions) | Status.

Status rules:
- "Overdue" if days since last explored > 90 AND risk score >= 5
- "Needs review" if days since last explored > 60
- "Stable" otherwise
- "Never explored" if no entry exists in the session log at all

Flag any functional area from list (2) that has NO entry in the
session log — these are blind spots.

SESSION LOG: <paste>
FUNCTIONAL AREAS: <paste>
RISK SCORES: <paste>

AI sẽ trả về bảng đã cập nhật kèm danh sách "blind spot" — những khu vực tồn tại trong feature map nhưng chưa từng xuất hiện trong log session nào. Đây thường là phát hiện gây bất ngờ nhất khi lần đầu chạy — nhiều team ngỡ ngàng khi thấy có module đã chạy production hơn năm mà chưa từng được explore một cách có chủ đích.

Mẹo: Lưu session log dưới dạng có cấu trúc ngay từ đầu (area, date, risk score tại thời điểm đó, bug found, người thực hiện) — dù chỉ là một file Markdown hay Google Sheet đơn giản. Nếu log chỉ là văn bản tự do rời rạc, AI sẽ khó tổng hợp chính xác và bạn sẽ phải làm sạch dữ liệu thủ công trước khi dùng được.

Dùng "Tuổi" Của Coverage Làm Tín Hiệu Rủi Ro

Một nguyên lý ít người để ý: bản thân việc "lâu chưa được test" là một loại rủi ro độc lập, tách biệt với rủi ro từ thay đổi code. Một module không có thay đổi code nào trong 6 tháng vẫn có thể trở nên rủi ro hơn theo thời gian, vì:

  • Môi trường xung quanh nó đã thay đổi (dependency nâng cấp, hạ tầng thay đổi, traffic pattern khác đi) dù code module đó vẫn y nguyên.
  • Người hiểu rõ logic module đó ban đầu có thể đã nghỉ việc hoặc chuyển team — kiến thức ngầm (tacit knowledge) về các edge case của nó dần mất đi.
  • Giả định nghiệp vụ ban đầu (ví dụ "khách hàng luôn thanh toán bằng 1 phương thức") có thể không còn đúng khi sản phẩm mở rộng, nhưng code chưa từng được nhìn lại dưới góc nhìn mới.

Vì vậy, công thức risk score tổng hợp nên cộng thêm một hệ số "coverage age" (tuổi độ phủ):

Recommend an adjusted priority score combining:
- Base risk score (from diff/change analysis, 0 if no recent change)
- Coverage age factor: add +1 point for every 30 days since last
  exploratory session, capped at +5
- Multiply the result by a criticality weight from our functional
  area list (payment=1.5x, admin=1.3x, reporting=1.0x, cosmetic=0.7x)

Given the coverage map above, output a ranked "explore next" list for
areas that have NOT changed recently but are aging out of coverage,
separate from areas with fresh code changes — these need different
justifications when I report to stakeholders.

Việc tách riêng hai danh sách — "khám phá vì vừa thay đổi" và "khám phá vì lâu rồi chưa ai chạm" — rất quan trọng khi giải trình với stakeholder, vì lý do biện minh cho hai loại này hoàn toàn khác nhau.

Truyền Đạt Trạng Thái Coverage Cho Stakeholder

Coverage map không chỉ là công cụ nội bộ của QA — nó là bằng chứng cụ thể để trao đổi với Product Manager, Engineering Manager về việc "chúng ta đang thực sự bao phủ được gì, và đang chấp nhận rủi ro ở đâu một cách có ý thức". Đây là điểm khác biệt lớn giữa QA bị xem là "cái máy chạy test" và QA được xem là đối tác chiến lược trong quản lý rủi ro sản phẩm.

Khi báo cáo, tránh liệt kê bảng thô đầy số liệu — thay vào đó, tóm tắt theo hướng quyết định:

Summarize the coverage map above into a stakeholder-facing risk report,
written for a Product Manager who has 2 minutes to read it:
- 1-sentence overall coverage health statement
- Top 3 areas of concern with concrete business impact framing (not
  technical jargon)
- What we are consciously choosing NOT to cover this sprint, and why
  that's an acceptable trade-off
- 1 concrete ask if we need more time/resources for a specific area

Cách trình bày "chúng ta đang chọn KHÔNG cover khu vực X vì lý do Y" đặc biệt quan trọng — nó biến QA từ vị trí bị động ("chưa test kịp") thành vị trí chủ động ra quyết định rủi ro cùng với business, được ghi nhận rõ ràng nếu về sau có vấn đề xảy ra.

Mẹo: Đưa coverage map (hoặc bản tóm tắt của nó) vào demo cuối sprint hoặc báo cáo release, dù chỉ 1 slide. Việc này tạo thói quen cho cả team nhìn testing như một hoạt động quản lý rủi ro liên tục, không phải một checkbox "đã test xong" nhị phân.

Tích Hợp Coverage Tracking Vào Sprint Planning

Bước cuối cùng để coverage map thực sự có giá trị lâu dài là đưa nó vào ngay trong buổi sprint planning, không để nó là một tài liệu "làm cho có" rồi bị lãng quên. Cách thực hiện cụ thể:

  1. Trước buổi planning: Chạy prompt cập nhật coverage map + risk score cho các PR/feature dự kiến của sprint tới.
  2. Trong buổi planning: Dành 5-10 phút cuối để review danh sách "explore next" (cả hai loại: theo thay đổi mới và theo tuổi coverage). Team cùng quyết định: sprint này ưu tiên khu vực nào, chấp nhận bỏ qua khu vực nào.
  3. Ghi lại quyết định: Lưu quyết định này (kể cả lý do bỏ qua) vào cùng file coverage map — để 2-3 sprint sau nhìn lại vẫn hiểu vì sao lúc đó chọn vậy, tránh lặp lại tranh luận cũ.
  4. Cuối sprint: Cập nhật lại coverage map với session thực tế đã chạy, bug tìm được — khép vòng feedback để risk score của sprint sau chính xác hơn.

Vòng lặp này, khi được duy trì đều đặn qua nhiều sprint, chính là thứ biến exploratory testing từ một hoạt động "ngẫu hứng dựa trên cảm tính" thành một hệ thống quản lý rủi ro có dữ liệu, có thể đo lường, và có thể cải thiện liên tục — đúng với tinh thần mà một QA senior cần mang lại cho tổ chức trong thời đại agentic testing.