Buổi 02: Prompting Cho QA — Nói Chuyện Với AI Như Senior Dev
Mục tiêu buổi học: Học cách viết prompt hiệu quả cho AI, áp dụng RARV Loop, và thực hành viết unit test + API test cho TaskFlow app bằng cách prompt AI.
📚 Phần 1: Lý Thuyết (35 phút)
1.1 Tại Sao Prompting Là Skill Quan Trọng
Trong AI-Native QA, prompt quality = test quality = code quality.
Nếu prompt bạn gửi cho AI:
- ❌ Mơ hồ → AI generate test không cover edge case
- ❌ Thiếu context → AI generate code không match requirement
- ❌ Không có output format → AI trả lời random format
Nhưng nếu prompt bạn:
- ✅ Rõ ràng, cụ thể → AI generate test coverage 95%+
- ✅ Có context đầy đủ → Code match requirement 100%
- ✅ Có output format → Output dùng ngay được
Thực tế: QA engineers giỏi prompting tiết kiệm 40% thời gian so với người không biết prompting.
1.2 RARV Loop — Vòng Lặp Lý Tưởng
RARV = Reason → Act → Reflect → Verify
Reason (Suy nghĩ):
- Trước khi prompt AI, hãy suy nghĩ: "Mình cần gì từ AI?"
- Cái gì đã có? Cái gì còn thiếu?
- Output nên như thế nào?
Act (Hành động):
- Viết prompt chi tiết (không chỉ 1-2 dòng)
- Gửi đến AI
- Nhận output
Reflect (Suy ngẫm):
- Đọc output từ AI
- So sánh với expectation
- Có gì chưa đúng? Gì không match requirement?
Verify (Xác nhận):
- Test output bằng cách chạy/kiểm tra
- Nếu đúng → sử dụng, deploy
- Nếu sai → Back to "Reason", refine prompt lần 2
┌─────────────────────────────────────┐
│ 1. REASON: Cần gì? │
│ 2. ACT: Prompt AI │
│ 3. REFLECT: Output có đúng không? │
│ 4. VERIFY: Test output │
└─────────────────────────────────────┘
↓
Sai? ↓ Đúng?
↓ ✓
Refine Prompt
↓
(Loop lại bước 1)1.3 5 Prompt Templates Cho QA
Dưới đây là 5 template prompt mà QA engineer thường dùng. Mỗi template có structure cụ thể.
Template 1: Viết Unit Test (Test-First)
Purpose: Viết test TRƯỚC code, để test FAIL trước (theo TDD Iron Law)
Structure:
[Context]
Tôi đang làm QA cho feature "Tạo Task Mới" trong TaskFlow app.
[Requirement]
- User nhập title, description
- App tạo task mới và lưu vào database
- Trả về task object với id, created_at, updated_at
[Edge Cases Cần Test]
1. Title là empty string → lỗi "Title is required"
2. Title là null → lỗi "Title is required"
3. Title > 255 characters → lỗi "Title too long"
4. Description là null/empty → OK (optional field)
5. Valid title + description → task được tạo thành công
[Output Format]
Viết unit test dùng Vitest framework. Test phải FAIL trước khi có implementation.
```javascript
import { describe, it, expect } from 'vitest';
import { createTask } from './taskModel.js';
describe('createTask()', () => {
// Test cases ở đây
});[Requirements Khác]
- Mỗi test tên rõ ràng, mô tả chính xác behavior
- Không mock database (sử dụng in-memory hoặc real database)
- Expect clear error messages
**Ví dụ Prompt Thực Tế:**Tôi là QA engineer cho TaskFlow app. Tôi cần viết unit test cho function createTask(title, description).
Requirement:
- createTask nhận 2 parameter: title (string) và description (optional string)
- Nếu title empty hoặc null → throw error 'Title is required'
- Nếu title > 255 chars → throw error 'Title too long'
- Nếu valid → return object
Viết unit test dùng Vitest. Test phải FAIL ngay (vì code chưa có implementation).
Test theo Arrange-Act-Assert pattern. Tên test phải rõ ràng.
Output format: code block javascript dùng ngay được.
#### Template 2: Tìm Edge Case (Brainstorming)
**Purpose:** AI giúp phát hiện edge case mà con người dễ bỏ sót
**Structure:**[Feature] Feature: Tạo Task Mới Input: title, description Output: task object (id, title, description, status='pending', created_at, updated_at)
[Current Test Cases]
- Valid title + description
- Valid title, empty description
[Ask AI] Tìm thêm 10 edge case khác. Cho mỗi case, viết:
- Input
- Expected Output
- Tại sao nó quan trọng
[Output Format]
| # | Input | Expected Output | Why Important |
|---|---|---|---|
| 1 | ... | ... | ... |
**Ví dụ Prompt Thực Tế:**Feature: createTask(title, description) trong TaskFlow
Current test cases:
- Valid title, valid description
- Valid title, empty description
Tìm 10 edge case khác. Mỗi case format:
- Input: [specific value]
- Expected: [behavior]
- Why: [1-2 dòng]
Ưu tiên: security, data validation, edge case dễ bỏ sót.
#### Template 3: Viết API Test (Integration Test)
**Purpose:** Test API endpoint, không chỉ function
**Structure:**[API Endpoint] POST /api/tasks Body: { title: string, description?: string } Response:
[Test Cases Cần]
- Happy path: valid title + description → 201, task object
- Missing title → 400, error "Title is required"
- Title too long → 400, error "Title too long"
- Description is null → 201, description stored as empty string
[Output Format] Dùng Jest/Vitest, dùng supertest library để test HTTP request.
import request from 'supertest';
import app from '../src/app.js';
describe('POST /api/tasks', () => {
// Test cases ở đây
});[Note]
- Before each test: clear database
- After each test: close database connection
**Ví dụ Prompt Thực Tế:**API endpoint: POST /api/tasks Body: { title, description } Response: { success: true, task } hoặc
Test cases:
- Valid input → status 201, task object returned
- Empty title → status 400, error "Title is required"
- Title > 255 → status 400, error "Title too long"
- No title field → status 400, error "Title is required"
- Database connection error → status 500, error message
Viết test dùng Vitest + supertest. Mỗi test:
- Setup: clear DB
- Act: HTTP request
- Assert: status + response body
#### Template 4: Tạo Bug Report (From Test Failure)
**Purpose:** Khi test FAIL, AI generate bug report tự động
**Structure:**[Test Failure Info] Test Name: rejects title > 255 chars Expected: throw error 'Title too long' Actual: no error thrown, task created anyway Error: none
[Code Being Tested]
function createTask(title, description) {
return { id: '123', title, description, created_at: new Date() };
}[Generate Bug Report With]
- Title: [brief, actionable]
- Severity: [Critical/High/Medium/Low]
- Steps to Reproduce
- Expected Result
- Actual Result
- Root Cause Analysis
- Suggested Fix
[Format] Markdown format, dùng ngay để submit lên Jira
**Ví dụ Prompt Thực Tế:**Test failure: Test: 'rejects title > 255 chars' Expected: error 'Title too long' Actual: task created with long title (500+ chars)
Function:
function createTask(title, description) {
return { id: random(), title, description, created_at: new Date() };
}Create bug report:
- Title (brief, specific)
- Severity (Critical/High/Medium)
- Reproduce steps
- Expected vs Actual
- Impact analysis
- Suggested fix code
Format: markdown, production-ready.
#### Template 5: Review Code (Quality Gate)
**Purpose:** AI review code trước deploy
**Structure:**[Code to Review]
function createTask(title, description) {
if (!title || title.length === 0) throw new Error('Title is required');
if (title.length > 255) throw new Error('Title too long');
return {
id: Math.random().toString(36).substr(2, 9),
title,
description,
created_at: new Date()
};
}[Review Against]
- CodyMaster 7-Point Checklist (clean code)
- SOLID principles
- Security best practices
- Performance
- Testability
[Output Format]
## ✅ PASS / ❌ FAIL
### 1. Code Clarity [✅/❌]
- Comment: [analysis]
### 2. Error Handling [✅/❌]
- Comment: [analysis]
### 3. Security [✅/❌]
- Comment: [analysis]
... (7 points total)
### Issues Found
1. [Issue]
2. [Issue]
### Suggested Improvements
```javascript
// Better code
**Ví dụ Prompt Thực Tế:**Review this createTask() function:
function createTask(title, description) {
if (!title || title.length === 0) throw new Error('Title is required');
if (title.length > 255) throw new Error('Title too long');
return {
id: Math.random().toString(36).substr(2, 9),
title,
description,
created_at: new Date()
};
}Check against:
- Code clarity & readability
- Error handling
- Input validation
- Performance (e.g., random ID generation)
- Testability
- Security
- Edge cases
Output:
- Pass/Fail for each point
- Issues found
- Improved code if needed
### 1.4 Prompt Anti-Patterns (Avoid These)
❌ **Bad Prompt 1: Too Vague**"Write a test for createTask"
→ AI không biết test cái gì, output sẽ generic, không useful
✅ **Better:**"Write unit test cho createTask(title, description) function. Test edge case: empty title, null title, title > 255 chars. Format: Vitest, Arrange-Act-Assert pattern."
---
❌ **Bad Prompt 2: No Context**"Generate API test"
→ AI không biết API là gì, body format, response format
✅ **Better:**"API endpoint: POST /api/tasks Body: { title: string, description?: string } Response: { success: bool, task: object } or
Test 3 cases: valid input, missing title, title too long. Use Vitest + supertest."
---
❌ **Bad Prompt 3: No Output Format**"Create bug report for this test failure"
→ Output format không consistent, hard to parse
✅ **Better:**"Create bug report, markdown format:
- Title
- Severity
- Steps
- Expected
- Actual
- Root Cause
- Suggested Fix"
---
## 🛠️ Phần 2: Thực Hành (50 phút)
### 2.1 Bài Tập 1: Prompt AI để Tìm Edge Case (15 phút)
**Scenario:** Bạn làm QA cho function createTask(). Dùng RARV loop để tìm edge case.
**Bước 1: REASON (2 phút)**
- Cần: Danh sách 10 edge case cho createTask()
- Context: Function nhận title (string) + description (optional string)
- Output format: Table gồm Input, Expected, Why
**Bước 2: ACT (3 phút)**
Gửi prompt này cho AI (ChatGPT, Claude, hoặc tool khác):Feature: createTask(title, description) trong TaskFlow QA app
Current implementation:
- Input: title (required), description (optional)
- Output:
- Validation: title not empty, title <= 255 chars
Current test cases (happy path):
- createTask('Buy milk', 'Go to store')
- createTask('Call mom', '')
Task: Tìm 10 edge case khác. Mỗi case gồm:
- Input value (cụ thể)
- Expected behavior
- Why it's important (1 dòng)
Output format: Markdown table, có thể copy-paste ngay.
| # | Input | Expected | Why |
|---|---|---|---|
| 1 | ... | ... | ... |
Focus: data validation, security, performance, database constraints.
**Bước 3: REFLECT (5 phút)**
AI sẽ trả về danh sách như:
| # | Input | Expected | Why |
|---|---|---|---|
| 1 | title = " " (spaces only) | Reject, "Title is required" | Trimming check |
| 2 | title = null | Reject, "Title is required" | Null check |
| 3 | title = undefined | Reject, "Title is required" | Undefined check |
| 4 | title = 256 chars | Reject, "Title too long" | Boundary test |
| 5 | title with SQL injection: `"; DROP TABLE tasks;--` | Sanitized, no SQL executed | Security |
| 6 | description = very long (1MB text) | Stored as-is (or truncated) | Performance |
| 7 | Concurrent calls same title | Unique ID for each | Race condition |
| 8 | title with emoji: "📋 Task" | Accepted (UTF-8) | Character encoding |
| 9 | title = "" (empty string) | Reject | Edge case |
| 10 | Database down during insert | Throw error, don't silently fail | Error handling |
**Bước 4: VERIFY (5 phút)**
Chọn 3 edge case và kiểm tra code current có handle không:
```javascript
// Test: spaces only
createTask(' ', 'desc')
// Current code:
// if (!title || title.length === 0) → PASS (title.length = 3, không fail!)
// ❌ BUG: Không trim trước check
// Test: SQL injection
createTask('"; DROP TABLE tasks;--', 'desc')
// Không sanitize input
// ❌ BUG: Vulnerable to SQL injection
// Test: Concurrent calls
Promise.all([
createTask('Same title', 'desc1'),
createTask('Same title', 'desc2')
])
// Should create 2 different tasks dengan 2 different IDs
// ✅ OK (ID random, unlikely collision)2.2 Bài Tập 2: Prompt AI để Viết Unit Test (TDD) (20 phút)
Scenario: Viết unit test cho createTask(), test phải FAIL trước (TDD).
Bước 1: REASON (2 phút)
Cần: Unit test cho createTask(), test phải FAIL ngay (vì code chưa có validation)
Bước 2: ACT (8 phút)
Prompt AI:
I'm a QA engineer practicing TDD for createTask() function.
Current state:
- Function signature: createTask(title, description)
- Database: SQLite, table "tasks"
- Framework: Vitest
- NO implementation yet (just empty stub)
Requirements:
1. Validate title: not empty, not null, <= 255 chars
2. Description optional (default to '')
3. Return: { id, title, description, status: 'pending', created_at, updated_at }
4. Throw error for invalid input
Write unit tests using Vitest. IMPORTANT: Tests must FAIL right now (because implementation doesn't validate yet).
Tests to write:
- rejects empty title
- rejects null title
- rejects title > 255 chars
- accepts valid title + description
- accepts valid title without description
Format: Complete Vitest code block, ready to run.
```javascript
import { describe, it, expect } from 'vitest';
import { createTask } from './taskModel.js';
describe('createTask()', () => {
// Your tests here
});
**Bước 3: REFLECT (5 phút)**
AI trả về test code như:
```javascript
import { describe, it, expect } from 'vitest';
import { createTask } from './taskModel.js';
describe('createTask()', () => {
it('should reject empty title', () => {
expect(() => createTask('', 'description')).toThrow('Title is required');
});
it('should reject null title', () => {
expect(() => createTask(null, 'description')).toThrow('Title is required');
});
it('should reject title > 255 chars', () => {
const longTitle = 'a'.repeat(256);
expect(() => createTask(longTitle, 'desc')).toThrow('Title too long');
});
it('should accept valid title and description', () => {
const task = createTask('Buy milk', 'Go to store');
expect(task.id).toBeDefined();
expect(task.title).toBe('Buy milk');
expect(task.description).toBe('Go to store');
expect(task.status).toBe('pending');
expect(task.created_at).toBeInstanceOf(Date);
});
it('should accept title without description', () => {
const task = createTask('Call mom');
expect(task.description).toBe('');
});
});Bước 4: VERIFY (5 phút)
Chạy test để verify nó FAIL:
# Tạo stub function
echo "export function createTask(title, description) {
return { id: '123', title, description, status: 'pending', created_at: new Date() };
}" > src/taskModel.js
# Chạy test
npm test
# Output sẽ FAIL:
# ✗ should reject empty title
# Expected to throw [Title is required]
# but function returned: { id: '123', title: '', ... }
#
# ✗ should reject null title
# ...
#
# ✓ should accept valid title (this passes because stub just returns object)
# Điều này ĐÚNG! Test FAIL = chúng ta sẵn sàng implement.2.3 Bài Tập 3: Prompt AI để Viết API Test (15 phút)
Scenario: Viết API test cho POST /api/tasks endpoint.
Bước 1: REASON (2 phút)
Cần: API test, cover 5 scenarios: valid input, missing title, title too long, DB error, ...
Bước 2: ACT (6 phút)
Prompt AI:
I need to write API test for POST /api/tasks endpoint.
API Details:
- Endpoint: POST /api/tasks
- Body: { title: string, description?: string }
- Response on success: { success: true, task: { id, title, description, status, created_at, updated_at } }
- Response on error: { success: false, error: "error message" }
- HTTP Status: 201 (created) or 400 (validation error) or 500 (server error)
Backend: Express.js, SQLite database
Test cases needed:
1. Valid title + description → 201, task object
2. Valid title, no description → 201, description empty
3. Missing title field → 400, "Title is required"
4. Empty title → 400, "Title is required"
5. Title > 255 chars → 400, "Title too long"
Write test using Vitest + supertest library.
Format: Complete code block, ready to npm test.
```javascript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import request from 'supertest';
import app from '../src/app.js';
import Database from 'better-sqlite3';
describe('POST /api/tasks', () => {
let db;
beforeEach(() => {
// Setup: create fresh database
});
afterEach(() => {
// Cleanup: close database
});
// Your tests here
});
**Bước 3: REFLECT (4 phút)**
AI trả về test code:
```javascript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import request from 'supertest';
import app from '../src/app.js';
import Database from 'better-sqlite3';
import path from 'path';
describe('POST /api/tasks', () => {
let db;
beforeEach(() => {
// Create in-memory database for testing
db = new Database(':memory:');
db.exec(`
CREATE TABLE tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
});
afterEach(() => {
db.close();
});
it('should create task with valid title and description', async () => {
const response = await request(app)
.post('/api/tasks')
.send({ title: 'Buy milk', description: 'Go to store' })
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.task.id).toBeDefined();
expect(response.body.task.title).toBe('Buy milk');
expect(response.body.task.description).toBe('Go to store');
expect(response.body.task.status).toBe('pending');
});
it('should create task with title only', async () => {
const response = await request(app)
.post('/api/tasks')
.send({ title: 'Call mom' })
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.task.description).toBe('');
});
it('should reject missing title', async () => {
const response = await request(app)
.post('/api/tasks')
.send({ description: 'No title' })
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('Title is required');
});
it('should reject empty title', async () => {
const response = await request(app)
.post('/api/tasks')
.send({ title: '', description: 'Empty title' })
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('Title is required');
});
it('should reject title > 255 chars', async () => {
const longTitle = 'a'.repeat(256);
const response = await request(app)
.post('/api/tasks')
.send({ title: longTitle })
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('Title too long');
});
});Bước 4: VERIFY (3 phút)
Copy code, install dependencies, run:
npm install supertest
npm test -- api.test.js
# Output:
# ✓ should create task with valid title and description
# ✓ should create task with title only
# ✓ should reject missing title
# ✓ should reject empty title
# ✓ should reject title > 255 chars
#
# 5 passed📝 Phần 3: Bài Tập Về Nhà
Task 1: Viết API Test cho GET /api/tasks (20 phút)
Dùng RARV loop để:
- REASON: Định scope test cases cho GET /api/tasks endpoint
- ACT: Prompt AI để generate test code (no parameters, should return all tasks)
- REFLECT: Đọc output AI
- VERIFY: Chạy test, đảm bảo PASS
Test cases cần cover:
- Empty database → return empty array
- Database has 3 tasks → return 3 tasks
- Tasks ordered by created_at DESC → verify order
Gợi ý prompt:
Write Vitest + supertest test for GET /api/tasks endpoint.
Endpoint: GET /api/tasks
Response: { success: true, tasks: [...] }
Test cases:
1. Empty database → []
2. Has 3 tasks → return all 3, ordered by created_at DESC
3. Verify task fields: id, title, description, status, created_at
Format: Complete Vitest code, ready to run.Task 2: Tìm 5 Edge Case Cho updateTask() (15 phút)
Function: updateTask(id, { title?, description?, status? })
Prompt AI để tìm 5 edge case. Output format: Table gồm Input, Expected, Why.
Gợi ý:
- Không tìm task với id đó
- Update title → cũng validate empty/length
- Update status → chỉ allow 'pending' | 'completed'
- Update non-existent field
- Concurrent updates
Task 3: Review createTask() Code (10 phút)
Prompt AI để review function createTask() code bạn viết.
function createTask(title, description) {
if (!title || title.length === 0) throw new Error('Title is required');
if (title.length > 255) throw new Error('Title too long');
return {
id: Math.random().toString(36).substr(2, 9),
title,
description,
created_at: new Date()
};
}Prompt:
Code review against:
1. Clarity
2. Error handling
3. Input validation
4. Performance
5. Security
6. Testability
7. Best practices
Output:
- Pass/Fail for each point
- Issues found
- Improvements✅ Phần 4: Kiểm Tra Kiến Thức
Quiz (20 phút)
Câu 1: RARV Loop là gì?
- A) Route → API → Request → Verify
- B) Reason → Act → Reflect → Verify
- C) Read → Analyze → Review → Validate
- D) Return → Append → Render → View
- Đáp án đúng: B
Câu 2: Tại sao prompt AI cần có output format?
- A) Để AI bớt output
- B) Để output consistent, dễ parse
- C) Để server không overload
- D) Không cần thiết
- Đáp án đúng: B
Câu 3: Khi viết unit test, test FAIL là tốt hay xấu (trong TDD)?
- A) Xấu, test phải PASS
- B) Tốt, FAIL = implementation chưa có
- C) Không quan trọng
- D) Tùy thuộc vào framework
- Đáp án đúng: B
Câu 4: Prompt template nào thiếu context nhất?
- A) "Write a test for createTask"
- B) "Write unit test for createTask(title, description), test empty title, format: Vitest"
- C) "Generate test for POST /api/tasks endpoint, body: {title, description}, response format: {success, task/error}"
- D) "Review code against SOLID and security"
- Đáp án đúng: A & D
Câu 5: SQL injection vulnerability trong createTask(), fix nào đúng?
- A) Chỉ validate title length
- B) Use prepared statements (bind parameters)
- C) Reject title có special characters
- D) Trim title trước lưu
- Đáp án đúng: B
Tiêu Chí Đánh Giá
| Tiêu Chí | Điểm |
|---|---|
| Quiz: 5/5 đúng | 2 điểm |
| Bài tập về nhà hoàn thành | 2 điểm |
| RARV loop thực hành thành công | 2 điểm |
| Edge case detection skills | 2 điểm |
| Hiểu prompt templates | 2 điểm |
| Tổng cộng | 10 điểm |
🔗 Liên Kết & Tài Liệu
- Buổi trước: 01-mindset-shift
- TaskFlow App: taskflow-app
- Vitest Documentation: https://vitest.dev
- Supertest Docs: https://github.com/visionmedia/supertest
- OWASP SQL Injection: https://owasp.org/www-community/attacks/SQL_Injection
💡 Ghi Chú Cho Giáo Viên
- AI Tool: Khuyến khích học viên dùng ChatGPT, Claude, hoặc Copilot
- Timing: Nếu chậm, bỏ phần code review (Template 5)
- Supertest Issue: Cần mock database hoặc dùng in-memory SQLite cho test
- Security: Nhấn mạnh SQL injection risk — discuss parameterized queries
- Follow-up: Buổi tiếp theo sẽ implement code từ test này
📊 Mẹo Prompting Nâng Cao (Optional)
Nếu thời gian cho phép, giáo viên có thể dạy thêm:
- Chain-of-Thought Prompting
"Let's think step by step.
Step 1: What are the inputs?
Step 2: What are the expected outputs?
Step 3: What are edge cases?
Step 4: Generate test code"- Few-Shot Prompting (ví dụ)
"Here's an example test:
test('rejects empty input', () => {
expect(() => func('')).toThrow('Input required');
});
test('accepts valid input', () => {
expect(func('valid')).toReturn(expected);
});
Now write similar tests for createTask()..."- Role-Based Prompting
"You are a QA engineer reviewing code.
Focus on: security, performance, testability.
Output: markdown report."