Product Management
Đăng nhập
ESC

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

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

Bài 52 — Stress & Soak Testing Mobile

Đã đọc 0/13 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

Hầu hết các bug nghiêm trọng nhất trên mobile không xuất hiện trong 5 phút đầu tiên bạn mở app. Chúng xuất hiện sau khi người dùng cuộn qua danh sách 2.000 sản phẩm, sau khi app chạy nền suốt 8 tiếng trong lúc họ đi ngủ, sau khi họ tap loạn xạ vào nút "Đặt hàng" vì mạng lag. Đây chính là địa hạt của stress testing (kiểm thử tải cực đại) và soak testing (kiểm thử bền bỉ theo thời gian) — hai loại kiểm thử mà QA thường bỏ qua vì chúng tốn thời gian, khó tự động hóa, và không cho kết quả "pass/fail" ngay tức thì.

Nhưng chính đây là nơi phân biệt một QA mobile giỏi với một người chỉ biết chạy test case chức năng. Một app có thể pass 100% functional test nhưng vẫn crash hàng loạt trên tay người dùng thật, bởi vì trong đời thực, app không chạy trong môi trường sạch sẽ, chạy một lần rồi tắt. Nó phải sống sót qua hàng giờ, qua hàng nghìn thao tác, qua bộ nhớ ngày càng cạn kiệt.

Trong bài này, chúng ta sẽ đi sâu vào việc thiết kế, thực thi và đọc kết quả của stress test và soak test cho ứng dụng di động. Đây là kỹ năng giúp bạn bắt được lớp bug mà không công cụ functional testing nào chạm tới được.

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

Stress test — đẩy app vượt qua giới hạn bình thường

Stress test là việc cố tình đưa ứng dụng vào tình huống vượt quá điều kiện vận hành thông thường, để xem nó gãy ở đâu và gãy như thế nào. Câu hỏi cốt lõi không phải là "app có chạy đúng không?" mà là "khi app không chịu nổi nữa, nó thất bại một cách duyên dáng (graceful) hay thảm khốc (catastrophic)?".

Một app thất bại duyên dáng sẽ hiện thông báo "Danh sách quá dài, vui lòng lọc bớt" hoặc tự động phân trang. Một app thất bại thảm khốc sẽ đơ (ANR — Application Not Responding trên Android), crash, hoặc tệ hơn là làm hỏng dữ liệu người dùng.

Các dạng stress test phổ biến trên mobile:

  • Data stress — nhồi một lượng dữ liệu khổng lồ: danh sách 5.000 tin nhắn, giỏ hàng 500 sản phẩm, feed vô hạn cuộn liên tục. Mục tiêu là xem RecyclerView/UICollectionView có tái sử dụng cell đúng cách không, hay bộ nhớ phình lên theo số lượng item.
  • Input stress (rapid tap / monkey testing) — tap thật nhanh và ngẫu nhiên vào giao diện. Kinh điển nhất là "double tap" gây ra hai lần gọi API đặt hàng, tạo ra hai đơn trùng.
  • Concurrency stress — kích hoạt nhiều thao tác đồng thời: vừa upload ảnh, vừa gọi API, vừa chuyển màn hình.
  • Resource stress — chạy app khi thiết bị đã gần cạn RAM, gần đầy bộ nhớ trong, hoặc CPU đang bị chiếm bởi app khác.

Soak test — chạy liên tục trong nhiều giờ, nhiều ngày

Soak test (còn gọi là endurance testing — kiểm thử độ bền) trả lời một câu hỏi khác hẳn: "Nếu app chạy liên tục 6 tiếng, 24 tiếng, hay 3 ngày thì có gì xấu dần đi không?".

Điểm mấu chốt của soak test là nó phát hiện những vấn đề tích lũy theo thời gian — thứ mà một lần chạy ngắn không bao giờ lộ ra:

  • Memory leak — bộ nhớ rò rỉ, tăng dần cho đến khi hệ điều hành kill app (bài 15 đi sâu về leak detection; ở đây ta quan tâm việc phát hiện xu hướng qua thời gian dài).
  • Resource exhaustion — file handle, socket, database connection không được đóng, tích lũy dần.
  • Cache phình to — cache ảnh, cache dữ liệu không có giới hạn, chiếm hết bộ nhớ trong sau nhiều giờ.
  • Sự trôi (drift) của timer và scheduler — các tác vụ định kỳ chồng chéo lên nhau sau nhiều giờ.
  • Xuống cấp hiệu năng — sau 4 tiếng, giao diện phản hồi chậm hẳn dù lúc đầu rất mượt.
