Product Management
Đăng nhập
ESC

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

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

Bài 29 — Camera, Photo & File Picker 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 từng làm QA cho một ứng dụng thương mại điện tử, ngân hàng số, hay mạng xã hội ở Việt Nam, gần như chắc chắn bạn đã chạm tới ba tính năng: chụp ảnh (camera), chọn ảnh từ thư viện (photo library) và chọn tệp (file picker). Người dùng chụp ảnh CCCD để xác thực eKYC, đăng ảnh sản phẩm lên Shopee, gửi ảnh chân dung cho ví MoMo, hay upload file PDF hợp đồng lên app bảo hiểm. Đây là những luồng "nhạy cảm" bậc nhất về mặt trải nghiệm: chúng đụng tới phần cứng thật (camera, cảm biến), tới hệ thống quyền (permission), tới bộ nhớ và tới các trình chọn (picker) do chính hệ điều hành cung cấp — thứ mà đội phát triển của bạn không kiểm soát được.

Điều làm QA đau đầu là: các luồng này rất dễ "chạy được trên máy của developer" nhưng vỡ tan trên thiết bị thật của người dùng. Một chiếc Xiaomi đời cũ với camera 8MP xử lý HDR khác hẳn iPhone 15 Pro. Một người dùng bấm "Từ chối quyền camera" sẽ thấy màn hình trắng nếu app không xử lý graceful. Một tấm ảnh 48MP từ Samsung Galaxy có thể làm app crash vì hết RAM khi decode. Bài này giúp bạn xây dựng một tư duy kiểm thử đầy đủ cho toàn bộ nhóm tính năng camera – ảnh – tệp, để không tính năng nào lọt lưới ra production. Đây là phần bổ trợ quan trọng cho luồng test quyền (permission matrix, ta sẽ nói kỹ ở Bài 31) và cho performance memory (Bài 15) — ở bài này ta tập trung riêng vào hành vi chức năng của camera, ảnh và picker.

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

Ba cổng vào media của app

Khi test, hãy phân biệt rõ ba đường mà media đi vào app, vì mỗi đường có rủi ro riêng:

  • Camera capture (chụp trực tiếp): App yêu cầu quyền camera, mở giao diện chụp (có thể là camera hệ thống hoặc camera custom do app tự vẽ), người dùng chụp và app nhận về ảnh/video.
  • Photo library / Gallery (chọn từ thư viện): App mở trình chọn ảnh của hệ điều hành. Trên iOS là PHPicker (từ iOS 14, không cần quyền toàn bộ thư viện) hoặc quyền Photos. Trên Android là Photo Picker (Android 13+) hoặc Storage Access Framework.
  • File picker (chọn tệp): App mở Files (iOS) / Documents UI (Android) để chọn PDF, DOCX, ZIP... Đây là luồng hay bị bỏ quên nhất trong test.

Camera custom vs camera hệ thống

Một điểm cực kỳ quan trọng cần xác định ngay đầu dự án: app dùng camera của hệ điều hành (mở app Camera mặc định qua intent/API) hay camera custom (tự vẽ giao diện, tự điều khiển ống kính qua AVFoundation trên iOS / CameraX trên Android)? Câu trả lời quyết định phạm vi test của bạn.

  • Nếu dùng camera hệ thống: bạn chủ yếu test luồng "gọi camera → nhận kết quả", còn flash/HDR/lấy nét là việc của hệ điều hành.
  • Nếu camera custom (rất phổ biến ở app eKYC, quét mã, làm đẹp): bạn phải test toàn bộ các nút điều khiển do app tự làm — front/back, flash, HDR, zoom, lấy nét chạm, tỉ lệ khung hình. Đây là nơi bug sinh sôi.

Ma trận biến thể camera cần bao phủ

