Mở đầu — vì sao bài này quan trọng
Nếu bạn là QA cho một app phục vụ người dùng Việt Nam, thì "test tiếng Việt" không phải là một checklist phụ để làm cho vui — nó là một trong những nhóm lỗi âm thầm gây thiệt hại lớn nhất mà đội dev thường bỏ sót. Lý do rất đơn giản: hầu hết lập trình viên viết code và test bằng tiếng Anh, họ gõ "hello", "test123", "abc@gmail.com". Không ai trong quá trình phát triển vô tình gõ "Nguyễn Thị Hoàng Anh" hay "phường Bến Nghé, Quận 1" — cho đến khi 90% người dùng thật gõ đúng những chuỗi đó và app bắt đầu vỡ.
Tiếng Việt là một trong những ngôn ngữ khắc nghiệt nhất đối với phần mềm vì nó chồng nhiều lớp dấu lên một nguyên âm: dấu thanh (sắc, huyền, hỏi, ngã, nặng), dấu mũ/móc tạo nguyên âm phái sinh (â, ê, ô, ơ, ư), và ký tự đặc biệt đ/Đ. Một chữ như "ệ" thực chất là "e" + dấu mũ + dấu nặng chồng lên nhau. Khi encoding sai một mắt xích, hậu quả lan ra khắp hệ thống: tên bị hiển thị thành "Nguyá»…n", tìm kiếm không ra kết quả, giới hạn ký tự tính sai làm cắt mất tên người dùng, và tệ nhất là dữ liệu bị hỏng vĩnh viễn trong database.
Trên mobile, vấn đề còn nhân đôi. Ngoài encoding, bạn phải test bàn phím Telex và VNI — hai kiểu gõ tiếng Việt phổ biến nhất — nơi người dùng gõ nhiều phím để tạo ra một ký tự. Cách iOS và Android xử lý composition (ghép ký tự) khác nhau, và đây là mảnh đất màu mỡ sinh ra bug mà chỉ QA hiểu tiếng Việt mới bắt được. Bài này sẽ trang bị cho bạn tư duy và bộ test cụ thể để không một lỗi tiếng Việt nào lọt qua tay bạn.
Khái niệm cốt lõi
Unicode, code point và encoding
Máy tính không lưu chữ "ệ", nó lưu các con số (code point) theo chuẩn Unicode, rồi mã hóa (encode) các số đó thành byte để lưu trữ và truyền đi. Chuẩn mã hóa gần như bắt buộc ngày nay là UTF-8. Nếu bất kỳ tầng nào trong app — client, API, database, file — dùng encoding khác (như Latin-1, Windows-1252), tiếng Việt sẽ hỏng.
Dấu hiệu kinh điển của lỗi encoding:
- Mojibake: "Tiếng Việt" hiển thị thành "Tiếng Việt" (UTF-8 bị đọc nhầm là Latin-1).
- Dấu hỏi hoặc ô vuông: "Ti?ng Vi?t" hoặc "Ti□ng Vi□t" (ký tự không map được về charset đích).
- Cụt chuỗi: tên bị cắt ngang giữa ký tự có dấu.
NFC vs NFD — Unicode Normalization, thủ phạm số một
Đây là khái niệm quan trọng nhất của cả bài, hãy đọc kỹ. Cùng một chữ "ệ" có thể được lưu bằng hai cách khác nhau trong Unicode:
- NFC (Normalization Form Composed): lưu "ệ" như MỘT code point duy nhất (U+1EC7). Đây là dạng "gộp".
- NFD (Normalization Form Decomposed): lưu "ệ" như "e" + dấu mũ (U+0302) + dấu nặng (U+0323), tức là NHIỀU code point ghép lại. Đây là dạng "tách".
- Người dùng iPhone gõ tên "Lê" và lưu vào DB. iOS có thói quen tạo chuỗi dạng NFD.
- Sau đó họ tìm kiếm "Lê" trên web (thường tạo dạng NFC).
- Hai chuỗi trông y hệt nhưng byte khác nhau → tìm không ra, dù bản ghi rõ ràng tồn tại.
length <= 2 để validate, nó sẽ từ chối oan chuỗi NFD.Quy tắc vàng: chuẩn hóa toàn bộ input về NFC ngay tại cửa ngõ (API hoặc lớp lưu trữ) trước khi so sánh, lưu hay đếm. QA phải kiểm tra chính sách này có được thực thi không.
Telex và VNI — cách người Việt thật sự gõ
Người Việt không có phím "ế" trên bàn phím. Họ dùng bộ gõ:
- Telex: gõ "ee" ra "ê", "aa" ra "â", "dd" ra "đ", "s" thêm dấu sắc (as → á), "f" dấu huyền, "r" dấu hỏi, "x" dấu ngã, "j" dấu nặng. Ví dụ "Tieesng Vieejt" → "Tiếng Việt".
- VNI: dùng số. "a1" → á, "a8" → ă, "d9" → đ. Ví dụ "Tie61ng Vie65t".
onKeyPress, autocomplete, đếm ký tự real-time, hoặc chặn ký tự sẽ hành xử sai trong lúc composition. QA phải test bằng cách gõ THẬT với bộ gõ, không chỉ paste kết quả cuối.Nơi tiếng Việt hay vỡ nhất
- Search & filter: NFC/NFD mismatch, và câu hỏi "tìm 'Le' có ra 'Lê' không?" (search bỏ dấu).
- Character limit / validation: đếm nhầm code point vs grapheme.
- Uppercase/sort: "đ" viết hoa thành "Đ" nhiều thư viện làm sai; sắp xếp abc tiếng Việt khác chuẩn ASCII.
- Truncation hiển thị: cắt "..." giữa ký tự có dấu gây vỡ glyph.
- URL, filename, QR, SMS: nơi encoding dễ bị ép về ASCII.
Tình huống thực tế
Tình huống 1: App gọi xe "bốc hơi" khách hàng vì NFD trên iOS
Một startup gọi xe tại TP.HCM (gọi là "GoRide") gặp phốt: đội chăm sóc khách hàng báo rằng khi search tên khách để hỗ trợ, khoảng 8% khách "không tồn tại" trong hệ thống, dù họ rõ ràng đã đặt chuyến. Dev ban đầu nghi ngờ lỗi database.
QA đào sâu và phát hiện: tất cả các khách "biến mất" đều đăng ký qua app iOS và có dấu trong tên (Nguyễn, Trần Lê, Đỗ...). Nguyên nhân: iOS lưu tên ở dạng NFD, backend lưu nguyên xi. Nhưng ô search của tổng đài chạy trên web, tạo query dạng NFC. So sánh chuỗi thất bại vì byte khác nhau. Khách Android không bị vì Android thường tạo NFC.
Cách QA tái hiện: cài app trên iPhone thật, đăng ký tên "Lê Ngọc Hà", rồi dùng công cụ kiểm tra byte (hoặc log unicodedata.normalize) để chứng minh chuỗi lưu là NFD. Bài học: luôn test cùng một dữ liệu tiếng Việt trên cả iOS và Android, và yêu cầu dev normalize NFC ở backend. Fix mất 2 dòng code, nhưng nếu không có QA hiểu Unicode thì bug này ẩn cả năm.
Tình huống 2: Ngân hàng số cắt cụt tên và gây lỗi định danh eKYC
Một ngân hàng số ở Việt Nam triển khai eKYC (định danh điện tử). Người dùng nhập họ tên theo CCCD, hệ thống đối chiếu với dữ liệu quốc gia. QA phát hiện tỷ lệ fail bất thường ở những tên dài có nhiều dấu như "Nguyễn Hoàng Phương Uyên".
Điều tra ra hai lỗi chồng nhau. Thứ nhất, field giới hạn 30 ký tự nhưng đếm bằng độ dài NFD (mỗi ký tự có dấu tính 2-3 đơn vị), nên tên hợp lệ bị chặn oan. Thứ hai, hệ thống đối chiếu so sánh chuỗi mà không normalize, nên "Uyên" (NFD từ app) không khớp "Uyên" (NFC từ cơ sở dữ liệu quốc gia) → báo "thông tin không trùng khớp". Người dùng bị từ chối định danh dù nhập đúng.
Bài học: với các luồng nhạy cảm như thanh toán, eKYC, hợp đồng điện tử, một lỗi tiếng Việt không chỉ khó chịu mà còn chặn doanh thu và gây rủi ro pháp lý. QA phải đưa "chuỗi tiếng Việt dài, nhiều dấu, có đ/Đ" vào bộ test bắt buộc cho mọi field nhập tên.
Tình huống 3: Sàn TMĐT tìm kiếm sản phẩm ra kết quả rỗng
Một sàn thương mại điện tử (giả định "ChợViet") nhận phản ánh: gõ "áo dài" ra sản phẩm, nhưng gõ "ao dai" (không dấu — cách gõ nhanh phổ biến của người dùng mobile khi lười bật bộ gõ) lại ra 0 kết quả. Đối thủ của họ thì search kiểu nào cũng ra.
QA xác nhận đây không phải bug encoding mà là thiếu tính năng search bỏ dấu (diacritic-insensitive search) — một kỳ vọng thực tế của người dùng Việt. Họ đề xuất test case: "search không dấu phải trả về kết quả có dấu tương ứng", và mở rộng thành ma trận: có dấu → có dấu, không dấu → có dấu, viết hoa → viết thường, NFC → NFD. Sau khi backend thêm bước normalize + strip dấu khi index, tỷ lệ search rỗng giảm rõ rệt. Bài học: QA không chỉ bắt lỗi "sai", mà còn phát hiện "thiếu" so với kỳ vọng của người dùng bản địa.
Hướng dẫn từng bước
Đây là quy trình test tiếng Việt bạn có thể áp dụng cho bất kỳ field/màn hình nào.
Bước 1 — Chuẩn bị bộ dữ liệu vàng (golden strings). Luôn có sẵn vài chuỗi test kinh điển:
- Đủ 5 dấu thanh: "á à ả ã ạ".
- Nguyên âm phái sinh + đ: "ăâêôơư đĐ".
- Tên thật khó: "Nguyễn Thị Hoàng Anh", "Đỗ Quỳnh Nhã".
- Chuỗi cực đoan: "ệ" đơn lẻ, chuỗi dài 50 ký tự toàn dấu, emoji + tiếng Việt lẫn lộn.
Bước 3 — Test cả iOS và Android với CÙNG dữ liệu. Đây là bước bắt buộc để bắt lỗi NFC/NFD. Nhập cùng tên "Lê" trên cả hai nền tảng, kiểm tra chuỗi lưu vào DB có giống nhau không.
Bước 4 — Kiểm tra vòng đời dữ liệu end-to-end. Với một chuỗi tiếng Việt, đi theo nó qua từng tầng: nhập → lưu DB → đọc lại hiển thị → search → xuất ra (email, PDF, filename, API response). Encoding phải "sống sót" qua mọi tầng.
Bước 5 — Kiểm tra normalization. Nhờ dev/backend log ra dạng chuẩn hóa, hoặc dùng công cụ. Trong Python: import unicodedata; unicodedata.normalize('NFC', s) == s để xác nhận chuỗi đã ở NFC. Trong Appium/UIAutomator bạn có thể lấy giá trị text về rồi assert.
Bước 6 — Kiểm tra các phép biến đổi. Uppercase ("đỗ" → "ĐỖ"), sort danh sách tên tiếng Việt, truncate hiển thị (đảm bảo không cắt giữa ký tự), search bỏ dấu.
Bước 7 — Test biên nhạy cảm. Field có giới hạn ký tự: nhập đúng ngưỡng bằng chuỗi nhiều dấu, xem có bị đếm sai không.
Lỗi thường gặp & mẹo
- Chỉ test bằng paste, không gõ thật. Bỏ lỡ toàn bộ lớp bug IME composition. Mẹo: luôn có ít nhất một lượt gõ tay bằng Telex trên thiết bị thật.
- Quên test iOS lẫn Android. NFC/NFD mismatch chỉ lộ ra khi so cả hai. Đừng tin "test trên Android xong là đủ".
- Nhầm lỗi hiển thị với lỗi lưu trữ. Đôi khi DB lưu đúng nhưng font/hiển thị sai, hoặc ngược lại. Mẹo: luôn kiểm tra ở tầng byte (log, DB query) chứ đừng chỉ tin con mắt.
- Dùng
str.lengthđể đếm ký tự. Nhiều ngôn ngữ đếm code unit chứ không đếm grapheme, nên chuỗi NFD bị đếm dài hơn. Mẹo: khi test giới hạn ký tự, dùng chuỗi nhiều dấu để phơi bày cách đếm. - Bỏ qua đ/Đ và ơ/ư. Đây là các ký tự đặc thù tiếng Việt hay bị thư viện quốc tế xử lý uppercase/sort sai. Luôn đưa vào golden string.
- Quên các "ống dẫn" ép ASCII: URL slug, tên file khi upload ảnh, nội dung SMS OTP, QR code. Tiếng Việt thường vỡ nhất ở những chỗ này. Mẹo: test upload file tên "Đơn hàng số 5.pdf", tạo slug từ tiêu đề tiếng Việt.
- Giả định "một chuẩn hóa là xong". Normalize phải nhất quán ở mọi điểm so sánh, không chỉ lúc lưu. Mẹo: hỏi dev "normalize ở đâu?" và test cả đường search, không chỉ đường lưu.
Bài tập thực hành
- Săn NFC/NFD. Trên một app có sẵn (ví dụ app ghi chú hoặc form đăng ký), dùng iPhone gõ tên "Lê Đỗ", lưu lại, rồi tìm kiếm chính tên đó gõ lại từ đầu. Ghi lại: tìm có ra không? Nếu app có API, bắt request và kiểm tra byte chuỗi gửi lên có phải NFC không.
- Viết ma trận test search. Cho một ô tìm kiếm sản phẩm/danh bạ, lập bảng 4 cột: input có dấu, input không dấu, input viết hoa, input NFD. Với mỗi loại, ghi kết quả kỳ vọng và kết quả thực tế. Đánh dấu các ô fail.
- Test giới hạn ký tự. Tìm một field có max length (ví dụ tên hiển thị 30 ký tự). Nhập "Nguyễn Hoàng Phương Uyên Anh" (nhiều dấu) và một chuỗi tiếng Anh cùng độ dài hiển thị. So sánh: hai chuỗi có bị chặn ở cùng điểm không? Kết luận app đếm theo code point, code unit hay grapheme.
- Kiểm tra vòng đời dữ liệu. Chọn một chuỗi "Đặng Thị Mỹ Duyên", đi theo nó: nhập → hiển thị lại trên profile → xuất trong email xác nhận → tên file nếu có export. Ghi lại tầng nào làm hỏng dấu (nếu có).
- Bonus — Telex composition. Gõ tay bằng Telex chuỗi "Tieesng Vieejt" vào một field có đếm ký tự real-time hoặc autocomplete. Quan sát trong lúc gõ (chưa chốt ký tự) có hành vi lạ nào không: nhảy con trỏ, đếm sai, gợi ý sai.
Tóm tắt
Test tiếng Việt là một kỹ năng "vũ khí bí mật" của QA người Việt mà QA nước ngoài khó có được. Điểm cốt lõi cần nhớ:
- Tiếng Việt chồng nhiều lớp dấu, nên rất nhạy với encoding — luôn đảm bảo UTF-8 xuyên suốt và nhận diện được mojibake.
- NFC vs NFD là thủ phạm số một: hai chuỗi trông giống nhau nhưng byte khác nhau, gây tìm-không-ra và đếm-sai. iOS hay tạo NFD, Android hay tạo NFC — luôn test cả hai và yêu cầu backend normalize về NFC.
- Người Việt gõ bằng Telex/VNI, nên phải test bằng cách gõ thật để bắt lỗi IME composition, không chỉ paste.
- Đưa golden strings (đủ dấu thanh, ă â ê ô ơ ư, đ/Đ, tên thật dài) vào mọi field nhập liệu, và kiểm tra chuỗi qua toàn bộ vòng đời: nhập → lưu → hiển thị → search → xuất.
- Đừng quên search bỏ dấu, uppercase đ/Đ, sort tiếng Việt, và các ống dẫn ép ASCII như URL/filename/SMS.