Sự khác biệt cần nhớ: stress test đo giới hạn không gian (bao nhiêu là quá nhiều), soak test đo giới hạn thời gian (bao lâu là quá lâu). Một app có thể xử lý tốt 5.000 item nhưng vẫn chết sau 8 tiếng chạy nền — và ngược lại.

Vì sao mobile đặc biệt khắc nghiệt

Trên server, bạn có thể thêm RAM, thêm node. Trên mobile, bạn bị nhốt trong một chiếc điện thoại Android tầm trung 3GB RAM mà hệ điều hành sẵn sàng kill app của bạn bất cứ lúc nào để nhường tài nguyên cho ứng dụng đang mở. Đây là lý do soak test và stress test trên mobile không phải là "nice to have" mà là bắt buộc với bất kỳ app nào người dùng mở nhiều giờ mỗi ngày như app ngân hàng, gọi xe, hay mạng xã hội.

Tình huống thực tế

Ví dụ 1 — App gọi xe và cú soak test 12 tiếng của tài xế

Một startup gọi xe ở TP.HCM (gọi là "GoRide" cho dễ hình dung) nhận được phản ánh kỳ lạ: app tài xế thường "tự thoát" vào cuối ca đêm, khoảng 2-3 giờ sáng. Tài xế phải đăng nhập lại, và trong vài phút đó họ mất cuốc. Functional test không bao giờ tái hiện được vì mỗi lần test chỉ chạy vài phút.

Đội QA quyết định làm soak test đúng nghĩa: cắm một chiếc Samsung Galaxy A15 (RAM 4GB, đại diện cho máy phổ thông của tài xế Việt), mở app tài xế ở chế độ "online chờ cuốc", và để chạy liên tục 12 tiếng với một script mô phỏng nhận cuốc mỗi 20 phút. Họ dùng Android Studio Profiler ghi lại đường cong bộ nhớ.

Kết quả: đường bộ nhớ đi lên như bậc thang — mỗi lần bản đồ cập nhật vị trí GPS, app tạo một LocationListener mới nhưng không hủy cái cũ. Sau khoảng 9-10 tiếng, bộ nhớ chạm ngưỡng và hệ điều hành kill app. Con số cụ thể: bộ nhớ tăng trung bình 8MB mỗi giờ, và app crash khi chạm ~380MB trên máy 4GB đang chạy nhiều app nền.

Bài học: Bug này chỉ lộ ra dưới soak test. Không có lượng functional test nào bắt được nó, vì nó cần thời gian tích lũy. Sau khi fix (hủy listener cũ trước khi tạo mới), đường bộ nhớ trở nên phẳng và tỷ lệ "tự thoát" ban đêm giảm 94%.

Ví dụ 2 — Sàn thương mại điện tử và "cơn mưa tap" ngày sale

Một sàn TMĐT (giả định tên "ChợViệt") gặp sự cố trong đợt sale 12/12: hệ thống ghi nhận nhiều đơn hàng trùng lặp — cùng một khách, cùng sản phẩm, đặt cách nhau chưa tới 1 giây. Bộ phận chăm sóc khách hàng ngập trong khiếu nại "tôi bị trừ tiền hai lần".

QA tái hiện bằng một stress test input đơn giản trên Appium: gửi 10 lệnh tap liên tiếp vào nút "Đặt hàng" trong vòng 500ms, mô phỏng đúng hành vi người dùng sốt ruột khi mạng lag lúc sale.

# Rapid-tap stress: dội tap vào nút Đặt hàng
place_order_btn = driver.find_element(AppiumBy.ID, "com.choviet:id/btn_place_order")
for i in range(10):
    place_order_btn.click()   # không chờ giữa các lần
# Sau đó kiểm tra: chỉ được tạo ĐÚNG 1 đơn hàng

Kết quả tái hiện chính xác: 10 tap tạo ra 3-4 đơn hàng trùng. Nút không bị disable ngay sau lần tap đầu tiên, và không có cơ chế idempotency (khóa trùng) phía server dựa trên request ID.