Từ dàn ý gốc, ta mở rộng thành ma trận test có hệ thống. Với mỗi tổ hợp, hãy kiểm tra: ảnh chụp ra có đúng không, metadata (EXIF) có đúng không, và UI có phản hồi đúng không.

  • Camera trước (front) vs sau (back): Camera trước thường bị lật gương (mirror). Cần xác nhận yêu cầu sản phẩm: ảnh selfie lưu ra có nên lật lại như gương hay giữ nguyên như người khác nhìn? Chữ trên áo/giấy tờ chụp bằng camera trước có bị ngược không?
  • Flash: on / off / auto: Trong điều kiện thiếu sáng, flash on phải sáng; auto phải tự bật khi tối. Lưu ý camera trước nhiều máy dùng "screen flash" (làm sáng màn hình) thay vì đèn LED.
  • HDR mode: Chụp cảnh ngược sáng (cửa sổ, trời chiều) để xác nhận HDR hoạt động và không gây delay quá lâu (nhiều app freeze 1–2 giây khi merge HDR).
  • Ảnh (photo) vs video: Nếu app hỗ trợ cả hai, test chuyển đổi giữa hai chế độ, độ dài video tối đa, âm thanh có được ghi không, dung lượng file.
  • Xoay thiết bị & orientation: Chụp ở dọc và ngang, kiểm tra ảnh có bị xoay sai 90° không (lỗi kinh điển do đọc sai EXIF orientation).

Permission — trái tim của luồng media

Đây là phần gây lỗi production nhiều nhất. Với mỗi cổng vào media, phải test đủ các trạng thái quyền:

  • Chưa hỏi (not determined): Lần đầu bấm chụp → hệ thống hiện hộp thoại xin quyền.
  • Đồng ý (granted): Luồng chạy bình thường.
  • Từ chối (denied): App phải xử lý graceful — hiện thông báo tử tế, nút "Mở Cài đặt", không được crash hay treo màn hình đen. Đây là yêu cầu bắt buộc trong dàn ý gốc: "Permission denied → graceful".
  • Từ chối rồi vào lại: Trên iOS, sau khi từ chối, hệ thống không hỏi lại — app phải tự dẫn người dùng ra Settings. Test đúng hành vi này.
  • Quyền giới hạn (Limited Photos - iOS 14+): Người dùng chỉ cho phép chọn một vài ảnh. App phải hiển thị đúng tập ảnh được phép, có nút "Chọn thêm ảnh".
  • Bị thu hồi khi app đang chạy nền: Người dùng vào Settings tắt quyền rồi quay lại app — nhiều app crash ngay tại đây.

Tình huống thực tế

Tình huống 1 — Lỗi ảnh CCCD xoay ngang ở app ví điện tử

Một ví điện tử tại TP.HCM (gọi là "PayViet" cho ẩn danh) triển khai eKYC: người dùng chụp mặt trước CCCD bằng camera custom. Trong sprint kiểm thử, QA test trên iPhone 13 thấy mọi thứ ổn. Nhưng khi lên production, đội vận hành báo có tới hơn 30% hồ sơ từ dòng máy Oppo và Vivo bị hệ thống OCR đọc sai vì ảnh CCCD bị xoay ngang 90°.

Nguyên nhân: camera custom của app lưu ảnh theo raw buffer nhưng không đọc và áp dụng đúng thẻ EXIF Orientation. Trên iPhone, thư viện hình ảnh tự xử lý orientation nên QA không thấy lỗi; trên nhiều máy Android tầm trung, orientation nằm trong EXIF chứ chưa được xoay vật lý, khiến ảnh gửi lên server bị nằm ngang.

Bài học: Luôn test camera trên tối thiểu 4–5 dòng máy Android khác nhau (Samsung, Xiaomi, Oppo, Vivo) chứ không chỉ iPhone. Và trong bộ test luôn có bước "chụp → mở lại ảnh trên chính app và xem trên server" để bắt lỗi orientation. Sau sự cố, PayViet thêm case kiểm tra EXIF orientation vào regression suite và tỉ lệ hồ sơ lỗi giảm về dưới 2%.

Tình huống 2 — Crash khi chọn ảnh 108MP trên app mạng xã hội

Một app mạng xã hội cho giới trẻ (giả định "Vibe") cho phép đăng ảnh. Khi test bằng ảnh chụp thường (~3MB) mọi thứ mượt. Nhưng người dùng dùng Redmi Note với chế độ chụp 108MP tạo file ~25MB, độ phân giải 12000×9000 pixel. Khi họ chọn ảnh này từ gallery, app crash out-of-memory ngay lúc decode để hiển thị preview, vì app tải nguyên bitmap full-resolution vào RAM (một bitmap 12000×9000 ARGB chiếm hơn 400MB).

