Product Management
Đăng nhập
ESC

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

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

Bài 18 — Offline Mode Testing

Đã đọc 0/14 mục · còn khoảng 13 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

Hãy tưởng tượng bạn đang là một QA tại một ngân hàng số ở Việt Nam. Một sáng thứ Hai, bộ phận chăm sóc khách hàng chuyển lên một loạt phàn nàn: "App báo chuyển tiền thất bại nhưng tài khoản vẫn bị trừ tiền", "Tôi mở app dưới hầm gửi xe thì app trắng màn hình, phải khởi động lại", "Đang xem lịch sử giao dịch thì mất mạng, app văng ra ngoài". Điểm chung của tất cả những bug này là gì? Chúng xảy ra khi thiết bị không có kết nối mạng ổn định — và không ai trong đội test đã kiểm thử nghiêm túc kịch bản đó.

Ở các nước phát triển với 5G phủ khắp, người ta dễ lầm tưởng "lúc nào cũng có mạng". Nhưng bối cảnh Việt Nam thì khác hẳn. Người dùng của bạn mở app trong hầm gửi xe chung cư, trên tuyến Metro số 1 (Bến Thành – Suối Tiên) khi tàu chui vào đoạn ngầm, ở vùng nông thôn Đồng Tháp sóng 3G chập chờn, trong thang máy tòa nhà văn phòng, hay trên máy bay ở chế độ máy bay. Trong tất cả những khoảnh khắc đó, app của bạn phải hoạt động một cách có phẩm giá — chứ không phải trắng màn hình, treo, hay tệ hơn là làm hỏng dữ liệu.

Offline Mode Testing là nhánh kiểm thử tập trung vào cách app cư xử khi mất kết nối hoặc kết nối gián đoạn: nó hiển thị gì, lưu trữ gì cục bộ, đồng bộ lại ra sao khi có mạng trở lại, và quan trọng nhất — nó có làm mất hay làm sai dữ liệu của người dùng không. Đây là bài học tách biệt hoàn toàn với việc mô phỏng điều kiện mạng chậm (Bài 17) hay đồng bộ tác vụ nền (Bài 43). Ở đây ta quan tâm đến trạng thái offline hoàn toàn hoặc bán offline, và toàn bộ vòng đời dữ liệu quanh nó.

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

Offline-capable là gì, và không phải app nào cũng cần

Không phải app nào cũng cần offline mode đầy đủ. Việc đầu tiên của một QA giỏi là hiểu app này thuộc loại nào, để biết kỳ vọng đúng khi test.

  • App bắt buộc phải offline-first: ứng dụng ghi chú (Notes, Notion), todo/productivity, đọc sách (Kindle), nghe nhạc/podcast tải sẵn (Spotify, Zing MP3), đọc tin đã tải (báo VnExpress cache), email (Gmail đọc thư đã tải, soạn thư offline). Với nhóm này, người dùng kỳ vọng dùng được ngay cả khi không có mạng.
  • App cần offline một phần (graceful degradation): ngân hàng, ví điện tử (MoMo, ZaloPay), thương mại điện tử (Shopee, Tiki). Nhóm này không thể chuyển tiền offline, nhưng phải hiển thị dữ liệu đã cache (số dư gần nhất, lịch sử giao dịch, giỏ hàng) và thông báo rõ ràng khi thao tác cần mạng.
  • App gần như online-only: livestream, gọi video, đặt xe real-time (Grab, Be). Nhóm này chỉ cần báo lỗi tử tế và phục hồi mượt khi có mạng lại.
Xác định đúng nhóm giúp bạn không viết test sai kỳ vọng — ví dụ đừng bắt app đặt xe phải "đặt xe offline", nhưng phải bắt nó đừng crash khi mất sóng giữa chừng.

Ba trạng thái mạng cần phân biệt

Nhiều QA mới chỉ test hai trạng thái "có mạng / không mạng". Thực tế có ba trạng thái, và trạng thái thứ ba mới là nơi bug ẩn nấp:

  • Online: kết nối tốt, request đi và về bình thường.
  • Offline hoàn toàn: bật chế độ máy bay, hoặc không có sóng. Hệ điều hành báo ngay no network.
  • Lie-Fi (kết nối "dối trá"): có sóng, có IP, nhưng thực tế request timeout hoặc rớt giữa chừng. Đây là trạng thái thang máy/hầm gửi xe — điện thoại tưởng mình có mạng (biểu tượng WiFi/4G vẫn sáng) nên vẫn cố gửi request, request treo 30-60 giây rồi mới fail. Đây là kẻ giết chết trải nghiệm nhiều nhất.

Ba trụ cột kiểm thử offline