Bài học: Stress test dạng rapid-tap là bắt buộc cho mọi nút gây ra hành động không thể hoàn tác (thanh toán, gửi tiền, đặt hàng). Cách khắc phục là hai lớp: client disable nút ngay sau tap đầu + gửi kèm một idempotency_key để server từ chối request trùng. Con số ấn tượng nhất: một dòng code disable nút đã cứu cả một đội vận hành khỏi hàng nghìn đơn trùng trong 6 tiếng sale.

Ví dụ 3 — App tin tức và danh sách "vô hạn" làm phình bộ nhớ

Một tòa soạn số cho ra app đọc báo với tính năng cuộn vô hạn (infinite scroll). Trong quá trình QA, tester phát hiện app ngày càng giật khi cuộn sâu. Họ thiết kế stress test: dùng script cuộn liên tục qua 1.500 bài viết, mỗi bài có ảnh thumbnail, và theo dõi bộ nhớ + FPS.

Phát hiện: UICollectionView (iOS) không giải phóng ảnh của các bài đã cuộn qua khỏi màn hình. Sau 800 bài, bộ nhớ đạt 620MB và FPS tụt từ 60 xuống 22. Trên iPhone SE đời cũ (3GB RAM), app bị iOS terminate ở khoảng bài thứ 1.100.

Bài học: Infinite scroll là cái bẫy cổ điển kết hợp cả stress lẫn soak. Giải pháp là cell reuse đúng cách + hủy tải ảnh ngoài màn hình + giới hạn cửa sổ dữ liệu (chỉ giữ N item quanh vị trí hiện tại trong bộ nhớ). Đây là loại bug mà chỉ khi đẩy tới cực đại mới thấy được.

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

Bước 1 — Xác định "kịch bản khắc nghiệt nhất" của app bạn. Trước khi viết bất cứ script nào, hãy hỏi: người dùng nào dùng app lâu nhất và nặng nhất? Với app gọi xe là tài xế online cả ca. Với app chat là nhóm 500 thành viên nhắn liên tục. Đó là kịch bản bạn cần stress/soak.

Bước 2 — Chọn thiết bị "yếu nhất còn được hỗ trợ". Đừng test trên iPhone 15 Pro. Hãy test trên chiếc máy phổ thông thấp nhất trong danh sách hỗ trợ — Android 3-4GB RAM, iPhone SE. Bug tài nguyên luôn lộ ra ở máy yếu trước.

Bước 3 — Thiết lập giám sát trước khi chạy. Bật Android Studio Profiler (Memory + CPU) hoặc Xcode Instruments (Allocations + Leaks). Với soak test dài, hãy log số liệu ra file định kỳ (mỗi 60 giây ghi mức bộ nhớ, số thread, số open file) để có đường cong theo thời gian.

Bước 4 — Viết script stress cho input và data. Với rapid-tap, dùng vòng lặp không delay. Với data stress, seed database hoặc mock API trả về số lượng item cực lớn. Với monkey testing, Android có sẵn công cụ adb shell monkey:

# Dội 5000 sự kiện ngẫu nhiên vào app, throttle 100ms mỗi event
adb shell monkey -p com.choviet.app --throttle 100 -v 5000

Bước 5 — Chạy soak test đủ dài. Tối thiểu 4 tiếng để thấy xu hướng, lý tưởng là qua đêm (8-12 tiếng) hoặc cuối tuần (48-72 tiếng) cho app quan trọng. Trong lúc chạy, đừng chạm vào máy — để nó tự vận hành theo script.

Bước 6 — Đọc đường cong, không đọc điểm. Điều quan trọng nhất khi phân tích: nhìn hình dạng của đường bộ nhớ theo thời gian, không phải giá trị tại một thời điểm.

  • Đường phẳng, có răng cưa đều (do GC) = khỏe mạnh.
  • Đường đi lên đều đặn không quay về = leak, phải điều tra.
  • Đường bậc thang (mỗi thao tác nhảy một bậc và không tụt) = leak theo hành động.
Bước 7 — Ghi lại ngưỡng gãy (breaking point). Đừng chỉ báo "app crash". Hãy báo con số: "App crash ở item thứ 1.100 / sau 9,5 giờ / khi bộ nhớ chạm 380MB". Con số là thứ dev cần để tái hiện và fix.

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

Lỗi: nhầm lẫn stress test với performance test. Performance test đo app nhanh đến mức nào trong điều kiện bình thường (bài 14 nói về FPS). Stress test tìm điểm gãy trong điều kiện bất thường. Đừng gộp mục tiêu — chúng có tiêu chí pass/fail khác nhau.