QA ban đầu không bắt được vì máy test là thiết bị flagship RAM lớn và ảnh mẫu nhỏ. Bài học rút ra có hai lớp: (1) Bộ ảnh test phải bao gồm ảnh "quái vật" — ảnh siêu lớn, ảnh panorama siêu rộng, ảnh HEIC (định dạng mặc định của iPhone), ảnh PNG trong suốt, GIF động, và ảnh 0 byte hỏng. (2) Test trên máy RAM thấp (3–4GB) vì đó mới là thiết bị phổ biến của người dùng thực tế ở Việt Nam. Vibe sau đó thêm bước downscale ảnh trước khi hiển thị, và bổ sung case "ảnh 108MP + máy 3GB RAM" vào ma trận thiết bị.

Tình huống 3 — File picker chọn PDF nhưng app chỉ nhận ảnh

Một app bảo hiểm số cho phép người dùng upload hợp đồng dạng PDF. Trong luồng "Đính kèm tài liệu", nút mở file picker hệ thống. Trên iOS, UIDocumentPicker được cấu hình contentTypes chỉ nhận public.image, nên khi người dùng vào tab "Duyệt" chọn file PDF, file bị làm mờ không chọn được, gây bối rối. Tệ hơn, trên một số máy Android, file picker cho phép chọn PDF nhưng khi app đọc lại thì URI bị mất quyền truy cập (SecurityException) vì app không giữ persistable URI permission.

Bài học: File picker phải được test riêng cho từng loại tệp mà yêu cầu cho phép (PDF, DOCX, ảnh, ZIP), test cả tệp nằm trên Google Drive/iCloud (không phải bộ nhớ local), tệp có tên tiếng Việt có dấu, tệp dung lượng lớn hơn giới hạn, và tệp có phần mở rộng "giả" (ảnh đổi đuôi thành .pdf). Nhiều đội bỏ hoàn toàn file picker khỏi test vì nghĩ "nó là của hệ thống" — đó là sai lầm, vì cách app cấu hình và xử lý URI trả về hoàn toàn thuộc trách nhiệm của app.

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

