Best Practices & Testing
Nâng tầm code từ "chạy được" lên "production-ready" — với code quality tools, unit testing, documentation đúng chuẩn và Git workflow chuyên nghiệp.
🎯 Mục tiêu học tập
- Setup ESLint + Prettier để code nhất quán và không lỗi
- Viết unit tests với Jest (JS) và pytest (Python)
- Viết README chuyên nghiệp và JSDoc documentation
- Áp dụng Git workflow với Conventional Commits và branching strategy
Bài 9.1 — Code Quality: ESLint + Prettier
Tại sao cần Linter và Formatter?
ESLint — Phát hiện lỗi
Tìm bugs tiềm năng, unused variables, anti-patterns trước khi runtime. Đặc biệt quan trọng với AI-generated code.
Prettier — Format code
Tự động format code: indent, quotes, trailing commas... Không cần tranh luận style, mọi người code như nhau.
Pre-commit Hooks
Tự động chạy ESLint + Prettier trước mỗi commit với Husky. Code xấu sẽ không được commit.
Setup ESLint + Prettier cho dự án Node.js/React
npm install -D eslint prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks
# Init ESLint config
npx eslint --init
module.exports = {
env: {
browser: true,
es2022: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier', // Tắt các rules conflict với Prettier — phải để cuối
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
rules: {
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'no-console': ['warn', { allow: ['warn', 'error'] }],
'react/prop-types': 'warn',
'react/react-in-jsx-scope': 'off', // React 17+ không cần import React
},
settings: {
react: { version: 'detect' },
},
};
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid"
}
Setup Husky — Pre-commit Hooks
npm install -D husky lint-staged
npx husky install
# Add pre-commit hook
npx husky add .husky/pre-commit "npx lint-staged"
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,css}": [
"prettier --write"
]
}
}
Khi AI generate code và ESLint báo lỗi, paste lỗi vào Copilot Chat: "ESLint báo lỗi này: [error message]. Fix cho tôi và giải thích tại sao?" — Cách học tốt nhất là sửa lỗi thực tế.
Bài 9.2 — Unit Testing
Quy Trình TDD — Red, Green, Refactor
Viết test mô tả behavior mong muốn TRƯỚC KHI có code. Test phải fail vì chưa implement. Đây là lúc define "done" rõ ràng nhất.
Viết CODE ÍT NHẤT để test pass. Không cần đẹp, không cần tối ưu. Mục tiêu duy nhất: test xanh.
Refactor code sạch hơn, loại bỏ duplication, tối ưu. Tests vẫn phải pass sau khi refactor. Đây là "safety net".
Jest / Vitest Matchers — Tham Khảo Nhanh
| Matcher | Dùng cho | Ví dụ |
|---|---|---|
toBe(val) | So sánh bằng (===) | expect(2+2).toBe(4) |
toEqual(obj) | So sánh deep equality (object/array) | expect(user).toEqual({id: 1, name: 'An'}) |
toBeNull() | Kiểm tra null | expect(result).toBeNull() |
toBeTruthy() / toBeFalsy() | Truthy / Falsy check | expect(token).toBeTruthy() |
toContain(item) | Array/string chứa item | expect(list).toContain('react') |
toThrow(msg?) | Function ném lỗi | expect(() => fn()).toThrow('Invalid') |
toHaveBeenCalled() | Mock function đã được gọi | expect(mockFn).toHaveBeenCalledTimes(1) |
toMatchObject(obj) | Object chứa subset | expect(response).toMatchObject({status: 200}) |
resolves / rejects | Promise resolved/rejected | await expect(fn()).resolves.toBe('ok') |
JavaScript: Testing với Jest + Vitest
# Cho Vite/React project — dùng Vitest (nhanh hơn Jest, tích hợp Vite)
npm install -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom
# Cho Node.js — dùng Jest
npm install -D jest @jest/globals supertest
import { describe, it, expect } from 'vitest';
import { formatPrice, formatDate } from './formatters';
describe('formatPrice', () => {
it('formats number to VND currency', () => {
expect(formatPrice(100000)).toBe('100.000 ₫');
expect(formatPrice(1500000)).toBe('1.500.000 ₫');
});
it('returns "0 ₫" for 0', () => {
expect(formatPrice(0)).toBe('0 ₫');
});
it('handles negative numbers', () => {
expect(formatPrice(-5000)).toBe('-5.000 ₫');
});
});
describe('formatDate', () => {
it('formats ISO date to dd/MM/yyyy', () => {
expect(formatDate('2024-01-15')).toBe('15/01/2024');
});
});
const request = require('supertest');
const app = require('../src/app');
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
let authToken;
beforeAll(async () => {
// Tạo user test và lấy token
const res = await request(app)
.post('/api/auth/register')
.send({ email: 'test@test.com', username: 'testuser', password: 'Password123' });
authToken = res.body.token;
});
afterAll(async () => {
// Dọn sạch test data
await prisma.user.deleteMany({ where: { email: 'test@test.com' } });
await prisma.$disconnect();
});
describe('POST /api/todos', () => {
it('creates a todo when authenticated', async () => {
const res = await request(app)
.post('/api/todos')
.set('Authorization', `Bearer ${authToken}`)
.send({ title: 'Test todo', priority: 'high' });
expect(res.status).toBe(201);
expect(res.body).toMatchObject({
title: 'Test todo',
priority: 'high',
completed: false,
});
});
it('returns 401 without auth token', async () => {
const res = await request(app).post('/api/todos').send({ title: 'Test' });
expect(res.status).toBe(401);
});
it('returns 400 for empty title', async () => {
const res = await request(app)
.post('/api/todos')
.set('Authorization', `Bearer ${authToken}`)
.send({ title: '' });
expect(res.status).toBe(400);
});
});
Python: Testing với pytest
import pytest
from pathlib import Path
from organizer import categorize_file, organize_directory
# Dùng tmp_path fixture của pytest (tự tạo và xóa folder tạm)
def test_categorize_image(tmp_path):
file = tmp_path / "photo.jpg"
assert categorize_file(file) == "Images"
def test_categorize_document(tmp_path):
file = tmp_path / "report.pdf"
assert categorize_file(file) == "Documents"
def test_categorize_unknown(tmp_path):
file = tmp_path / "data.xyz"
assert categorize_file(file) == "Others"
def test_organize_moves_files(tmp_path):
# Tạo files test
(tmp_path / "photo.jpg").touch()
(tmp_path / "doc.pdf").touch()
(tmp_path / "music.mp3").touch()
organize_directory(tmp_path)
# Kiểm tra files đã di chuyển đúng chỗ
assert (tmp_path / "Images" / "photo.jpg").exists()
assert (tmp_path / "Documents" / "doc.pdf").exists()
assert (tmp_path / "Audio" / "music.mp3").exists()
def test_organize_handles_empty_dir(tmp_path):
# Không nên raise exception với folder rỗng
organize_directory(tmp_path)
assert True # Reached without exception
Prompt AI để generate tests
Đây là function tôi cần test:
[paste function code]
Viết unit tests với Jest/Vitest cho function này:
- Happy path (input hợp lệ)
- Edge cases (empty, null, boundary values)
- Error cases (invalid input)
Đặt mỗi test case trong describe/it rõ ràng
Bài 9.3 — Documentation Chuyên Nghiệp
README chuẩn dự án
# Tên Dự Án
> Một dòng mô tả ngắn gọn và hấp dẫn về dự án.
[](LICENSE)
[](https://nodejs.org)
## ✨ Tính năng
- ✅ Authentication (JWT)
- ✅ CRUD operations
- ✅ File upload
- 🚧 Email notifications (coming soon)
## 🚀 Bắt đầu nhanh
### Yêu cầu hệ thống
- Node.js 18+
- npm 9+
### Cài đặt
```bash
git clone https://github.com/your-username/project-name.git
cd project-name
npm install
cp .env.example .env # Điền thông tin cấu hình
npm run dev
```
Truy cập: http://localhost:3000
## ⚙️ Cấu hình (.env)
| Biến | Mô tả | Mặc định |
|---------------------|---------------------------|------------|
| `PORT` | Port server | `3000` |
| `DATABASE_URL` | Chuỗi kết nối database | Bắt buộc |
| `JWT_SECRET` | Secret key cho JWT | Bắt buộc |
| `ALLOWED_ORIGIN` | CORS allowed origin | `http://localhost:5173` |
## 📡 API Endpoints
| Method | Endpoint | Mô tả | Auth |
|--------|--------------------|--------------------|------|
| POST | `/api/auth/register` | Đăng ký tài khoản | ❌ |
| POST | `/api/auth/login` | Đăng nhập | ❌ |
| GET | `/api/todos` | Lấy danh sách todo | ✅ |
| POST | `/api/todos` | Tạo todo mới | ✅ |
## 🧪 Chạy Tests
```bash
npm test # Chạy tất cả tests
npm run test:watch # Watch mode
npm run test:coverage # Xem test coverage
```
## 🤝 Đóng góp
Pull requests được chào đón. Xem [CONTRIBUTING.md](CONTRIBUTING.md) để biết thêm.
## 📝 License
MIT © [Tên của bạn]
Bài 9.4 — Git Workflow Chuyên Nghiệp
Conventional Commits
Conventional Commits là quy ước đặt tên commit message theo format chuẩn. AI rất giỏi generate commit messages chuẩn.
type(scope): description
[optional body]
[optional footer]
------- CÁC TYPES -------
feat: Thêm tính năng mới
fix: Sửa bug
docs: Thay đổi documentation
style: Format code (không đổi logic)
refactor: Refactor code (không feat, không fix)
test: Thêm hoặc sửa tests
chore: Cập nhật build scripts, dependencies
------- VÍ DỤ -------
feat(auth): add JWT refresh token endpoint
fix(api): return 404 when post not found
docs(readme): add API endpoints table
test(todos): add edge cases for empty title
chore(deps): upgrade prisma to v5.8
Branching Strategy (GitHub Flow)
# Main branch = production-ready, luôn deployable
# Mỗi feature/fix = 1 branch riêng
# 1. Tạo feature branch từ main
git checkout main
git pull origin main
git checkout -b feat/add-search-filter
# 2. Code, commit thường xuyên
git add -p # Chọn từng hunk để add
git commit -m "feat(posts): add search by title"
# 3. Push và tạo Pull Request
git push -u origin feat/add-search-filter
# → Tạo PR trên GitHub → Code review → Merge
# 4. Sau khi merge, xóa branch
git checkout main
git pull origin main
git branch -d feat/add-search-filter
Dùng AI để viết commit messages
# Xem diff trước khi commit
git diff --staged
# Nhờ Copilot CLI generate commit message
# (Copilot sẽ đọc diff và suggest message chuẩn Conventional Commits)
git commit # Copilot sẽ suggest message trong terminal
Cài gh copilot extension: gh extension install github/gh-copilot. Sau đó dùng gh copilot suggest để nhờ AI suggest git commands, hoặc gh copilot explain để AI giải thích một lệnh phức tạp.
Bài 9.5 — Setup Testing Hoàn Chỉnh: Unit Test + Integration Test + CI/CD
Chúng ta sẽ setup hệ thống testing đầy đủ cho cả backend (Vitest + Supertest) và frontend (React Testing Library), sau đó tích hợp vào CI/CD pipeline với GitHub Actions.
Bước 1 — Setup Testing Cho Backend (Express API)
cd backend
# Cài Vitest (nhanh hơn Jest, tương thích Jest API)
npm install --save-dev vitest supertest @vitest/coverage-v8
# Thêm script vào package.json
# "test": "vitest run"
# "test:watch": "vitest"
# "test:coverage": "vitest run --coverage"
# Tạo thư mục test
mkdir tests tests/unit tests/integration
touch tests/unit/utils.test.js tests/integration/auth.test.js tests/integration/notes.test.js
// tests/unit/utils.test.js — Unit test cho helper functions
// Prompt Copilot: "Viết unit tests cho các functions trong utils.js"
import { describe, it, expect } from 'vitest';
// Ví dụ: test helper function validateEmail
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validatePassword(password) {
return password.length >= 8;
}
describe('validateEmail', () => {
it('trả về true với email hợp lệ', () => {
expect(validateEmail('user@example.com')).toBe(true);
expect(validateEmail('user+tag@domain.co')).toBe(true);
});
it('trả về false với email không hợp lệ', () => {
expect(validateEmail('notanemail')).toBe(false);
expect(validateEmail('@domain.com')).toBe(false);
expect(validateEmail('')).toBe(false);
});
});
describe('validatePassword', () => {
it('chấp nhận password đủ 8 ký tự', () => {
expect(validatePassword('12345678')).toBe(true);
expect(validatePassword('LongPassword123')).toBe(true);
});
it('từ chối password ngắn hơn 8 ký tự', () => {
expect(validatePassword('short')).toBe(false);
expect(validatePassword('')).toBe(false);
});
});
// tests/integration/auth.test.js — Integration test cho auth endpoints
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import app from '../../src/app.js'; // Export app riêng (không gọi app.listen)
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Cleanup database trước và sau test suite
beforeAll(async () => {
await prisma.user.deleteMany({ where: { email: { contains: '@test.com' } } });
});
afterAll(async () => {
await prisma.user.deleteMany({ where: { email: { contains: '@test.com' } } });
await prisma.$disconnect();
});
describe('POST /api/auth/register', () => {
it('đăng ký thành công với thông tin hợp lệ', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({ email: 'newuser@test.com', password: 'Password123', name: 'Test User' });
expect(res.status).toBe(201);
expect(res.body).toHaveProperty('token');
expect(res.body.user.email).toBe('newuser@test.com');
expect(res.body.user).not.toHaveProperty('password'); // Không trả về password
});
it('trả về 409 khi email đã tồn tại', async () => {
// Đăng ký lần 2 với cùng email
const res = await request(app)
.post('/api/auth/register')
.send({ email: 'newuser@test.com', password: 'Password123' });
expect(res.status).toBe(409);
expect(res.body.error).toMatch(/đã được đăng ký/);
});
});
describe('POST /api/auth/login', () => {
it('đăng nhập thành công với thông tin đúng', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({ email: 'newuser@test.com', password: 'Password123' });
expect(res.status).toBe(200);
expect(res.body).toHaveProperty('token');
});
it('trả về 401 với mật khẩu sai', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({ email: 'newuser@test.com', password: 'WrongPassword' });
expect(res.status).toBe(401);
});
});
# Chạy tất cả tests một lần
npm test
# Chạy tests ở chế độ watch (tự reload khi save file)
npm run test:watch
# Chạy tests với coverage report
npm run test:coverage
# Chạy chỉ một file test cụ thể
npx vitest run tests/integration/auth.test.js
# Chạy tests match pattern tên
npx vitest run --reporter=verbose
Bước 2 — Setup ESLint + Prettier
# Cài ESLint (chạy trong thư mục gốc project)
npm install --save-dev eslint @eslint/js
# Khởi tạo config ESLint
npx eslint --init
# Trả lời các câu hỏi:
# - To check syntax and find problems
# - CommonJS (hoặc ESM tùy project)
# - None of these (nếu không dùng framework)
# - No (TypeScript)
# - Node.js
# - JSON format
# Cài Prettier
npm install --save-dev prettier eslint-config-prettier
# Tạo .prettierrc
echo '{"semi":true,"singleQuote":true,"tabWidth":2,"printWidth":100}' > .prettierrc
# Tạo .eslintignore
echo 'node_modules\ndist\ncoverage' > .eslintignore
# Cài Husky và lint-staged
npm install --save-dev husky lint-staged
# Khởi tạo Husky
npx husky init
# Tạo pre-commit hook
echo 'npx lint-staged' > .husky/pre-commit
# Thêm lint-staged config vào package.json:
# "lint-staged": {
# "*.js": ["eslint --fix", "prettier --write"],
# "*.{json,md}": ["prettier --write"]
# }
# Test pre-commit hook
git add .
git commit -m "test: add testing setup"
# Hook sẽ tự động chạy ESLint + Prettier trước khi commit
Bước 3 — Setup GitHub Actions CI/CD
mkdir -p .github/workflows
touch .github/workflows/ci.yml
name: CI — Lint, Test, Build
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test-backend:
name: Backend Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: backend/package-lock.json
- name: Install backend dependencies
working-directory: ./backend
run: npm ci
- name: Run ESLint
working-directory: ./backend
run: npm run lint
- name: Run tests
working-directory: ./backend
env:
DATABASE_URL: "file:./test.db"
JWT_SECRET: "ci-test-secret-key-32-characters-long"
NODE_ENV: "test"
run: npm test
test-frontend:
name: Frontend Build Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
working-directory: ./frontend
run: npm ci
- name: Build frontend
working-directory: ./frontend
run: npm run build
Mỗi khi bạn push code lên GitHub, workflow sẽ tự động chạy: lint → test → build. Pull Request sẽ bị block nếu có test fail. Đây là quy trình chuyên nghiệp mà mọi team làm việc.
- Test names nên đọc như câu chuyện: "given X, when Y, then Z". Ví dụ: "given valid email, when user registers, then returns 201 with token".
- Đừng mock quá nhiều trong tests — mock chỉ external services (APIs, email). Test với real database (SQLite in-memory) cho integration tests.
- Commit message quan trọng hơn bạn nghĩ. Sau 6 tháng,
git loglà cách duy nhất để hiểu tại sao code được viết như vậy. - Husky + lint-staged = code review tự động. Mỗi commit đều pass linter — team không cần nói "nhớ chạy eslint" nữa.
Bài 9.6 — AI-Assisted Code Review & Refactoring
Code review là kỹ năng quan trọng nhất để trở thành developer giỏi. AI có thể review code của bạn như một senior developer — bắt bugs, suggest improvements, và giải thích tại sao. Đây là bài học về cách dùng AI để nâng cao chất lượng code liên tục.
- Correctness: logic có đúng không?
- Security: có vulnerabilities không?
- Performance: bottlenecks, N+1 queries
- Maintainability: dễ đọc, dễ thay đổi
- Extract function / Extract class
- Replace magic numbers with constants
- Eliminate duplication (DRY)
- Simplify conditionals
Prompt Template: Code Review Toàn Diện
Hãy review code này theo vai trò Senior Developer với 10 năm kinh nghiệm. Phân tích theo các dimension sau: **1. CORRECTNESS** - Logic có đúng không? Có edge cases nào bị bỏ qua? - Error handling có đầy đủ không? - Race conditions hay async issues? **2. SECURITY** (OWASP Top 10) - Input validation đủ chưa? - Data exposure risks? - Authentication/Authorization checks? **3. PERFORMANCE** - Có N+1 query problem không? (database) - Unnecessary re-renders? (React) - Memory leaks tiềm ẩn? - Có thể cache gì? **4. MAINTAINABILITY** - Function/class có làm quá nhiều việc? (Single Responsibility) - Có magic numbers/strings cần extract thành constants? - Naming: variable names có rõ ràng không? - Duplication: có code nào cần refactor DRY? **5. TESTS** - Cần viết thêm tests cho trường hợp nào? - Test coverage hiện tại có đủ không? Code cần review: [paste code vào đây] Format output: từng issue với severity (critical/major/minor) + code fix cụ thể.
Refactoring Thực Tế: Before & After
// ❌ BEFORE: Nhiều vấn đề
async function processOrder(o: any) {
if (o.status == 'pending') {
const db = await getDb();
const u = await db.query(`SELECT * FROM users WHERE id = ${o.user_id}`); // SQL injection!
if (u.rows.length > 0) {
if (u.rows[0].balance >= o.amount) {
await db.query(`UPDATE users SET balance = balance - ${o.amount} WHERE id = ${o.user_id}`);
await db.query(`UPDATE orders SET status = 'completed' WHERE id = ${o.id}`);
// Gửi email nhưng không handle error
sendEmail(u.rows[0].email, 'Order completed');
return true;
} else {
return false;
}
}
}
return false;
}
// ✅ AFTER: Clean, safe, maintainable
interface Order { id: number; userId: number; amount: number; status: OrderStatus; }
type OrderStatus = 'pending' | 'completed' | 'failed' | 'cancelled';
class InsufficientBalanceError extends Error {
constructor() { super('Insufficient balance'); this.name = 'InsufficientBalanceError'; }
}
async function processOrder(order: Order): Promise<void> {
if (order.status !== 'pending') {
throw new Error(`Cannot process order with status: ${order.status}`);
}
const user = await prisma.user.findUniqueOrThrow({ where: { id: order.userId } });
if (user.balance < order.amount) {
throw new InsufficientBalanceError();
}
// Transaction: cả hai update xảy ra cùng lúc hoặc không cái nào
await prisma.$transaction([
prisma.user.update({
where: { id: order.userId },
data: { balance: { decrement: order.amount } }
}),
prisma.order.update({
where: { id: order.id },
data: { status: 'completed' }
})
]);
// Không block order process nếu email fail
sendOrderConfirmationEmail(user.email, order).catch(err => {
console.error('Failed to send confirmation email:', err);
// Log to monitoring service but don't throw
});
}
Refactor đoạn code này cho tôi theo các nguyên tắc: - Single Responsibility: mỗi function chỉ làm 1 việc - DRY: loại bỏ code trùng lặp - Early return: reduce nesting levels - TypeScript: thêm types đầy đủ, không dùng any - Error handling: sử dụng custom errors thay vì return boolean - Security: fix SQL injection nếu có - Database: wrap multiple writes trong transaction [paste code vào đây] Sau khi refactor, giải thích từng thay đổi và tại sao nó tốt hơn.
Code Metrics — Đo Lường Chất Lượng Code
| Metric | Target | Tool |
|---|---|---|
| Test Coverage | > 70% cho utils, > 50% tổng thể | Jest --coverage |
| Cyclomatic Complexity | < 10 per function | ESLint complexity rule |
| Function Length | < 30 lines (guideline) | ESLint max-lines-per-function |
| Bundle Size (Frontend) | < 200KB initial JS | vite-bundle-analyzer |
| Lighthouse Score | > 90 Performance | Chrome DevTools |
| TypeScript Strict | 0 type errors | tsc --noEmit |
| Dependency Age | No critical CVEs | npm audit |
- Trước commit: Highlight code mới viết → Copilot
/review→ fix issues - Khi viết PR: Paste diff vào Copilot Chat → "Review PR này, tìm bugs và suggest improvements"
- Học từ AI: Sau khi nhận feedback, hỏi thêm "Tại sao approach này tốt hơn? Nguyên tắc design pattern nào?"
- Weekly: Chạy
npm audit+ update dependencies — hỏi Copilot về breaking changes
- Lấy file code phức tạp nhất trong project của bạn (>50 lines)
- Mở Copilot Chat, paste code + prompt review 5 chiều ở trên
- Với từng issue Copilot tìm thấy: hiểu tại sao, rồi fix
- Refactor 1 function dùng prompt template refactoring
- Chạy
npm test -- --coverage→ xem coverage report, viết thêm tests cho functions coverage thấp - So sánh code trước và sau refactoring — cảm nhận sự khác biệt
- Setup ESLint + Prettier + Husky cho project của bạn — từ giờ mỗi commit đều qua linter
- Viết unit tests cho 5 functions quan trọng nhất — target 70% coverage
- Dùng prompt review 5 chiều để review lại toàn bộ auth module
- Refactor 2 functions phức tạp nhất theo nguyên tắc Single Responsibility
- Viết README hoàn chỉnh với badges (CI status, coverage, license)
- Thử thách: Setup SonarCloud (free cho open source) — scan toàn bộ codebase và fix tất cả issues Critical/Major
- Test chỉ happy path: 90% bugs xảy ra ở edge cases — empty input, null, concurrent requests. Copilot prompt: “Generate edge case tests cho function này”
- Mock quá nhiều: Unit test mock mọi thứ → test pass nhưng production fail. Kết hợp integration tests với real dependencies để đảm bảo end-to-end.
- Test coverage ≠ code quality: 100% coverage với tests trivial vẫn là code xấu. Focus vào test meaningful behaviors, không chase số %.
- Bỏ qua ESLint warnings: Warnings hôm nay = bugs ngày mai. Treat warnings as errors trong CI: thêm
"error": truetrong ESLint config. - Refactor không có tests trước: Không bao giờ refactor code quan trọng mà chưa có test bao phủ. Viết tests trước → refactor → tests vẫn xanh = safe.
npm run lint→ 0 errors, 0 warnings (hoặc chỉ intentional suppressions)npm test -- --coverage→ ≥70% statement coverage cho utils, ≥50% tổng thể- Mọi function public có JSDoc/docstring, mọi API endpoint có mô tả rõ ràng
- Git log có commit messages theo Conventional Commits (feat:, fix:, docs:...)
- Code review 5 chiều với Copilot: 0 issues Critical, ≤3 issues Major
- README đầy đủ — người lạ có thể clone repo và chạy app trong 5 phút
🗒 Tóm Tắt Chương 9
- ESLint + Prettier + Husky = "safety net" — setup 1 lần, chạy mãi mãi
- Unit test: test 1 function riêng lẻ, fast, no side effects; Integration: nhiều layers, real DB
- AI review code theo framework 5 chiều: Correctness, Security, Performance, Maintainability, Tests
- Refactoring không phải viết lại — là làm code sạch hơn mà không thay đổi behavior
- Code metrics: coverage >70%, complexity <10, bundle <200KB — đo và cải thiện liên tục
- Conventional Commits + GitHub Flow: workflow chuyên nghiệp cho solo hoặc team