Quy mô và hiệu năng
Kiểm thử đặc thù mobile
Bốn nhóm kịch bản chỉ mobile mới có — app lifecycle, network chaos, runtime permission và upgrade/migration. Đây là nơi sinh ra phần lớn bug lọt ra production.
Nội dung bài
Thuật ngữ trong bài (32)
- dialog · hộp thoại
- push notification · thông báo đẩy
- acceptance criteria · tiêu chí nghiệm thu
- build · bản dựng
- CI · tích hợp liên tục
- linter · máy soát mã
- pull request · yêu cầu gộp mã
- bug · lỗi
- emulator · máy giả lập
- regression · lỗi tái phát
- unit test · kiểm thử đơn vị
- release · bản phát hành
- rollback · quay về bản cũ
- chaos engineering · chủ động gây sự cố
- on-call · ca trực
- root cause · nguyên nhân gốc
- crash-free · tỷ lệ không sập
- OTP · mã dùng một lần
- permission · quyền truy cập
- refresh token · khóa làm mới phiên
- session · phiên đăng nhập
- API · giao diện lập trình
- cache · bộ nhớ đệm
- deep link · liên kết vào thẳng màn hình
- endpoint · điểm gọi
- gateway · cổng vào hệ thống
- idempotency · gọi lại không nhân đôi
- migration · chuyển đổi dữ liệu
- retry · thử lại
- schema · hình dạng dữ liệu
- timeout · hạn chờ
- token · khóa truy cập
8
kịch bản lifecycle
7
điều kiện mạng
6
trạng thái quyền
8
kịch bản nâng cấp
Vì sao phải tách riêng bốn nhóm này
Bốn nhóm này trả lời một câu hỏi khác với test chức năng
Test chức năng hỏi "tính năng có làm đúng việc của nó không". Bốn nhóm dưới đây hỏi "khi hệ điều hành, mạng, hoặc chính user can thiệp vào giữa chừng thì app xử lý ra sao".
App lifecycle
OS tạm dừng, đẩy xuống nền, kill bất cứ lúc nào. Tám kịch bản.
Network chaos
Mạng chậm, chập chờn, response không về. Bảy điều kiện.
Runtime permission
Sáu trạng thái, không chỉ "đã cho phép".
Upgrade và migration
User update đè, mang theo dữ liệu cũ. Tám kịch bản.
Chúng gần như không bao giờ xuất hiện khi dev tự thử trên máy mình, vì hai bên thử trong hai thế giới khác nhau:
Điều kiện dev thử
- Máy mới, RAM dư, CPU mạnh
- Wi-Fi văn phòng ổn định
- Quyền đã cấp đủ từ lần chạy đầu
- App vừa cài mới, database sạch
- Ngồi yên một chỗ, không ai gọi đến
Điều kiện user thật
- Máy ba năm tuổi, bộ nhớ gần đầy
- 3G chập chờn, chuyển sang Wi-Fi giữa chừng
- Từng bấm "Không cho phép" và quên mất
- Update đè bản cũ, mang theo dữ liệu hai năm
- Có cuộc gọi, có thông báo, khoá máy giữa thao tác
1. App lifecycle
Hệ điều hành có toàn quyền tạm dừng, đẩy xuống background, hoặc kill app bất cứ lúc nào. Code giả định "mình chạy liên tục từ đầu tới cuối" là code sẽ hỏng.
đẩy xuống background
▶lưu nháp trước khi mất quyền chạy
▶kill vì thiếu bộ nhớ
user mở lại, app khởi động từ số không
▶đọc lại nháp
▶khôi phục đúng màn hình, không màn trắng
◀
1.Foreground → background → foreground
State còn nguyên, không gọi lại API thừa, không mất dữ liệu đang nhập. Kịch bản này xảy ra nhiều lần mỗi phiên dùng app, nên hỏng ở đây là hỏng với mọi user.
2.App bị OS kill trong background rồi mở lại
Khôi phục đúng màn hình, hoặc về màn hình an toàn — không crash, không màn trắng. Bước "lưu nháp trước khi mất quyền chạy" là bước duy nhất app còn kiểm soát được; bỏ nó thì mọi bước sau vô nghĩa.
3.Khoá màn hình rồi mở khoá
Session còn hạn thì không bắt đăng nhập lại; hết hạn thì báo rõ.
4.Có cuộc gọi đến giữa chừng
Video hoặc audio tạm dừng đúng chỗ, quay lại tiếp tục được.
5.Push notification khi app đang mở
Không đè lên màn hình đang thao tác, deep link vào đúng chỗ.
6.Low memory
Không mất dữ liệu chưa gửi.
7.Battery saver bật
Background task bị hạn chế — app báo cho user hay im lặng hỏng?
8.Restart device
Token, cache, dữ liệu local còn dùng được.
Sáu việc phải chốt trước khi nhóm này được coi là làm xong — cả bốn nhóm dưới đây đều dùng đúng sáu ô này:
Ai chốt
Chủ kỹ thuật của module. Bốn flow rủi ro cao ở trên mỗi flow ghi tên một người trong bảng phân công, không để "team lo".
Chặn ở chặng nào
Lúc review thiết kế màn hình — màn nào giữ dữ liệu user đang nhập thì phải khai chỗ lưu nháp ngay trong thiết kế. Đợi QA thấy màn trắng là đã muộn ba chặng.
Máy hay người
Máy: unit test save/restore state chạy mỗi PR. Người: bật Don't keep activities trên Android, kill app từ recent apps rồi mở lại.
Đo bằng số nào
Crash-free session rate, tách riêng 5 giây đầu sau khi quay lại foreground. Ở 500.000 user, 99,5% nghĩa là 2.500 phiên hỏng mỗi ngày.
Bằng chứng ở đâu
Video quay màn hình kill-rồi-mở-lại, đính vào pull request, cho đúng bốn flow rủi ro cao.
Hết giờ cắt gì
Cắt battery saver và restart device trước. Không bao giờ cắt "app bị OS kill trong background" ở bốn flow đó.
Ép app vào từng trạng thái bằng lệnh gì, vì sao Don't keep activities cho kết quả xanh giả, và bảy bước kiểm một flow rủi ro cao: Test app lifecycle thế nào.
2. Network chaos
Mất mạng hoàn toàn là trường hợp dễ, vì nó rõ ràng và app dễ phát hiện. Trường hợp khó chịu hơn nhiều là mạng chậm, chập chờn, hoặc request đi được mà response không về.
POST /orders kèm idempotency-key
▶chuyển tiếp
▶tạo đơn, trừ tiền
tự xử lýresponse mất vì mạng đứt giữa chừng
◀user bấm lại, gửi lại đúng key cũ
▶chuyển tiếp
▶trả lại đơn cũ, không tạo đơn thứ hai
◀
Cùng một thao tác của user, khác nhau đúng một trường trong request:
Không có idempotency-key
- Server thấy hai request khác nhau
- Tạo hai đơn, trừ tiền hai lần
- User khiếu nại, kế toán đối soát tay
- Bug chỉ hiện ở mạng yếu nên khó tái hiện
Có idempotency-key
- Server nhận ra key đã xử lý
- Trả lại đúng đơn cũ, không trừ thêm
- User bấm bao nhiêu lần cũng một kết quả
- Test chủ động gửi trùng là chứng minh được
Các điều kiện cần giả lập:
1.Request đi nhưng response mất
Nguy hiểm nhất trong bảy điều kiện, vì app không phân biệt được với thất bại thật. Đây chính là kịch bản sơ đồ trên mô tả, và duplicate transaction là hậu quả.
2.Chuyển mạng Wi-Fi sang 4G giữa request
Socket đứt giữa chừng, tin nhắn gửi hai lần hoặc mất hẳn. Xảy ra hàng ngày với user đi ra khỏi vùng phủ Wi-Fi văn phòng.
3.Băng thông rất thấp, 100–500 Kbps
Timeout đặt quá ngắn, spinner quay vĩnh viễn.
4.Latency cao
User bấm lại nhiều lần vì tưởng máy treo.
5.Packet loss, connection reset
Retry không có backoff, đánh sập luôn server.
6.API trả 500 hoặc 503
App hiện lỗi kỹ thuật thô cho user, hoặc coi như thành công.
7.Timeout ở tầng gateway
Không phân biệt được "chưa chắc thất bại" với "chắc chắn thất bại". Hai trạng thái này cần hai cách xử lý khác nhau, và gộp chúng lại là lỗi thiết kế chứ không phải lỗi code.
Với mỗi kịch bản, câu hỏi kiểm tra luôn là năm câu này, theo đúng thứ tự:
App có crash không?
Spinner có quay mãi không, hay có đường thoát?
Có retry vô hạn không?
Có tạo ra duplicate transaction không?
Câu quan trọng nhất với module Bán hàng.
Thông báo cho user có đúng với thực tế không?
Sáu việc phải chốt trước khi nhóm này được coi là làm xong:
Ai chốt
Chủ kỹ thuật, lúc duyệt hợp đồng API. Endpoint ghi dữ liệu thì spec bắt buộc khai idempotency-key.
Chặn ở chặng nào
Vòng review thiết kế API, không phải lúc QA nhận build. POST thiếu key thì trả lại ngay từ vòng đó.
Máy hay người
Máy: test chủ động gửi trùng, chạy CI mỗi PR đụng endpoint ghi. Người: đổi Wi-Fi sang 4G giữa lúc thao tác — phần này máy giả lập không giống thật.
Đo bằng số nào
Tỷ lệ đơn trùng trên tổng đơn. Ở 500.000 user, 0,1% là 500 đơn phải đối soát tay.
Bằng chứng ở đâu
Log test gửi trùng đính vào pull request: một key, hai lần gửi, hai response giống hệt nhau.
Hết giờ cắt gì
Luồng risk thấp giữ lại ba trạng thái biên. Không bao giờ cắt idempotency ở luồng chạm tiền.
Dựng bảy điều kiện mạng đó bằng công cụ gì, cái nào đẩy được vào CI, và quy trình sáu bước chứng minh idempotency thật sự chạy: Chạy network chaos test thế nào.
3. Runtime permission
Mỗi permission phải test đủ sáu trạng thái, không chỉ trạng thái "đã cho phép":
Xin quyền đúng lúc user cần chức năng, kèm một câu giải thích
Allow
luồng chính chạy đúng
Deny một lần
app giải thích vì sao cần, cho đường thử lại
Deny vĩnh viễn
app không hỏi lại vô ích, chỉ ra Settings
Allow once (iOS)
lần mở sau hỏi lại, app không giả định còn quyền
Thu hồi từ Settings khi app đang chạy background
quay lại app không crash
Bị reset sau khi update app hoặc sau thời gian dài không dùng
coi như chưa từng cấp
Không xin permission ở màn hình đầu tiên
Xin đúng lúc user cần chức năng đó, kèm một câu giải thích. Xin sớm thì tỷ lệ deny vĩnh viễn tăng vọt, và deny vĩnh viễn thì gần như không lấy lại được.
Camera và photo
Đính kèm ảnh trong Chat.
Notification
Task nhắc hạn.
Location
Nếu có tính năng cần tới.
Sáu việc phải chốt trước khi nhóm này được coi là làm xong:
Ai chốt
Chủ sản phẩm. Xin quyền vào lúc nào và câu giải thích viết ra sao là quyết định sản phẩm, không phải quyết định code.
Chặn ở chặng nào
Lúc viết acceptance criteria cho màn hình. Sáu trạng thái ở cây trên là sáu dòng AC — thiếu dòng nào thì màn hình đó chưa đủ điều kiện giao cho dev.
Máy hay người
Gần như toàn bộ là người, vì dialog của hệ điều hành không tự động hoá rẻ được. Máy chỉ chặn được đúng một việc: lint cấm gọi xin quyền ở màn hình đầu tiên.
Đo bằng số nào
Tỷ lệ deny vĩnh viễn theo từng quyền. Notification 5% ở 500.000 user là 25.000 người không bao giờ nhận được nhắc hạn Task.
Bằng chứng ở đâu
Ảnh chụp đủ sáu trạng thái cho mỗi quyền, lưu cùng tài liệu màn hình, để người sau không phải dựng lại từ đầu.
Hết giờ cắt gì
Cắt "allow once" và "thu hồi từ Settings" ở quyền ít dùng. Không cắt deny vĩnh viễn — đó là trạng thái không lấy lại được.
Đưa quyền về từng trạng thái bằng lệnh gì, và vì sao Android không phân biệt được "chưa hỏi lần nào" với "deny vĩnh viễn": Test runtime permission thế nào.
4. Upgrade và data migration
Đây là nhóm nguy hiểm nhất, và là nhóm bị bỏ sót nhiều nhất, vì dev luôn test trên bản cài mới. User thật thì không cài mới — họ update đè lên bản cũ, mang theo dữ liệu cũ, cache cũ, token cũ, schema database cũ. Migration không chạy một bước, nó chạy nối tiếp nhau:
- v1.0
bản cũ nhất còn hỗ trợ
- v1.4
migration 1 → 2
- v2.0
migration 2 → 3, chạy ngay sau bước trên
- v2.3 hiện tại
nhảy cóc nhiều bậc là chỗ dễ gãy nhất
1.Update từ version cũ nhất còn hỗ trợ, nhảy cóc nhiều version
Migration phải chạy nối tiếp nhau, và đây là chỗ dễ gãy nhất trong tám kịch bản — mỗi bước đúng riêng lẻ không bảo đảm chuỗi bước đúng.
2.App bị kill giữa lúc migration
Lần mở sau phải chạy lại được, không để database nửa vời. Ghép với nhóm lifecycle ở trên: OS không hỏi ý kiến app trước khi kill.
3.Fresh install
Đường cơ sở.
4.Update từ version liền trước
Trường hợp phổ biến nhất.
5.Update khi đang đăng nhập
Session, token, refresh token còn dùng được.
6.Update khi token đã hết hạn
Không được rơi vào vòng lặp đăng nhập.
7.Máy gần hết dung lượng khi migration
Phải fail an toàn, không xoá dữ liệu cũ trước khi ghi xong dữ liệu mới.
8.Reinstall sau khi gỡ
Dữ liệu cũ còn sót lại có gây xung đột không.
Nguyên tắc cứng, chạy đúng ba bước và không được đảo thứ tự:
Ghi xong dữ liệu mới
Ghi sang chỗ mới, chưa đụng gì tới dữ liệu cũ.
Đọc lại và xác nhận dữ liệu mới dùng được
Không tin lệnh ghi trả về thành công là đủ.
Bước này mới được xoá dữ liệu cũ
Đảo hai bước cuối là mất dữ liệu vĩnh viễn.
Sáu việc phải chốt trước khi nhóm này được coi là làm xong:
Ai chốt
Chủ kỹ thuật, lúc duyệt thay đổi schema. Đổi schema mà không kèm kế hoạch migration thì không duyệt.
Chặn ở chặng nào
Vòng review pull request đổi schema — ba bước ghi, đọc lại, xoá phải nhìn thấy được trong diff. Sau khi phát hành thì không còn chặng nào để chặn nữa.
Máy hay người
Máy: test migration từ mọi version còn hỗ trợ, chạy trên dữ liệu mẫu thật, mỗi PR đụng schema. Người: cài bản cũ lên máy thật, dùng vài ngày cho có dữ liệu, rồi update đè.
Đo bằng số nào
Tỷ lệ mở app thất bại trong 24 giờ sau update. Ở 500.000 user, 0,2% là 1.000 người mất dữ liệu local, và không có đường rollback.
Bằng chứng ở đâu
Bảng version nguồn nhân kết quả update, lưu cùng release notes nội bộ của bản phát hành đó.
Hết giờ cắt gì
Cắt "reinstall sau khi gỡ" và "máy gần hết dung lượng". Không bao giờ cắt nhảy cóc nhiều version và kill giữa lúc migration.
Dựng dữ liệu của version cũ bằng cách nào, cài đè ra sao cho không mất dữ liệu, và cách ép hỏng giữa lúc migration mà không phải đua với hệ điều hành: Test upgrade và data migration thế nào.
Phân tầng: áp bộ nào cho luồng nào
Áp cả bốn nhóm cho mọi hạng mục thì throughput sụp. Phân theo mức risk:
Mức cao
Thanh toán, tạo đơn, OTP, gửi tin nhắn.
Mức trung bình
Đăng nhập, upload, đồng bộ Task.
Mức thấp
Màn hình chỉ đọc, cài đặt, trang tĩnh.
Giới hạn
Danh sách này dài
Nên nó chỉ chạy đầy đủ ở luồng mức risk cao và ở regression trước release, không chạy cho mọi pull request.