Lỗi: soak test quá ngắn. Chạy soak test 30 phút rồi kết luận "không có leak" là sai lầm phổ biến nhất. Nhiều leak chỉ lộ sau 3-4 tiếng. Nếu không đủ thời gian trong ngày, hãy để máy chạy qua đêm.

Lỗi: chạy trên môi trường quá lý tưởng. Test trên máy mạnh, RAM trống, mạng wifi hoàn hảo sẽ giấu đi hầu hết bug. Hãy cố tình làm bẩn môi trường: mở vài app nền, dùng máy yếu, mạng 3G chập chờn.

Lỗi: không tính đến vòng đời app. Trên mobile, soak test phải bao gồm cả tình huống app bị đẩy xuống nền rồi kéo lên (background/foreground) — đây là lúc leak về listener và observer hay xảy ra nhất.

Mẹo — tự động hóa việc thu thập số liệu. Với soak test dài, đừng ngồi canh màn hình. Viết một script adb shell dumpsys meminfo <package> chạy định kỳ, ghi ra CSV, rồi vẽ biểu đồ. Đường cong sẽ nói cho bạn mọi thứ.

Mẹo — kết hợp Chaos vào stress test. Trong lúc stress, chủ động bật/tắt mạng, xoay màn hình, nhận cuộc gọi đến. App phải sống sót qua cả sự hỗn loạn, không chỉ qua tải cao.

Mẹo — luôn có test "double tap" cho nút không thể hoàn tác. Đây là stress test rẻ nhất, nhanh nhất, và cứu bạn khỏi thảm họa lớn nhất (đơn trùng, chuyển tiền trùng). Đưa nó vào regression suite.

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

  • Thiết kế soak test. Chọn một app bạn dùng hàng ngày (Grab, Momo, Zalo...). Viết ra kịch bản soak test 8 tiếng cho người dùng nặng nhất của app đó: thiết bị nào, thao tác lặp lại nào, số liệu nào cần theo dõi, và bạn kỳ vọng đường bộ nhớ trông ra sao.
  • Rapid-tap thực chiến. Trên một app Android bất kỳ có nút gây hành động (gửi tin, thêm giỏ hàng), dùng adb shell input tap x y trong vòng lặp 10 lần liên tiếp không delay. Quan sát: nút có bị disable không? Có tạo ra hành động trùng không? Viết bug report với con số cụ thể.
  • Đọc đường cong bộ nhớ. Mở Android Studio Profiler (hoặc Xcode Instruments), mở một app, cuộn một danh sách dài liên tục trong 10 phút, chuyển app xuống nền rồi kéo lên 20 lần. Chụp lại đường bộ nhớ và phân loại: phẳng, đi lên đều, hay bậc thang? Giải thích điều bạn thấy.
  • Xác định breaking point. Với một màn hình danh sách, tăng dần số item (100 → 500 → 1.000 → 5.000) và ghi lại mức bộ nhớ + FPS tại mỗi mốc. Vẽ đồ thị và chỉ ra điểm app bắt đầu xuống cấp rõ rệt.

Tóm tắt

Stress test và soak test là hai loại kiểm thử tìm ra lớp bug mà functional test không bao giờ chạm tới. Stress test đẩy app vượt giới hạn không gian — quá nhiều dữ liệu, quá nhiều tap, quá nhiều thao tác đồng thời — để tìm điểm gãy và xem app gãy duyên dáng hay thảm khốc. Soak test đẩy app vượt giới hạn thời gian — chạy liên tục nhiều giờ, nhiều ngày — để phát hiện các vấn đề tích lũy như memory leak, cache phình, và xuống cấp hiệu năng.

Ba nguyên tắc cần khắc cốt ghi tâm: (1) test trên thiết bị yếu nhất còn hỗ trợ, vì bug tài nguyên lộ ra ở đó trước; (2) đọc đường cong theo thời gian chứ không đọc một điểm; (3) luôn ghi lại con số cụ thể của điểm gãy để dev tái hiện được. Từ tài xế GoRide bị tự thoát lúc 3 giờ sáng, đến đơn hàng trùng của ChợViệt ngày sale, tất cả đều là những bug mà chỉ khi đẩy app tới cực hạn mới hiện nguyên hình — và đó chính là giá trị mà một QA mobile chuyên nghiệp mang lại.

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