1. Bối cảnh doanh nghiệp: quy mô, SLA và tuân thủ của core-banking
Một hệ thống core-banking không giống một ứng dụng web thông thường. Nó xử lý hàng triệu giao dịch mỗi ngày, mỗi giao dịch chạm trực tiếp vào tiền thật của khách hàng, và một lỗi nhỏ có thể khiến ngân hàng mất tiền hoặc vi phạm quy định. Trong bối cảnh đó, đội kiểm thử không được phép coi 'test xanh' là mục tiêu; mục tiêu thật là chứng minh rằng mọi bút toán đều cân, tiền không bao giờ tự sinh ra hay biến mất, và mỗi lần thử lại chỉ tạo đúng một trạng thái cuối cùng. Bài viết này mô tả cách AI hỗ trợ tăng tốc regression cho core-banking mà không hề được phép quyết định tính đúng đắn.
Các ràng buộc phi chức năng ở đây khắc nghiệt hơn hẳn. SLA thường yêu cầu độ trễ ghi sổ dưới một ngưỡng cứng và tỉ lệ khả dụng bốn hoặc năm số chín. Về tuân thủ, mọi giao dịch phải có dấu vết kiểm toán bất biến, dữ liệu nhạy cảm phải được che khi vào môi trường test, và các chuẩn như PCI-DSS hay quy định ngân hàng trung ương chi phối cách bạn xử lý số thẻ, khoá và log. Vì thế 'kiểm thử nhanh hơn nhờ AI' phải luôn đi kèm câu hỏi: nhanh hơn nhưng có còn giữ được dấu vết kiểm toán và tính tất định không?
2. Kiến trúc và mô hình dữ liệu: sổ cái, tài khoản và bút toán
Để kiểm thử đúng, trước hết phải hiểu mô hình dữ liệu. Trái tim của core-banking là sổ cái (ledger) gồm các tài khoản (account) và các bút toán (entry). Một giao dịch chuyển tiền không được lưu như 'trừ số dư A, cộng số dư B' một cách rời rạc, mà được ghi thành một transaction chứa ít nhất hai entry đối ứng: một bên nợ (debit) và một bên có (credit), sao cho tổng của chúng luôn bằng không. Số dư của mỗi tài khoản là hệ quả suy ra từ tổng các bút toán, không phải một con số được cập nhật tự do. Chính thiết kế này khiến bút toán kép trở thành oracle tự nhiên.
-- Mô hình dữ liệu sổ cái tối giản (bút toán kép)
CREATE TABLE accounts (
id BIGINT PRIMARY KEY,
currency CHAR(3) NOT NULL,
-- KHÔNG lưu balance như cột tự do; balance = SUM(entries.amount)
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE transactions (
id UUID PRIMARY KEY,
idempotency_key UUID UNIQUE NOT NULL, -- chống double-post
status TEXT NOT NULL, -- PENDING|POSTED|REVERSED
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE entries (
id BIGINT PRIMARY KEY,
transaction_id UUID NOT NULL REFERENCES transactions(id),
account_id BIGINT NOT NULL REFERENCES accounts(id),
amount BIGINT NOT NULL, -- đơn vị nhỏ nhất (cents); debit<0, credit>0
CHECK (amount <> 0)
);
-- Bất biến sổ cái: với mỗi transaction, SUM(entries.amount) = 0Hãy để ý ba chi tiết quan trọng. Thứ nhất, số dư không được lưu như một cột có thể ghi tự do; nó là kết quả cộng dồn các bút toán, nên không thể bị 'lệch' âm thầm. Thứ hai, mỗi transaction có một idempotency_key duy nhất, là chốt chặn để một yêu cầu chuyển tiền gửi trùng không tạo hai bút toán. Thứ ba, số tiền được lưu bằng đơn vị nhỏ nhất là số nguyên (cents), không dùng số thực dấu phẩy động, để tránh sai số làm tổng không bao giờ tròn về không. Ba chi tiết này là nền cho toàn bộ chiến lược oracle phía sau.
3. Bút toán kép làm oracle: tiền bảo toàn, nợ bằng có
Đây là ý tưởng trung tâm của cả bài. Thay vì kiểm tra 'màn hình hiện chuyển khoản thành công', ta kiểm tra các bất biến kế toán mà mọi giao dịch đúng đắn phải thoả mãn. Bất biến thứ nhất: với mỗi transaction, tổng số tiền của các bút toán bằng không, tức tổng nợ bằng tổng có. Bất biến thứ hai: tổng số dư của toàn hệ thống trước và sau một chuyển khoản nội bộ không đổi, tức tiền được bảo toàn. Bất biến thứ ba: một yêu cầu gửi trùng chỉ tạo đúng một trạng thái cuối, tức tính idempotent. Ba bất biến này là oracle, và chúng đúng bất kể UI thay đổi thế nào.
// Oracle bút toán kép — dùng lại trong nhiều test regression
import { expect } from '@playwright/test';
// 1) Với mỗi transaction: Σ amount = 0 (nợ = có)
export async function assertTxnBalanced(db: DB, txnId: string) {
const rows = await db.query(
'SELECT COALESCE(SUM(amount),0) AS s FROM entries WHERE transaction_id=$1', [txnId]);
expect(Number(rows[0].s)).toBe(0); // KHÔNG assert "success text"
}
// 2) Tiền bảo toàn: tổng số dư hệ thống không đổi qua chuyển khoản nội bộ
export async function systemTotal(db: DB): Promise<number> {
const r = await db.query('SELECT COALESCE(SUM(amount),0) AS s FROM entries');
return Number(r[0].s); // với hệ đóng, tổng này là hằng số bất biến
}Điểm mạnh của oracle này là nó không phụ thuộc vào cách trình bày. Dù nút bấm đổi tên, dù thông báo đổi màu, dù luồng thêm một bước xác nhận, bất biến 'tổng bút toán bằng không' vẫn phải đúng. Đó là lý do oracle nghiệp vụ bền hơn nhiều so với assertion giao diện. Khi AI sinh ra hàng chục case regression, ta không lo chúng assert những thứ hời hợt, vì mọi case đều được neo vào cùng một bộ oracle kế toán do con người định nghĩa và không bao giờ được nới lỏng.
❓ Vì sao 'assert bút toán cân' tốt hơn 'assert thấy chữ Thành công'?
Vì chữ 'Thành công' có thể hiện ra ngay cả khi sổ cái đã hỏng: hệ thống có thể trả về UI thành công nhưng chỉ ghi một nửa bút toán do lỗi partial failure. Assertion giao diện sẽ xanh giả trong khi tiền đã lệch. Oracle bút toán kép truy thẳng vào sổ cái nên bắt được đúng lớp lỗi nguy hiểm nhất — nơi tiền bốc hơi mà UI vẫn tươi cười.
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!