Mọi test case offline đều xoay quanh ba câu hỏi:

  • Reading (Đọc): khi offline, app hiển thị được dữ liệu đã cache không? Có báo rõ "đây là dữ liệu cũ, cập nhật lúc HH:mm" không?
  • Writing (Ghi): khi offline, người dùng tạo/sửa/xóa dữ liệu — app có cho phép và xếp hàng chờ (queue) thao tác đó không? Hay chặn thẳng?
  • Syncing (Đồng bộ): khi mạng trở lại, các thao tác trong hàng chờ có tự động gửi lên server đúng thứ tự không? Có xử lý xung đột (conflict) khi cùng bản ghi bị sửa ở hai nơi không? Có bị gửi trùng (duplicate) không?

Optimistic UI và cạm bẫy của nó

Nhiều app hiện đại dùng optimistic UI: khi bạn nhấn "thích" một bài viết, giao diện hiện ngay trái tim đỏ trước khi server xác nhận, để cảm giác nhanh. Điều này tuyệt vời khi online, nhưng khi offline nó trở thành cái bẫy dữ liệu: UI báo "thành công" nhưng thực chất thao tác chưa lên server. QA phải kiểm tra: nếu thao tác cuối cùng fail khi sync, app có rollback (hoàn lại trạng thái) và báo cho người dùng không? Hay để lại một lời nói dối trên màn hình?

Tình huống thực tế

Tình huống 1 — App ngân hàng và "giao dịch ma" dưới hầm gửi xe

Một ngân hàng số tại TP.HCM (giả định là "VBank") nhận báo cáo: khách chuyển khoản dưới hầm gửi xe chung cư, app quay vòng tròn rồi báo "Giao dịch thất bại, vui lòng thử lại". Khách nhấn thử lại 2-3 lần. Kết quả: người nhận nhận được ba lần tiền.

Nguyên nhân kỹ thuật: dưới hầm là trạng thái Lie-Fi. Request chuyển tiền đã lên tới server và được xử lý thành công, nhưng response trả về bị rớt do mất sóng giữa chừng. App phía client tưởng thất bại nên cho phép nhấn lại — mỗi lần lại tạo một giao dịch mới. Đây là lỗi kinh điển của việc thiếu idempotency key (khóa chống trùng lặp).

Bài học rút ra: với mọi thao tác có tác dụng phụ (chuyển tiền, đặt hàng, thanh toán), QA phải test kịch bản "gửi request → cắt mạng ngay khi request đang bay → khôi phục mạng → thử lại". Kỳ vọng đúng: app phải gửi kèm một idempotency-key để server nhận ra "đây là cùng một giao dịch" và không xử lý lần hai. Test case này đáng giá hàng tỷ đồng và phải nằm trong bộ regression bắt buộc.

Tình huống 2 — App ghi chú và cuộc chiến "last write wins"

Một startup làm app ghi chú cho sinh viên (giả định "NoteVN") quảng cáo "soạn ghi chú mọi lúc, kể cả offline". Một bạn sinh viên soạn bài trên tàu Metro (đoạn ngầm, mất mạng), sửa một ghi chú dài 500 chữ. Cùng lúc, trên máy tính ở nhà, bạn ấy cũng mở đúng ghi chú đó và sửa vài dòng rồi lưu (máy tính có mạng). Khi tàu ra khỏi hầm, điện thoại đồng bộ — và toàn bộ bản sửa trên máy tính biến mất, bị ghi đè bởi bản offline của điện thoại.

Nguyên nhân: hệ thống dùng chiến lược ngây thơ "last write wins" (bản ghi cuối thắng) mà không phát hiện xung đột. Bản offline khi đồng bộ lại mang timestamp "mới hơn" nên đè lên tất cả.

Bài học rút ra: QA phải thiết kế conflict test — sửa cùng một bản ghi ở hai thiết bị, một online một offline, rồi cho đồng bộ. Kỳ vọng tối thiểu: app không được âm thầm mất dữ liệu. Cách xử lý tốt là tạo bản sao xung đột ("Ghi chú (bản xung đột)") hoặc merge theo trường. Đây là loại bug mà chỉ test offline có chủ đích mới lộ ra.

Tình huống 3 — App đọc báo và cache khiến người dùng đọc tin cũ

Một tòa soạn báo lớn (giả định "TinNhanh") cho phép đọc offline các bài đã tải. Nhưng có sự cố: một bài đính chính thông tin sai được cập nhật trên server, thế nhưng người dùng offline vẫn đọc bản cache cũ suốt 3 ngày vì app không có cơ chế báo "nội dung đã có bản mới" và không hiển thị mốc thời gian cache.