Đây là quy trình test một luồng media hoàn chỉnh, áp dụng được cho hầu hết app:

  • Xác định phạm vi: Hỏi PO/dev app dùng camera custom hay hệ thống, hỗ trợ những loại media nào (ảnh/video/file), định dạng nào, giới hạn dung lượng bao nhiêu, có nén ảnh không, ảnh gửi lên server ở độ phân giải nào.
  • Chuẩn bị bộ dữ liệu test (test asset kit): Tạo sẵn thư viện chuẩn gồm: ảnh JPEG thường, ảnh HEIC (iPhone), ảnh PNG trong suốt, ảnh siêu lớn (>50MP), ảnh panorama, GIF, ảnh 0 byte hỏng, video ngắn/dài, PDF thường, PDF có mật khẩu, DOCX, file tên tiếng Việt có dấu, file vượt giới hạn dung lượng. Nạp sẵn vào các máy test.
  • Test luồng permission trước tiên: Cài mới app (fresh install) để reset trạng thái quyền. Chạy qua đủ granted / denied / limited / thu hồi giữa chừng. Xác nhận mỗi trạng hợp có UI graceful và nút dẫn ra Settings hoạt động.
  • Test camera theo ma trận: Chạy qua front/back × flash(on/off/auto) × HDR × ảnh/video × dọc/ngang. Với mỗi ảnh chụp, mở lại và kiểm tra orientation, tỉ lệ khung, độ sáng.
  • Test chọn từ thư viện: Chọn từng loại ảnh trong asset kit, kiểm tra preview, tốc độ tải, không crash với ảnh lớn, giữ đúng ảnh sau khi xoay máy.
  • Test file picker: Chọn từng loại tệp được phép và không được phép, tệp trên cloud, tên có dấu, vượt dung lượng. Xác nhận app đọc được nội dung sau khi chọn (không mất quyền URI).
  • Test ngắt quãng (interruption): Trong lúc đang chụp/chọn, có cuộc gọi đến, app bị đẩy xuống nền rồi quay lại, hết pin cảnh báo, xoay màn hình. Xác nhận không mất dữ liệu đang chụp và không crash.
  • Kiểm tra kết quả cuối: Ảnh/tệp gửi lên server đúng định dạng, đúng hướng, đúng dung lượng sau nén, metadata nhạy cảm (vị trí GPS trong EXIF) được xử lý theo chính sách riêng tư.

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

  • Chỉ test trên iPhone hoặc chỉ trên máy flagship. Lỗi camera phụ thuộc phần cứng cực nặng. Tối thiểu bao phủ 1 iPhone, và 3–4 dòng Android phổ biến ở VN (Samsung Galaxy A-series, Xiaomi/Redmi, Oppo, Vivo) ở các mức RAM khác nhau.
  • Quên định dạng HEIC. iPhone mặc định chụp HEIC, nhiều backend chỉ nhận JPEG → ảnh upload thất bại âm thầm. Luôn có case ảnh HEIC.
  • Bỏ qua trạng thái "denied lần hai". Trên iOS sau khi từ chối, bấm chụp lại không hiện hộp thoại quyền nữa — nếu app không dẫn ra Settings, người dùng bị kẹt. Đây là bug graceful-handling kinh điển.
  • Không test EXIF orientation. Luôn chụp dọc lẫn ngang bằng cả camera trước và sau, rồi xem ảnh trên server, không chỉ xem trên app.
  • Bỏ quên vị trí GPS trong EXIF. Ảnh có thể chứa toạ độ nơi chụp. Với app cần bảo mật (hẹn hò, ngân hàng), hãy kiểm tra metadata này đã được xoá trước khi upload — đây vừa là bug chức năng vừa là rủi ro riêng tư.
  • Không test picker với file trên cloud. File từ iCloud/Google Drive cần tải về, có độ trễ và có thể fail mạng. App phải hiện loading và xử lý lỗi.
  • Mẹo tự động hoá: Camera thật rất khó tự động hoá vì cần phần cứng. Thủ thuật phổ biến là dùng mock/inject ảnh qua build cấu hình test, hoặc dùng thiết bị farm hỗ trợ camera image injection (một số cung cấp qua BrowserStack/Sauce). Nhưng luồng permission và picker thì nên giữ một phần manual trên thiết bị thật vì đây là nơi hành vi hệ điều hành thay đổi liên tục qua các phiên bản OS.

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

  • Lập ma trận test camera: Chọn một app bạn đang dùng (ví dụ app ngân hàng có tính năng chụp CCCD). Vẽ bảng tổ hợp front/back × flash(on/off/auto) × dọc/ngang, và ghi kết quả kỳ vọng cho từng ô. Đánh dấu những ô bạn nghĩ dễ sinh bug nhất.
  • Tạo asset kit của riêng bạn: Thu thập tối thiểu 10 tệp media "khó" (HEIC, ảnh >50MP, GIF, PDF tên tiếng Việt, ảnh 0 byte hỏng...). Đây sẽ là bộ dữ liệu tái sử dụng cho mọi dự án media sau này.
  • Kịch bản permission denied: Viết một test case đầy đủ (tiền điều kiện, các bước, kết quả kỳ vọng) cho tình huống người dùng từ chối quyền camera rồi bấm chụp lần hai trên iOS. Mô tả rõ đâu là hành vi "graceful" đúng.
  • Săn bug orientation: Trên một máy Android, dùng app bất kỳ có upload ảnh, chụp một tờ giấy có chữ ở cả hướng dọc và ngang bằng cả hai camera. Kiểm tra ảnh hiển thị lại có bị xoay/lật sai không, ghi lại phát hiện.

Tóm tắt

Camera, photo và file picker là ba cổng đưa media vào app, và cũng là ba nguồn bug production dai dẳng nhất vì chúng phụ thuộc phần cứng thật, hệ thống quyền và các trình chọn của hệ điều hành mà đội bạn không kiểm soát. Chìa khoá để test tốt gồm: xác định rõ camera là custom hay hệ thống; bao phủ đầy đủ ma trận biến thể (front/back, flash on/off/auto, HDR, ảnh/video, dọc/ngang); test permission qua mọi trạng thái với yêu cầu bắt buộc là xử lý graceful khi bị từ chối; chuẩn bị một asset kit gồm các tệp "khó" như HEIC, ảnh siêu lớn, file tên tiếng Việt; và luôn kiểm tra kết quả cuối trên nhiều dòng máy Android phổ biến ở Việt Nam, không chỉ iPhone. Ba tình huống PayViet, Vibe và app bảo hiểm cho thấy: lỗi orientation, out-of-memory và cấu hình picker sai đều né được nếu bạn test có hệ thống ngay từ đầu. Ở các bài sau, ma trận permission (Bài 31) và crash reporting (Bài 32) sẽ bổ trợ trực tiếp cho nhóm luồng media này.

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