AI Regression Testing Skill: 5 Chiến Lược Tối Ưu Nhất

Khi chúng ta giao phó ngày càng nhiều công việc lập trình cho các trợ lý AI như Cursor, Claude Code hay GitHub Copilot, một vấn đề lớn bắt đầu xuất hiện...

Khi chúng ta giao phó ngày càng nhiều công việc lập trình cho các trợ lý AI như Cursor, Claude Code hay GitHub Copilot, một vấn đề lớn bắt đầu xuất hiện: AI tự viết code và sau đó cũng tự review mã nguồn của chính nó. Vấn đề là khi một mô hình AI thực hiện cả hai bước này, nó mang theo cùng một giả định và định kiến vào cả hai quá trình. Điều này dẫn đến sự ra đời của khái niệm AI regression testing — một giải pháp kiểm thử hồi quy đặc thù để phát hiện và ngăn chặn những lỗi lập trình tinh vi do chính AI gây ra.

Thực tế thì việc kiểm thử truyền thống thường tập trung vào logic của con người. Nhưng với mã nguồn do AI sinh ra, chúng ta cần một chiến lược kiểm thử khác biệt, hướng đến việc vá các lỗ hổng tư duy của mô hình ngôn ngữ lớn (LLM). Bài viết này sẽ đi sâu vào cơ chế hoạt động của kiểm thử hồi quy trong kỷ nguyên AI và cách thiết lập một hệ thống kiểm thử tự động không phụ thuộc cơ sở dữ liệu trên Next.js và Vitest.

Điểm mù hệ thống khi AI tự viết và tự duyệt mã nguồn

Thú thật là mình đã nhiều lần chứng kiến các dự án gặp lỗi nghiêm trọng chỉ vì quá tin tưởng vào khả năng tự sửa lỗi của AI Agent. Khi một lập trình viên AI tạo ra một bản sửa lỗi (bug fix), sau đó chúng ta chạy lệnh kiểm tra hoặc yêu cầu chính mô hình đó xem lại mã nguồn xem đã ổn chưa, câu trả lời nhận được hầu như luôn là: “Mã nguồn trông đã chính xác và sẵn sàng chạy”.

Nhưng tại sao lỗi vẫn tồn tại trên môi trường chạy thực tế? Câu trả lời nằm ở điểm mù nhận thức của AI. Mô hình AI đánh giá tính đúng đắn của mã nguồn dựa trên các token và ngữ cảnh mà nó vừa tạo ra. Nếu nó bỏ sót một điều kiện biên khi viết code, nó cũng sẽ bỏ sót chính điều kiện biên đó khi đọc lại đoạn code ấy. Vòng lặp phản hồi này hoàn toàn vô hại trên lý thuyết nhưng lại cực kỳ tai hại trong thực tế.

Mô hình AI viết mã nguồn và mô hình AI đánh giá mã nguồn thường mang cùng một tập hợp giả định sai lầm. Chỉ có các kiểm thử tự động hóa độc lập mới có thể phá vỡ vòng lặp này.

Hãy xem xét một ví dụ thực tế diễn ra trong dự án phát triển gần đây của mình. Dự án yêu cầu bổ sung trường thông tin cấu hình thông báo (notification_settings) vào API thông tin người dùng. Quá trình diễn ra như sau:

  • Bước 1: AI chỉnh sửa API response để trả về trường dữ liệu mới nhưng quên không bổ sung trường này vào câu lệnh SELECT SQL truy vấn cơ sở dữ liệu.
  • Bước 2: AI tự kiểm tra lại mã nguồn và kết luận: “Mã nguồn đã trả về đúng cấu trúc yêu cầu”. Lỗi này lọt qua vì AI chỉ nhìn vào phần định nghĩa kiểu dữ liệu trả về mà không nhận ra giá trị thực tế sẽ luôn là undefined.
  • Bước 3: Nhà phát triển yêu cầu AI sửa lỗi sau khi phát hiện lỗi build. AI thay thế câu SELECT bằng cách sử dụng SELECT * để lấy toàn bộ dữ liệu, sửa được lỗi trên môi trường production nhưng lại làm hỏng môi trường giả lập (sandbox) do cơ sở dữ liệu sandbox chưa được đồng bộ schema mới nhất.

Lỗi bất tương thích giữa sandbox và production chính là loại regression phổ biến nhất do AI giới thiệu. Để giải quyết triệt để vấn đề này, chúng ta cần một quy trình AI regression testing chặt chẽ, nơi các kịch bản kiểm thử được thiết lập độc lập và tự động hóa hoàn toàn.

Thiết lập Sandbox-Mode API Testing cách ly cơ sở dữ liệu

