Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 19 — Vietnamese Diacritics & Input Testing

Đã đọc 0/14 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

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".
Hai chuỗi này nhìn trên màn hình giống hệt nhau — mắt người không phân biệt được — nhưng với máy tính chúng là hai chuỗi byte hoàn toàn khác. Đây là lý do gây ra loại bug ám ảnh nhất:

  • 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.
Ngoài ra, độ dài chuỗi cũng khác: "Lê" ở dạng NFC có 2 ký tự, ở dạng NFD có 3 (vì "ê" tách thành e + dấu). Nếu code kiểm tra 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".
Điều này tạo ra hiện tượng IME composition (Input Method Editor): trong lúc gõ, app nhận một chuỗi các ký tự trung gian rồi mới "chốt" thành ký tự cuối. Nhiều field có xử lý 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 2 — Gõ thật bằng bộ gõ, không chỉ paste. Bật bàn phím tiếng Việt Telex trên thiết bị thật (iOS: Settings → Keyboard → thêm Vietnamese; Android: Gboard bật Vietnamese Telex). Gõ trực tiếp để kích hoạt luồng IME composition. Sau đó test lại bằng cách paste để so sánh hành vi.

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.
Khi bạn biến những điểm trên thành thói quen, bạn sẽ bắt được nhóm bug mà cả đội dev không ai thấy — và đó chính là giá trị không thể thay thế của một Mobile QA hiểu người dùng Việt.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi