Product Management
Đăng nhập
ESC

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

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

Test dấu tiếng Việt — diacritic input

Mở đầu — vì sao bài này quan trọng

Nếu bạn làm QA cho một sản phẩm phục vụ người dùng Việt Nam, thì việc kiểm thử dấu tiếng Việt (Vietnamese diacritics) không phải là chuyện "nice to have" — nó là một loại bug có xác suất xuất hiện cực cao và cực kỳ mất mặt khi lọt ra production. Tiếng Việt có 134 ký tự có dấu, trải rộng trên nhiều dấu phụ (accent marks) chồng lên nhau: dấu thanh (sắc, huyền, hỏi, ngã, nặng) cộng với dấu mũ, dấu móc, dấu trăng. Một chữ như "nghiêng" hay "khuếch" gói ghém rất nhiều thông tin Unicode. Chỉ cần một mắt xích trong chuỗi xử lý — trình duyệt, form, API, database, email, PDF — cấu hình sai encoding một chút là chữ "Nguyễn" biến thành "Nguyễn" hoặc "Nguy?n". Người dùng nhìn thấy tên mình bị vỡ, họ mất niềm tin ngay lập tức.

Đây là chủ đề riêng của bài này: cách thiết kế và tự động hóa test cho input tiếng Việt có dấu. Tôi sẽ không đi lại các kỹ thuật locator, wait, hay POM (những bài khác đã lo) — mà tập trung vào thứ đặc thù: dữ liệu test tiếng Việt, cách encoding có thể vỡ, và cách viết assertion để bắt được bug encoding trước khi khách hàng bắt được nó.

Khái niệm cốt lõi

Unicode và hai cách biểu diễn cùng một chữ

Điểm mấu chốt khiến test tiếng Việt khó hơn test tiếng Anh là Unicode Normalization. Cùng một chữ "ế" (e với dấu mũ và dấu sắc) có thể được lưu theo hai cách hoàn toàn khác nhau về byte:

  • NFC (Composed): một code point duy nhất U+1EBF. Đây là dạng "gọn".
  • NFD (Decomposed): chữ e (U+0065) + dấu mũ tổ hợp (U+0302) + dấu sắc tổ hợp (U+0301). Ba code point ghép lại.
Với mắt người, hai dạng này nhìn giống hệt nhau. Nhưng với máy tính, "ế".length có thể trả về 1 hoặc 3 tùy dạng. Phép so sánh chuoiA == chuoiB sẽ fail dù người dùng thấy hai chuỗi y chang. Đây là nguồn gốc của rất nhiều bug "tìm không ra dữ liệu dù rõ ràng có". macOS mặc định lưu tên file dạng NFD, còn hầu hết web dùng NFC — nên nếu tester dùng máy Mac copy-paste dữ liệu test, bạn có thể vô tình test bằng NFD mà không biết.

Bài học đầu tiên: khi so sánh chuỗi tiếng Việt trong automation, hãy normalize cả hai vế về cùng một dạng (thường là NFC) trước khi assert.

Chuỗi các mắt xích dễ vỡ encoding

Một ký tự tiếng Việt phải đi qua rất nhiều tầng, mỗi tầng là một điểm có thể hỏng:

  • Bàn phím / bộ gõ: Unikey, bộ gõ iOS/Android gõ theo kiểu Telex hoặc VNI, có thể sinh ra NFC hoặc NFD.
  • HTTP request: form phải gửi Content-Type: application/x-www-form-urlencoded; charset=UTF-8 hoặc JSON UTF-8. Nếu server đọc bằng Latin-1 (ISO-8859-1) là hỏng.
  • Application layer: PHP strlen() vs mb_strlen(), Java String (UTF-16), Python 3 (mặc định UTF-8 — an toàn hơn).
  • Database: MySQL/MariaDB phải là utf8mb4 với collation utf8mb4_unicode_ci. Nếu ai đó lỡ dùng charset latin1 hoặc utf8 (3-byte cũ), dữ liệu sẽ bị "mojibake".
  • Output: HTML meta charset, response header, email header (=?UTF-8?B?...?=), PDF font có chứa glyph tiếng Việt hay không.
Test dấu tiếng Việt về bản chất là kiểm tra tính toàn vẹn của chuỗi này từ đầu đến cuối (round-trip).

Bộ dữ liệu test "vàng" cho tiếng Việt

Bạn cần một tập dữ liệu (test corpus) bao phủ đủ các ca hiểm. Tôi khuyên luôn có sẵn các chuỗi sau trong file test data:

  • Đủ 5 dấu thanh: á à ả ã ạ
  • Nguyên âm phức tạp: ế ệ ỗ ữ ượ ườ (chồng nhiều dấu)
  • Chữ đặc trưng: đ Đ (thường bị nhầm với d)
  • Chữ có ơ ư ă â ê ô
  • Tên thật hay gặp: Nguyễn, Huỳnh, Phạm Thị Hoàng Oanh, Đặng Trường Giang
  • Chuỗi hỗn hợp có số và ký tự: Đường 3/2, Quận 10
  • Emoji + tiếng Việt (nếu app cho phép): Chào bạn 👋 nhé
  • Cả NFC và NFD của cùng một từ để test chủ động.

Tình huống thực tế

Tình huống 1: Sàn TMĐT và cái tên bị "?" trên vận đơn

Một công ty thương mại điện tử ở TP.HCM (gọi là ShopViet) chạy khuyến mãi lớn dịp Tết. Khách đặt hàng, nhập tên "Nguyễn Thị Bưởi", địa chỉ "Ngõ 42, phường Ngã Tư Sở". Trên website hiển thị đẹp, nhưng khi hệ thống xuất file CSV để đẩy sang đối tác vận chuyển (GHTK), tên khách biến thành "Nguy?n Th? B??i". Shipper không đọc được, gọi điện xác nhận từng đơn, làm chậm cả nghìn đơn hàng trong cao điểm.

Diễn giải: bug không nằm ở web (web dùng UTF-8) mà ở bước export CSV — thư viện xuất file mặc định dùng encoding của hệ điều hành server (Windows-1258), làm rớt ký tự Unicode. Bài học: điểm vỡ encoding thường nằm ở ranh giới giữa các hệ thống (web → file → đối tác), không phải trong lõi ứng dụng. Test dấu tiếng Việt phải bao gồm cả các luồng xuất dữ liệu (export, báo cáo, tích hợp bên thứ ba), chứ không chỉ màn hình nhập liệu.

Tình huống 2: App ngân hàng số và ô tìm kiếm "không ra kết quả"

Một ngân hàng số ở Đông Nam Á có tính năng tìm kiếm người thụ hưởng theo tên. QA test bằng cách gõ "Lê Văn Đức" vào ô tìm, không ra kết quả, dù tài khoản đó chắc chắn tồn tại. Dev khẳng định "code đúng mà". Sau khi điều tra, hóa ra tester dùng máy Mac, chuỗi họ gõ vào là NFD, còn dữ liệu trong DB lưu dạng NFC. Truy vấn WHERE name = ? so sánh byte-by-byte nên trượt.

Diễn giải: đây chính là cái bẫy NFC/NFD ở phần khái niệm. Bài học kép: (1) automation test phải chủ động sinh cả hai dạng và khẳng định hệ thống xử lý được cả hai (thường bằng cách normalize input về NFC ngay khi nhận request); (2) đừng vội tin "code đúng" — hãy in ra chuỗi byte thực tế (hex dump) để so sánh, đừng so bằng mắt.

Tình huống 3: Email xác nhận với tiêu đề "mojibake"

Một startup edtech gửi email "Chúc mừng bạn đã hoàn thành khóa học!" cho học viên. Nội dung email hiển thị tốt, nhưng tiêu đề (subject) email hiển thị thành Chúc mừng bạn... trên một số client như Outlook cũ. Lý do: phần body đã set UTF-8, nhưng header email cần mã hóa riêng theo chuẩn RFC 2047 (=?UTF-8?B?...?= — Base64 hoặc Quoted-Printable), và thư viện gửi mail không encode header.

Diễn giải: subject email là một điểm mù kinh điển. Bài học: khi test luồng thông báo có tiếng Việt (email, SMS, push notification), phải kiểm cả tiêu đề lẫn nội dung, và test trên nhiều client thực tế vì mỗi client parse header một kiểu.

Hướng dẫn từng bước

Dưới đây là quy trình để đưa test dấu tiếng Việt vào bộ automation của bạn.

Bước 1: Tạo file test data tập trung

Đừng rải chuỗi tiếng Việt lung tung trong code. Gom vào một file, ví dụ vietnamese_test_data.json:

{
  "full_names": ["Nguyễn Văn Đức", "Huỳnh Thị Mỹ Duyên", "Đặng Trường Giang"],
  "hard_diacritics": ["nghiêng", "khuếch", "ượu", "quặng"],
  "special_d": ["Đà Nẵng", "đường", "Đông Đô"],
  "addresses": ["Ngõ 42, phường Ngã Tư Sở", "Đường 3/2, Quận 10"],
  "mixed": ["Chào bạn 👋", "Giá: 1.500.000₫"]
}

Bước 2: Chuẩn hóa (normalize) trước khi so sánh

Trong assertion, luôn normalize cả giá trị mong đợi và giá trị thực tế về NFC.

Python (pytest):

import unicodedata

def nfc(s: str) -> str: return unicodedata.normalize("NFC", s)

def test_ten_hien_thi_dung(page): expected = "Nguyễn Văn Đức" page.fill("#fullname", expected) page.click("#save") actual = page.text_content("#profile-name") assert nfc(actual) == nfc(expected)

Java: java.text.Normalizer.normalize(s, Normalizer.Form.NFC). JavaScript (Cypress/Playwright): s.normalize("NFC").

Bước 3: Test round-trip qua toàn bộ chuỗi

Với mỗi chuỗi trong test data, kiểm tra: nhập vào → lưu → đọc lại → hiển thị, và assert chuỗi cuối bằng chuỗi đầu (sau normalize). Nếu có API, gọi thẳng API để tách biệt lỗi UI và lỗi backend:

import requests, unicodedata

def test_api_roundtrip_tieng_viet(): name = "Phạm Thị Hoàng Oanh" r = requests.post("https://api.example.vn/users", json={"name": name}) uid = r.json()["id"] got = requests.get(f"https://api.example.vn/users/{uid}").json()["name"] assert unicodedata.normalize("NFC", got) == unicodedata.normalize("NFC", name)

Bước 4: Kiểm tra ở tầng database

Chạy assertion trực tiếp trên DB để phát hiện mojibake sớm. Kiểm tra charset của cột:

SELECT CHARACTER_SET_NAME, COLLATION_NAME
FROM information_schema.COLUMNS
WHERE TABLE_NAME = 'users' AND COLUMN_NAME = 'name';
-- Phải là utf8mb4 / utf8mb4_unicode_ci

Và so sánh độ dài ký tự với độ dài byte để phát hiện lưu sai:

SELECT name, CHAR_LENGTH(name), LENGTH(name) FROM users WHERE id = 123;
-- CHAR_LENGTH đếm ký tự, LENGTH đếm byte. Với tiếng Việt LENGTH > CHAR_LENGTH là bình thường.
-- Nếu CHAR_LENGTH bằng số ký tự vỡ (nhiều hơn kỳ vọng) → đã bị double-encode.

Bước 5: Kiểm tra byte thực tế khi nghi ngờ

Khi test fail mà "nhìn thì giống", hãy in hex để nhìn tận byte:

print(name.encode("utf-8").hex())

So dạng NFC và NFD sẽ ra chuỗi byte khác nhau -> lộ ngay vấn đề

Bước 6: Đưa các test này vào bộ regression

Encoding bug hay tái xuất sau mỗi lần đổi thư viện, nâng cấp DB, hay thêm tích hợp mới. Gắn nhãn (tag) nhóm test này là @vietnamese để dễ chạy riêng và chạy chúng trong pipeline trên mỗi release.

Lỗi thường gặp & mẹo

  • So sánh chuỗi mà không normalize. Đây là lỗi số một. Luôn normalize("NFC") cả hai vế. Nếu không, test sẽ đỏ ngẫu nhiên tùy máy tester dùng Mac hay Windows.
  • Chỉ test trên UI, quên tầng dưới. Bug thường nằm ở export, email header, hoặc tích hợp bên thứ ba. Hãy phủ cả các luồng dữ liệu ra ngoài.
  • Dùng độ dài .length / strlen() để validate. Với tiếng Việt, đếm ký tự bằng đơn vị code unit sẽ sai. Trong PHP luôn dùng mb_strlen(); giới hạn ký tự (max length) phải đếm theo grapheme, không theo byte, nếu không "Đường" có thể bị coi là quá dài.
  • Test data bị IDE tự "sửa". Một số editor lưu file kèm BOM hoặc tự đổi encoding. Chốt file test data là UTF-8 không BOM và commit vào git để kiểm soát.
  • Copy-paste chuỗi từ nguồn không rõ. Chuỗi copy từ Word, PDF, hay macOS Finder có thể là NFD hoặc chứa ký tự vô hình (zero-width). Ưu tiên gõ trực tiếp hoặc sinh bằng code có kiểm soát.
  • Quên test ô tìm kiếm và sắp xếp. Không chỉ nhập/hiển thị — tìm kiếm "Đức" phải ra "Đức", và sắp xếp tên theo alphabet tiếng Việt (đ đứng sau d) là một ca riêng đáng test.
  • Mẹo: giữ một chuỗi "canary" siêu khó như "Đặng Thị Quỳnh Hương — nghiêng ưỡn" trong smoke test. Nếu chuỗi này đi qua toàn hệ thống mà còn nguyên vẹn, phần lớn ca encoding khác cũng an toàn.

Bài tập thực hành

  • Xây test data: Tạo file vietnamese_test_data.json với tối thiểu 15 chuỗi phủ đủ: 5 dấu thanh, chữ đ/Đ, nguyên âm chồng dấu (ế ữ ượ), tên người thật, địa chỉ có số, và một chuỗi có emoji.
  • Viết hàm normalize và test round-trip: Trên một form đăng ký bất kỳ (có thể dùng site demo), viết test tự động điền từng chuỗi, lưu, đọc lại và assert bằng chuỗi gốc sau khi normalize NFC. Ghi lại chuỗi nào fail.
  • Bẫy NFC/NFD: Với chuỗi "Đức", tạo phiên bản NFD bằng unicodedata.normalize("NFD", "Đức"), gửi vào ô tìm kiếm và khẳng định hệ thống vẫn tìm ra bản NFC trong DB. Nếu không ra, đó là bug cần báo.
  • Kiểm tra tầng DB: Viết truy vấn kiểm tra charset cột và so CHAR_LENGTH với LENGTH cho một bản ghi tiếng Việt. Giải thích vì sao hai số này khác nhau.
  • Điểm mù: Chọn một luồng "ra ngoài" (xuất CSV, gửi email, hoặc tạo PDF) và kiểm tra chuỗi "Nguyễn Thị Bưởi" có còn nguyên vẹn không. Nếu vỡ, xác định mắt xích nào làm vỡ.

Tóm tắt

Test dấu tiếng Việt là bài kiểm tra tính toàn vẹn của chuỗi ký tự qua mọi tầng: bàn phím, HTTP, ứng dụng, database, và mọi cổng ra như email, CSV, PDF. Ba ý cần khắc cốt: (1) NFC vs NFD — luôn normalize trước khi so sánh, vì hai chuỗi nhìn giống nhau có thể khác byte; (2) bug encoding thường nằm ở ranh giới giữa các hệ thống và ở các điểm mù như subject email hay file export, chứ không phải trong lõi; (3) hãy có một bộ test data tiếng Việt chuẩn, đưa vào regression với tag riêng, và khi nghi ngờ thì soi tận byte bằng hex thay vì tin vào mắt. Làm được ba điều này, bạn sẽ chặn được loại bug vừa phổ biến vừa mất mặt nhất với người dùng Việt Nam trước khi nó kịp ra production.

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