Một trong những rào cản lớn nhất khi chạy kiểm thử hồi quy cho API là sự phụ thuộc vào cơ sở dữ liệu thực tế. Việc kết nối, khởi tạo schema và dọn dẹp dữ liệu kiểm thử sau mỗi lượt chạy làm tăng thời gian chạy test từ vài giây lên vài phút. Điều này làm giảm đáng kể tần suất chạy kiểm thử của các nhà phát triển và cả các AI Agent.

Giải pháp tối ưu ở đây là thiết lập một chế độ giả lập (sandbox mode) toàn diện. Ở chế độ này, toàn bộ các truy vấn cơ sở dữ liệu hoặc các cuộc gọi API bên ngoài sẽ được định tuyến lại để đọc ghi trên bộ nhớ tạm (in-memory mock) hoặc dữ liệu mẫu tĩnh. Điều này giúp kiểm thử chạy cực kỳ nhanh và ổn định, tạo điều kiện thuận lợi cho việc kiểm thử hồi quy liên tục.

Nếu bạn đang phát triển các ứng dụng dựa trên Next.js App Router, sự kết hợp giữa Vitest và chế độ sandbox mode sẽ giúp việc viết test trở nên vô cùng mượt mà. Chúng ta có thể tham khảo thêm các bước tối ưu kiểm thử tương tự trong bài viết về webapp-testing skill để hiểu rõ hơn cách AI Agent tương tác với trình duyệt.

1. Cấu hình Vitest cho Next.js App Router

Đầu tiên, chúng ta cần tạo file cấu hình vitest.config.ts ở thư mục gốc của dự án. File này định nghĩa môi trường chạy test là Node.js, khai báo các file thiết lập môi trường và cấu hình đường dẫn alias quen thuộc của Next.js.

import { defineConfig } from "vitest/config";
import path from "path";

export default defineConfig({
  test: {
    environment: "node",
    globals: true,
    include: ["__tests__/**/*.test.ts"],
    setupFiles: ["__tests__/setup.ts"],
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "."),
    },
  },
});

Để đảm bảo việc cấu hình diễn ra chính xác, hãy tham khảo tài liệu chính thức của Vitest. Cấu hình trên sử dụng thiết lập `setupFiles` để chạy một file thiết lập môi trường trước khi bất kỳ ca kiểm thử nào được thực thi. Đây là nơi chúng ta sẽ kích hoạt chế độ sandbox mode.

2. Thiết lập File Khởi Tạo Môi Trường (Setup File)

Tạo file __tests__/setup.ts và thêm mã nguồn bên dưới. Mục đích của file này là ép buộc toàn bộ hệ thống chạy trong chế độ sandbox bằng cách gán các biến môi trường tương ứng. Chúng ta cũng vô hiệu hóa các khóa kết nối đến dịch vụ bên ngoài như Supabase để tránh việc vô tình gọi lên máy chủ cloud thực tế.

process.env.SANDBOX_MODE = "true";
process.env.NEXT_PUBLIC_SUPABASE_URL = "";
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = "";

Bằng cách đặt biến môi trường SANDBOX_MODE bằng “true”, tất cả các module xử lý dữ liệu trong ứng dụng Next.js của bạn sẽ biết rằng chúng cần sử dụng các bộ lưu trữ giả lập thay vì kết nối với cơ sở dữ liệu thực. Điều này giúp ngăn chặn các tác động ngoài ý muốn đến dữ liệu production và giảm thiểu rủi ro bảo mật, một chủ đề đã được thảo luận kỹ trong hướng dẫn về Supabase Agent Skills.

Xây dựng các Test Helpers mô phỏng HTTP Requests

Với Next.js App Router, các route handlers nhận đối tượng NextRequest và trả về Response. Để kiểm thử các route này mà không cần khởi chạy toàn bộ máy chủ HTTP của Next.js, chúng ta có thể gọi trực tiếp hàm GET, POST hoặc PATCH của route đó bằng cách truyền vào một đối tượng NextRequest được giả lập.

Dưới đây là bộ công cụ helper nằm trong file __tests__/helpers.ts giúp bạn dễ dàng khởi tạo các request giả lập một cách nhanh chóng, hỗ trợ đầy đủ các phương thức HTTP, truyền payload body và gán thông tin định danh người dùng giả lập.

import { NextRequest } from "next/server";

export function createTestRequest(
  url: string,
  options?: {
    method?: string;
    body?: Record<string, unknown>;
    headers?: Record<string, string>;
    sandboxUserId?: string;
  },
): NextRequest {
  const { method = "GET", body, headers = {}, sandboxUserId } = options || {};
  const fullUrl = url.startsWith("http") ? url : `http://localhost:3000${url}`;
  const reqHeaders: Record<string, string> = { ...headers };

  if (sandboxUserId) {
    reqHeaders["x-sandbox-user-id"] = sandboxUserId;
  }

  const init: { method: string; headers: Record<string, string>; body?: string } =  {
    method,
    headers: reqHeaders,
  };

  if (body) {
    init.body = JSON.stringify(body);
    reqHeaders["content-type"] = "application/json";
  }

  return new NextRequest(fullUrl, init);
}

export async function parseResponse(response: Response) {
  const json = await response.json();
  return { status: response.status, json };
}

Có một chi tiết thú vị là lớp NextRequest kế thừa từ Request tiêu chuẩn của trình duyệt nên nó yêu cầu một URL tuyệt đối khi khởi tạo. Hàm helper trên tự động bổ sung tiền tố localhost nếu chúng ta chỉ truyền vào đường dẫn tương đối, giúp mã nguồn kiểm thử ngắn gọn và dễ đọc hơn rất nhiều.

Triển khai kiểm thử dựa trên ràng buộc dữ liệu (Contract-Based Testing)

Để ngăn ngừa lỗi hồi quy hiệu quả nhất, nguyên tắc cốt lõi là chúng ta cần viết các ca kiểm thử dựa trên hợp đồng dữ liệu cam kết (data contract). Thay vì chỉ kiểm tra xem API có trả về mã lỗi 200 hay không, chúng ta phải xác minh sự tồn tại của tất cả các trường dữ liệu bắt buộc trong kết quả trả về.

Việc này đảm bảo rằng nếu trong tương lai, một AI Agent vô tình thay đổi câu lệnh truy vấn dữ liệu hoặc cấu trúc của cơ sở dữ liệu làm biến mất một trường thông tin quan trọng, hệ thống kiểm thử sẽ phát hiện ra ngay lập tức. Đây là cách bảo vệ hệ thống trước các tác động ngoài ý muốn khi cập nhật giao diện người dùng, như khi sử dụng các thư viện UI hiện đại được đề cập tại hướng dẫn về Agent Skill shadcn.

Tạo file __tests__/api/user/profile.test.ts để kiểm thử API thông tin cá nhân của người dùng:

import { describe, it, expect } from "vitest";
import { createTestRequest, parseResponse } from "../../helpers";
import { GET } from "@/app/api/user/profile/route";

const REQUIRED_FIELDS = [
  "id",
  "email",
  "full_name",
  "phone",
  "role",
  "created_at",
  "avatar_url",
  "notification_settings",
];

describe("GET /api/user/profile", () => {
  it("returns all required fields", async () => {
    const req = createTestRequest("/api/user/profile", {
      sandboxUserId: "usr_test_001",
    });
    const res = await GET(req);
    const { status, json } = await parseResponse(res);

    expect(status).toBe(200);
    expect(json.success).toBe(true);
    
    for (const field of REQUIRED_FIELDS) {
      expect(json.data).toHaveProperty(field);
    }
  });
});

Điểm đáng chú ý ở đây là mảng `REQUIRED_FIELDS` định nghĩa một hợp đồng dữ liệu nghiêm ngặt. Nếu bất kỳ trường thông tin nào như `notification_settings` bị thiếu hoặc trả về giá trị null không mong muốn, vòng lặp kiểm tra thuộc tính `toHaveProperty` sẽ kích hoạt lỗi fail test ngay lập tức.

5 chiến lược tối ưu quy trình AI Regression Testing hiệu quả

Việc áp dụng kiểm thử hồi quy trong môi trường làm việc phối hợp với AI đòi hỏi các quy tắc vận hành thông minh để tránh việc sinh ra quá nhiều tệp tin rác hoặc gây lãng phí tài nguyên hệ thống. Dưới đây là 5 chiến lược thực chiến mà mình đã đúc kết được.

1. Áp dụng triết lý kiểm thử hướng lỗi (Bug-Driven Testing)

Đừng cố gắng viết test bao phủ toàn bộ 100% dòng lệnh ngay từ đầu. Thay vào đó, hãy ưu tiên viết test cho các lỗi thực tế đã từng xảy ra và đã được vá. Mỗi khi phát hiện ra một lỗi nghiệp vụ hoặc lỗi hệ thống, bước đầu tiên trước khi sửa lỗi là viết một ca kiểm thử tái hiện đúng lỗi đó (để kết quả chạy test ban đầu là fail). Sau đó mới tiến hành sửa code để test chuyển sang pass. Điều này đảm bảo lỗi cũ sẽ không bao giờ xuất hiện lại.

