VISEN GROUP
Technology & Engineering

Tận dụng AI để viết Unit Test tự động: Đừng để test suite chỉ là hình thức

September 16, 20264 min readUncategorized

Hầu hết lập trình viên đều hiểu giá trị của Unit Test, nhưng việc viết test thường bị trì hoãn hoặc làm qua loa vì tốn thời gian thiết lập mock data, spy hàm và lặp lại các đoạn boilerplate nhàm chán. Kết quả là nhiều dự án chỉ test “vui vẻ” (Happy Path), bỏ quên toàn bộ các nhánh lỗi logic và biên dữ liệu (Edge Cases).

Với sự hỗ trợ của các công cụ AI hiện đại (Cursor, GitHub Copilot, Claude), bạn hoàn toàn có thể tự động sinh ra một bộ test suite chuẩn mực (Jest / Vitest) từ Interface hoặc Schema chỉ trong vài chục giây.

Vấn đề cốt lõi: Nếu chỉ bảo AI “viết test cho hàm này”, nó sẽ chỉ viết vài test case hiển nhiên. Bí quyết nằm ở việc ép AI tư duy theo mô hình kiểm thử hộp đen dựa trên ranh giới dữ liệu.

1. Kỹ thuật Context Injection: Cung cấp Interface thay vì toàn bộ codebase

Thay vì ném cả file controller hàng trăm dòng vào prompt khiến AI bị phân tâm, bạn chỉ cần cung cấp 3 thành phần:

  • Contract/Interface: Đầu vào, đầu ra của hàm.
  • Dependencies: Các service bên ngoài cần mock (Database model, Mailer, Cache).
  • Business Rules: Quy tắc nghiệp vụ cụ thể.

2. Template Prompt 3 tầng chuẩn kiểm thử

Sao chép prompt dưới đây vào Cursor Composer (Ctrl + I / Cmd + I) hoặc khung chat của Copilot:

Prompt Template:

“Đóng vai Senior QA Engineer và Backend Developer chuyên về [Jest / Vitest]. Hãy viết toàn bộ Unit Test cho hàm dưới đây:

TypeScript

[Dán code hàm hoặc interface TypeScript tại đây]

Yêu cầu bắt buộc:

  1. Mô hình AAA: Chia rõ từng test case theo 3 khối Arrange – Act – Assert.
  2. Mocking chuẩn: Dùng jest.mock() hoặc vi.mock() cho toàn bộ I/O bên ngoài, kèm hàm beforeEach(() => vi.clearAllMocks()).
  3. Độ phủ kiểm thử (Test Coverage):
    • 1 test case Happy Path (dữ liệu hoàn hảo).
    • 2 test case Boundary (dữ liệu chạm ngưỡng: mảng rỗng, chuỗi 0 ký tự, số âm).
    • 2 test case Error Handling (bị reject từ database, timeout API ngoài).
  4. Đặt tên test: Dùng cú pháp
it('should [kết quả mong muốn] when [điều kiện kích hoạt]')."

3. Ví dụ thực chiến: Test hàm xử lý thanh toán đơn hàng

Giả sử bạn có hàm trừ tiền ví người dùng:

TypeScript


// wallet.service.ts
export async function deductBalance(userId: string, amount: number) {
  if (amount <= 0) throw new Error('INVALID_AMOUNT');
  const user = await userRepo.findById(userId);
  if (!user) throw new Error('USER_NOT_FOUND');
  if (user.balance < amount) throw new Error('INSUFFICIENT_FUNDS');

  return await userRepo.updateBalance(userId, user.balance - amount);
}

Áp dụng prompt trên, AI sẽ sinh ra ngay một test suite hoàn chỉnh bao quát mọi nhánh rẽ:

TypeScript

// wallet.service.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { deductBalance } from './wallet.service';
import { userRepo } from './user.repo';

vi.mock('./user.repo');

describe('deductBalance()', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should deduct balance successfully when user has sufficient funds', async () => {
    // Arrange
    vi.mocked(userRepo.findById).mockResolvedValue({ id: 'u1', balance: 500 });
    vi.mocked(userRepo.updateBalance).mockResolvedValue({ id: 'u1', balance: 300 });

    // Act
    const result = await deductBalance('u1', 200);

    // Assert
    expect(result.balance).toBe(300);
    expect(userRepo.updateBalance).toHaveBeenCalledWith('u1', 300);
  });

  it('should throw INVALID_AMOUNT when amount is less than or equal to zero', async () => {
    await expect(deductBalance('u1', 0)).rejects.toThrow('INVALID_AMOUNT');
    await expect(deductBalance('u1', -50)).rejects.toThrow('INVALID_AMOUNT');
  });

  it('should throw INSUFFICIENT_FUNDS when balance is less than required amount', async () => {
    vi.mocked(userRepo.findById).mockResolvedValue({ id: 'u1', balance: 100 });
    await expect(deductBalance('u1', 200)).rejects.toThrow('INSUFFICIENT_FUNDS');
  });
});

4. Hai nguyên tắc vàng khi nhận code test từ AI

  • Không tin tưởng Assert tự sinh: Luôn rà soát xem AI có đang expect(true).toBe(true) hoặc assert lỏng lẻo không. Một test case xanh (pass) nhưng assert sai còn nguy hiểm hơn không viết test.
  • Test đột biến (Mutation Test tư duy): Thử cố tình sửa logic trong code chính (ví dụ đổi amount <= 0 thành amount < 0) rồi chạy lại test xem test case có đỏ (fail) hay không. Nếu test vẫn pass, test case đó vô giá trị.

AI không thay thế tư duy kiểm thử của lập trình viên, nhưng nó giải phóng bạn khỏi 80% công việc gõ mock data lặp lại để tập trung vào các trường hợp biên nguy hiểm nhất.

Leave a Reply

Your email address will not be published. Required fields are marked *