Bài học rút ra: cache offline là con dao hai lưỡi. QA cần kiểm tra hai điều: (1) app có hiển thị mốc thời gian "Cập nhật lần cuối: 08:15 hôm nay" để người dùng biết dữ liệu cũ tới đâu; (2) khi có mạng lại, app có tự làm mới (refresh) dữ liệu quan trọng không, hay chờ người dùng kéo-để-làm-mới. Với nội dung nhạy cảm (số dư, giá, tin tức đính chính), cache im lặng là rủi ro nghiêm trọng.

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

Dưới đây là quy trình test offline mode một cách có hệ thống mà bạn có thể áp dụng ngay cho bất kỳ app nào.

Bước 1 — Lập bản đồ tính năng theo trạng thái mạng. Trước khi test, hãy lập bảng: cột là các tính năng chính (xem số dư, chuyển tiền, xem lịch sử, cập nhật hồ sơ...), hàng là ba trạng thái (Online / Offline / Lie-Fi). Trong mỗi ô, ghi kỳ vọng đúng. Bảng này chính là bộ test case của bạn và là công cụ để tranh luận với dev/PO về hành vi mong muốn.

Bước 2 — Chuẩn bị công cụ mô phỏng mạng. Bạn cần nhiều hơn nút "chế độ máy bay":

  • Chế độ máy bay (Airplane Mode): mô phỏng offline hoàn toàn — đơn giản, luôn nên test.
  • Tắt WiFi/di động riêng lẻ để test chuyển đổi giữa hai mạng.
  • Trên iOS: dùng Network Link Conditioner (trong Developer settings) để tạo profile "100% Loss" mô phỏng Lie-Fi.
  • Trên Android: dùng lệnh adb hoặc bật/tắt data qua Developer Options.
  • Dùng proxy Charles/Proxyman với tính năng "Throttle" và "Fail Requests" để cắt request giữa chừng — đây là cách chính xác nhất để tạo Lie-Fi.
  • Với thiết bị thật: đi thang máy, xuống hầm xe — không gì thay thế được test thực địa cho app quan trọng.
Bước 3 — Test luồng Đọc (Read). Mở app khi ONLINE, để dữ liệu tải và cache. Sau đó bật chế độ máy bay và điều hướng qua các màn hình. Kiểm tra: dữ liệu cache có hiển thị không? Màn hình chưa từng mở (chưa cache) hiển thị gì — thông báo lỗi tử tế hay màn trắng/spinner vô tận? Có mốc thời gian dữ liệu cũ không?

Bước 4 — Test luồng Ghi (Write) khi offline. Ở trạng thái offline, thực hiện thao tác tạo/sửa/xóa. Kiểm tra: app cho phép và xếp hàng chờ, hay chặn? Nếu xếp hàng, có chỉ báo trạng thái ("Đang chờ đồng bộ", biểu tượng đồng hồ) không? Người dùng có hiểu thao tác chưa hoàn tất không?

Bước 5 — Test luồng Đồng bộ (Sync). Đây là bước quan trọng nhất. Sau khi tạo vài thao tác offline, bật lại mạng và quan sát: các thao tác có tự sync không, hay phải mở lại app? Thứ tự có đúng không (tạo trước, sửa sau)? Có bị gửi trùng không (kiểm tra ở phía server/DB)? Nếu một thao tác fail, các thao tác sau có bị kẹt theo không?

Bước 6 — Test chuyển trạng thái đột ngột (transition testing). Đây là nơi ẩn nhiều bug nhất. Cắt mạng ngay giữa lúc một request đang bay. Đổi từ WiFi sang 4G giữa lúc upload ảnh. Đóng app (kill) khi đang có thao tác trong hàng chờ, mở lại — hàng chờ còn nguyên không? Xoay điện thoại/khóa màn hình khi đang sync.

Bước 7 — Test độ bền dữ liệu (persistence). Tạo thao tác offline → force-kill app → mở lại. Dữ liệu chờ sync phải còn nguyên, không được bốc hơi. Đây là kiểm tra bộ nhớ cục bộ có thực sự ghi xuống đĩa (SQLite/Realm) hay chỉ giữ trong RAM.

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

Chỉ test "on/off" mà bỏ qua Lie-Fi. Đây là sai lầm phổ biến nhất. Bật/tắt chế độ máy bay thì hệ điều hành báo lỗi ngay, mọi thứ có vẻ ổn. Nhưng bug thật nằm ở Lie-Fi — request treo rồi timeout. Mẹo: luôn có ít nhất một profile "100% packet loss nhưng vẫn có kết nối" trong bộ test.

Quên test khi app bị kill. Nhiều đội test transition ngay trong lúc app đang chạy nhưng quên rằng người dùng thật hay tắt app hoặc bị OS thu hồi bộ nhớ. Mẹo: đưa "force-kill giữa lúc offline" vào checklist bắt buộc cho mọi app có ghi dữ liệu.

Không kiểm tra phía server khi test duplicate. Bạn thấy UI báo "thành công một lần" nhưng thực chất server nhận ba request. Mẹo: khi test thao tác quan trọng, luôn đối chiếu với log server hoặc database, đừng chỉ tin UI.

Bỏ qua chỉ báo trạng thái cho người dùng. App làm đúng về kỹ thuật (xếp hàng, sync đúng) nhưng không cho người dùng biết → người dùng hoang mang, nhấn đi nhấn lại. Mẹo: bug về "thiếu feedback trạng thái offline" cũng là bug — hãy log nó, đừng bỏ qua vì "chức năng vẫn chạy".

Cache không bao giờ hết hạn. App cache dữ liệu mãi mãi, hiển thị số dư từ tuần trước như thể mới. Mẹo: hỏi dev về chính sách TTL (time-to-live) của cache và test rằng dữ liệu quá cũ được đánh dấu hoặc làm mới.

Mẹo vàng — viết test kèm "kỳ vọng offline" cho mọi test case online. Thay vì làm offline test như một đợt riêng, hãy tập thói quen: mỗi khi viết test case cho một tính năng, tự hỏi "nếu offline thì sao?". Điều này biến offline testing thành phản xạ chứ không phải một checklist bị lãng quên.

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

  • Lập ma trận trạng thái mạng. Chọn một app bạn hay dùng (MoMo, VnExpress, Zalo, hoặc app ngân hàng của bạn). Chọn 5 tính năng chính và lập bảng 5 hàng × 3 cột (Online / Offline / Lie-Fi), điền kỳ vọng đúng vào từng ô. Đánh dấu ô nào bạn nghi ngờ app xử lý sai.
  • Săn bug Lie-Fi. Cài Charles hoặc Proxyman (bản dùng thử miễn phí), bật tính năng Throttle/Fail request. Chọn một app, thực hiện một thao tác ghi (like, gửi bình luận, thêm giỏ hàng) trong lúc cắt request giữa chừng. Ghi lại: app có báo lỗi tử tế không? Có gửi trùng khi bạn thử lại không? Viết một bug report chuẩn nếu tìm thấy vấn đề.
  • Kiểm tra độ bền hàng chờ. Với một app cho phép soạn nội dung offline (ghi chú, email, hoặc app tương tự), bật chế độ máy bay, tạo 3 mục mới, force-kill app, mở lại và kiểm tra 3 mục còn không. Sau đó bật mạng và xác nhận cả 3 sync đúng, không trùng, đúng thứ tự.
  • Thiết kế conflict test. Viết ra các bước chi tiết (bằng lời) để kiểm thử xung đột dữ liệu: cùng một bản ghi bị sửa ở hai nơi (một online, một offline). Nêu rõ ít nhất hai hành vi có thể chấp nhận được và một hành vi không chấp nhận được (mất dữ liệu âm thầm).

Tóm tắt

Offline Mode Testing không phải chuyện phụ mà là trải nghiệm cốt lõi trong bối cảnh Việt Nam, nơi hầm gửi xe, tàu Metro, thang máy và vùng sóng yếu là hiện thực hằng ngày của người dùng. Điểm mấu chốt cần nhớ:

  • Xác định đúng app thuộc nhóm nào (offline-first, degradation một phần, hay online-only) để đặt kỳ vọng test chính xác.
  • Luôn phân biệt ba trạng thái: Online, Offline hoàn toàn, và Lie-Fi — trạng thái nguy hiểm nhất, nơi request treo rồi rớt.
  • Kiểm thử theo ba trụ cột: Đọc (cache, mốc thời gian), Ghi (xếp hàng, chỉ báo trạng thái), Đồng bộ (đúng thứ tự, không trùng, xử lý xung đột).
  • Những bug đắt giá nhất nằm ở transition (cắt mạng giữa request), duplicate (thiếu idempotency key), và mất dữ liệu âm thầm khi sync xung đột.
  • Đừng chỉ tin UI — với thao tác quan trọng, hãy đối chiếu phía server. Và đừng quên: thiếu feedback trạng thái offline cho người dùng cũng là một bug.
Khi bạn đã biến câu hỏi "nếu mất mạng thì sao?" thành phản xạ trong mọi test case, bạn đã nâng chất lượng app lên một tầm mà đa số đội QA còn bỏ ngỏ.

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