2. Tích hợp chạy test tự động vào quy trình CI/CD

Hệ thống kiểm thử chỉ có giá trị khi nó được chạy liên tục. Bạn nên cấu hình hệ thống CI/CD (như GitHub Actions hoặc GitLab CI) để tự động chạy toàn bộ các ca kiểm thử hồi quy mỗi khi có pull request hoặc có thay đổi trên nhánh chính. Do chế độ sandbox mode giúp các ca test chạy cực kỳ nhanh (thường dưới 10 giây cho hàng trăm ca test), việc này sẽ không làm gián đoạn tiến độ công việc của nhóm.

3. Sử dụng AI để tự động tạo dữ liệu biên (Edge-case Generator)

Mặc dù AI có thể có điểm mù khi tự viết logic nghiệp vụ, nhưng nó lại cực kỳ xuất sắc trong việc liệt kê các trường hợp biên nguy hiểm (edge cases). Bạn có thể yêu cầu AI Agent phân tích mã nguồn của API và tự động sinh ra các đối tượng dữ liệu đầu vào chứa ký tự đặc biệt, giá trị cực đại, giá trị rỗng hoặc định dạng sai lệch để kiểm thử độ bền bỉ của API.

4. Đảm bảo tính đồng bộ cấu hình giữa sandbox và production

Để tránh lỗi “chạy tốt trên máy tôi nhưng lỗi trên server”, chúng ta cần đảm bảo cấu hình và môi trường giả lập mô phỏng chính xác hành vi của môi trường production. Nếu production sử dụng cơ sở dữ liệu PostgreSQL có phân biệt chữ hoa chữ thường trong câu lệnh truy vấn, thì lớp giả lập dữ liệu trong sandbox mode cũng phải mô phỏng chính xác cơ chế so khớp này.

5. Thiết lập phân vai rõ ràng cho các AI Agent khác nhau

Nếu có thể, hãy sử dụng hai tác nhân AI khác nhau hoặc hai phiên làm việc (sessions) độc lập cho hai nhiệm vụ: một bên chịu trách nhiệm viết mã nguồn triển khai tính năng và một bên chịu trách nhiệm viết kịch bản kiểm thử. Việc phân tách trách nhiệm này giúp hạn chế tối đa việc lặp lại cùng một lối mòn tư duy và giảm thiểu điểm mù hệ thống.

Quy trình tích hợp kiểm thử hồi quy vào công việc hằng ngày

Để quy trình AI regression testing đi vào nề nếp, nhóm phát triển có thể áp dụng các bước hành động cụ thể sau:

  • Bước 1: Khi phát hiện lỗi trong API, hãy ghi lại payload gây lỗi và cấu trúc dữ liệu mong muốn.
  • Bước 2: Viết một ca kiểm thử mới trong thư mục `__tests__` mô tả lỗi này, chạy thử để xác nhận test bị hỏng (đỏ).
  • Bước 3: Chỉ định AI Agent sửa lỗi trong mã nguồn chính của ứng dụng Next.js.
  • Bước 4: Chạy lệnh `vitest run` cục bộ để đảm bảo ca kiểm thử mới và toàn bộ các ca kiểm thử cũ đều vượt qua thành công (xanh).
  • Bước 5: Đẩy mã nguồn lên hệ thống quản lý phiên bản để kích hoạt chạy kiểm tra tự động trên CI/CD trước khi tiến hành deploy lên Vercel hoặc các nền tảng đám mây khác.

Để tìm hiểu thêm về cách thức hoạt động của các hệ thống route và ứng dụng Next.js, tài liệu chính thức của Next.js là một nguồn tài nguyên vô cùng quý giá mà mọi lập trình viên nên tham khảo thường xuyên.

Kết luận và góc nhìn thực chiến

Kiểm thử hồi quy tự động không còn là một lựa chọn “có thì tốt” mà đã trở thành yêu cầu bắt buộc để sống sót trong kỷ nguyên phát triển phần mềm có sự hỗ trợ của AI. Bằng cách thiết lập chế độ sandbox mode thông minh và xây dựng các bộ test helper tinh gọn, chúng ta có thể loại bỏ hoàn toàn các điểm mù nhận thức của AI Agent, giúp quá trình bàn giao mã nguồn diễn ra an toàn và tự tin hơn bao giờ hết.

Hy vọng những chia sẻ thực tế và các mẫu mã nguồn cấu hình trên đây sẽ giúp bạn xây dựng được một hệ thống kiểm thử hồi quy vững chắc cho dự án của mình. Chúc các bạn thành công trên con đường làm chủ các công nghệ lập trình hiện đại!