TapBrain Template

Chương 9: Hướng Dẫn Test, Debug & Bảng Lỗi Thường Gặp

🎯 Mục tiêu chương này

Khi phát triển game mobile, việc kiểm thử và khoanh vùng lỗi không chỉ dừng lại ở Unity Editor mà bắt buộc phải diễn ra trên thiết bị thật (Android và iOS). Chương này cung cấp công cụ kiểm thử nhanh không cần dựng UI (DebugServicePanel), hướng dẫn đọc hiểu log khởi động, kỹ thuật test chuyên sâu cho Firebase, AppLovin MAX, IAP, chế độ ngoại tuyến (Offline), và bảng tra cứu lỗi thực tế mở rộng từ tài liệu vận hành.

1. Bảng điều khiển Debug Panel (DebugServicePanel)

Để hỗ trợ smoke-test nhanh mọi service cốt lõi ngay trên máy thật mà không phải mất thời gian kéo thả nút bấm UI, template tích hợp sẵn component DebugServicePanel (đặt tại Assets/_Project/Game/UI/DebugServicePanel.cs).

Cách kích hoạt và điều kiện hiển thị

  • Trong Unity Editor: Nhấn phím F1 trên bàn phím.
  • Trên thiết bị di động thật (Android / iOS): Chạm nhanh 5 lần vào góc trên bên trái màn hình (trong vòng 2 giây, vùng 15% góc màn hình). Chỉ cần 1 ngón: template đặt Input.multiTouchEnabled = false nên mọi cử chỉ nhiều ngón đều không hoạt động trên máy thật.
  • Cơ chế bảo vệ bản Release: Trong hàm Awake(), panel kiểm tra điều kiện an toàn:
    // Visible only in Editor / development builds and when showDebugPanel is true
    bool allowed = (Debug.isDebugBuild || Application.isEditor) 
        && Bootstrapper.Config != null 
        && Bootstrapper.Config.showDebugPanel;
    if (!allowed) Destroy(this);
    Trong bản build Release đưa lên Store, panel sẽ tự hủy hoàn toàn. Nhờ đó, phương thức OnGUI() không gây phân bổ bộ nhớ rác (GC allocation) trong bản phát hành chính thức.

Mô tả chi tiết các nút bấm trên Debug Panel

Giao diện panel hiển thị trạng thái tổng quan ở đầu:

  • Dòng định danh: User {UserId} | Status: {_status} — Hiển thị User ID dùng chung từ UserIdentity và kết quả của lệnh vừa bấm.
  • Dòng trạng thái Service: Ads init={IsInitialized} inter={IsInterstitialReady} rewarded={IsRewardedReady} | IAP={IsReady}.
Nút bấm trên Panel Lệnh C# thực thi Tác dụng & Hành vi kiểm thử
Show Interstitial ads.TryShowInterstitial("debug") Yêu cầu hiển thị quảng cáo xen kẽ với placement "debug". Trả về inter shown nếu thỏa mãn chính sách AdsPolicy (đủ 30s khoảng cách, đã qua 60s đầu, chưa mua Remove Ads), ngược lại trả về inter skipped.
Show Rewarded ads.ShowRewarded("debug", r => ...) Phát video nhận thưởng. Khi đóng video, cập nhật _status với kết quả: Rewarded, Skipped, NotReady hoặc Failed.
Show Banner ads.ShowBanner() Gọi hiển thị banner ở cạnh dưới màn hình (Bottom Center). Giúp kiểm tra kích thước và vùng hiển thị an toàn.
Hide Banner ads.HideBanner() Ẩn banner đang hiển thị khỏi màn hình.
Mediation Debugger ads.ShowMediationDebugger() Mở giao diện Mediation Debugger chính thức của AppLovin MAX để kiểm tra trạng thái SDK và các adapter của mạng quảng cáo.
Buy {testProductId} iap.Purchase(testProductId, r => ...) Mặc định mua gói com.tapbrain.template.coins_small. Trong Editor sẽ mở popup Fake Store; trên máy thật kết nối tới tài khoản Sandbox của Google/Apple.
Restore iap.Restore(ok => ...) Khôi phục các giao dịch Non-consumable (như gói xóa quảng cáo). Rất quan trọng khi kiểm tra tuân thủ điều kiện duyệt ứng dụng của Apple App Store.
Log test event App.Analytics.LogEvent("debug_test", ...) Gửi event phân tích thử nghiệm kèm tham số value = 1 tới tất cả các sink đang hoạt động (Firebase, GameAnalytics, AppMetrica).
Fetch Remote Config host.Run(App.RemoteConfig.Fetch()) Kích hoạt coroutine tải cấu hình mới từ Firebase Remote Config và kích hoạt giá trị vừa nhận.
Haptic Medium App.Haptic.Play(HapticType.Medium) Kiểm tra phản hồi rung xúc giác native (UIFeedbackGenerator trên iOS hoặc VibrationEffect trên Android).
Request Review App.Review.RequestReview() Yêu cầu popup đánh giá in-app native (Google Play In-App Review hoặc iOS StoreKit Review).
Toast App.Overlay.Toast("Hello from Core") Kiểm tra hệ thống hiển thị thông báo nổi Toast trên canvas lớp phủ UIOverlay.
Close _visible = false Đóng panel giao diện debug.

2. Hệ thống Log Console & Cách đọc Boot Log

Template sử dụng class tĩnh CoreLog (đặt tại Assets/_Project/Core/Infra/CoreLog.cs) để kiểm soát việc xuất log có cấu trúc:

// CoreLog.cs
public static class CoreLog
{
    public static bool Verbose { get; set; } = true;
    public static void Info(string tag, string message)
    {
        if (Verbose) Debug.Log($"[{tag}] {message}");
    }
    public static void Warn(string tag, string message) => Debug.LogWarning($"[{tag}] {message}");
    public static void Error(string tag, string message) => Debug.LogError($"[{tag}] {message}");
}

Quy tắc: Biến Verbose chỉ bật khi CoreConfig.verboseLog == true hoặc đang chạy bản Development Build. Trong bản Release, các log Info bị ẩn để tối ưu hiệu năng CPU, nhưng Warn và Error vẫn luôn xuất hiện để Firebase Crashlytics có thể bắt được ngữ cảnh lỗi.

Các tiền tố (Tag) chuẩn trong Console

  • [Boot]: Quá trình khởi tạo tuần tự các service trong InitPipeline.
  • [Ads]: Vận hành quảng cáo MAX (khởi tạo, load, show, skip do cooldown policy).
  • [IAP]: Khởi tạo catalog, kết nối store, kết quả mua hàng và khôi phục giao dịch.
  • [Analytics]: Ghi nhận sự kiện phân tích và chuyển tiếp tới các sink.
  • [Firebase], [RemoteConfig], [RTDB], [AppUpdate], [ASA] (Apple Search Ads): Trạng thái kết nối dịch vụ backend và nền tảng.
  • [Storage]: Thao tác đọc/ghi dữ liệu cục bộ bằng LocalStorage.
  • [Scene]: Quá trình nạp và chuyển cảnh có hiệu ứng fade bằng SceneLoader.
  • [Dialog]: Trạng thái nạp prefab và mở/đóng popup trong DialogService.

Cách đọc và phân tích luồng Boot Log

Mỗi bước khởi động trong InitPipeline đều được giám sát thời gian thực thi (time-boxed) độc lập. Khi xem cửa sổ Console, bạn sẽ thấy chuỗi log khởi động mẫu như sau:

[Boot] ATT done in 0.01s
[Boot] Firebase done in 0.32s
[Boot] Auth timed out after 5s, continuing
[Boot] RemoteConfig done in 0.45s
[Boot] RealtimeDb+BI done in 0.12s
[Boot] Ads done in 0.85s
[Boot] GameAnalytics+AppMetrica done in 0.05s
[Boot] IAP done in 1.10s
[Boot] Notifications done in 0.02s
[Boot] Background done in 0.00s
[Boot] Completed, user=17937590557361 new=True session=1
[Analytics] home_open
💡 Giải mã ý nghĩa luồng Log
  • [Boot] <Tên> done in X.XXs: Bước đó hoàn thành tốt đẹp trong khoảng thời gian cho phép.
  • [Boot] <Tên> timed out after Xs, continuing: Bước đó chạy quá thời gian tối đa (do mất mạng hoặc server chậm). Pipeline tự động ngắt và bỏ qua để game tiếp tục chạy, đảm bảo không bao giờ bị treo cứng ở màn hình loading!
  • [Boot] Completed, user=...: Báo hiệu toàn bộ pipeline đã xong, sẵn sàng chuyển cảnh sang Home.

3. Kiểm tra Analytics thời gian thực với Firebase DebugView

Theo mặc định để tiết kiệm pin trên thiết bị di động, Firebase Analytics gom các sự kiện thành các gói (batch) và chỉ gửi lên máy chủ sau mỗi 1 giờ. Khi lập trình, bạn không thể chờ 1 tiếng để biết sự kiện có được ghi nhận đúng tham số hay không. Tính năng Firebase DebugView cho phép bạn theo dõi luồng sự kiện theo thời gian thực (độ trễ chỉ vài giây).

Kích hoạt trên thiết bị Android qua ADB

Kết nối điện thoại Android với máy tính qua cáp USB (đã bật USB Debugging), mở PowerShell hoặc Terminal và chạy lệnh:

# Kích hoạt chế độ debug cho ứng dụng của bạn
adb shell setprop debug.firebase.analytics.app <bundle_id_cua_game>

# Ví dụ cụ thể với template:
adb shell setprop debug.firebase.analytics.app com.tapbrain.template

# Sau khi hoàn tất kiểm thử, tắt chế độ debug bằng lệnh:
adb shell setprop debug.firebase.analytics.app .none.

Kích hoạt trên thiết bị iOS trong Xcode

  1. Mở dự án Xcode được xuất từ Unity.
  2. Vào menu: Product → Scheme → Edit Scheme... (hoặc nhấn Cmd + <).
  3. Tại cột bên trái chọn mục Run, chuyển sang tab Arguments.
  4. Tại phần Arguments Passed On Launch, nhấn dấu + và thêm đối số:
    -FIRDebugEnabled
  5. (Khi muốn tắt chế độ debug, bạn có thể xóa cờ trên hoặc thêm cờ -FIRDebugDisabled).

Quan sát trên Firebase Console

Mở trình duyệt truy cập Firebase Console → vào Project của game → chọn mục Analytics → DebugView. Trong danh sách "Debug Device" ở góc trên bên trái, chọn thiết bị của bạn. Khi bạn chơi game trên máy, toàn bộ các sự kiện như main_open, level_start, level_complete cùng các tham số (level, score, time_spent) sẽ hiển thị trực quan theo từng giây.

4. Kiểm tra mạng quảng cáo với MAX Mediation Debugger

AppLovin MAX Mediation Debugger là công cụ trực quan tối quan trọng giúp bạn kiểm tra tính hợp lệ của việc tích hợp quảng cáo mediation trên thiết bị di động thật.

Cách mở Mediation Debugger

  1. Bật DebugServicePanel (nhấn F1 hoặc chạm nhanh 5 lần vào góc trên bên trái), sau đó nhấn nút Mediation Debugger.
  2. Hoặc gọi trực tiếp từ code C#:
    App.Ads.ShowMediationDebugger();

Các hạng mục cần kiểm tra trong màn hình Debugger

  • AppLovin SDK Key: Hiển thị trạng thái màu xanh lá (Valid).
  • Mạng quảng cáo (Ad Networks & Adapters): Danh sách các mạng (Google AdMob, Mintegral, Unity Ads...). Đảm bảo adapter tương ứng hiện màu xanh lá kèm phiên bản SDK hỗ trợ. Nếu hiện màu đỏ, bạn đang thiếu file thư viện hoặc chưa chạy Force Resolve trong EDM.
  • Chế độ Test Mode: Bạn có thể bật tính năng "Test Mode" cho từng mạng trực tiếp trong màn hình này để ép buộc ad network luôn trả về quảng cáo mẫu, tránh bị khóa tài khoản quảng cáo do tự xem quảng cáo thật (self-clicking).
  • Quyền riêng tư (Privacy & Consent): Kiểm tra trạng thái CMP/GDPR và quyền ATT trên iOS (Authorized hay Denied).

5. Quy trình kiểm tra In-App Purchasing (IAP)

Test trong Unity Editor với Fake Store

Unity IAP tích hợp sẵn hệ thống giả lập Fake Store khi chạy trong Unity Editor:

  • Khi nhấn nút mua gói IAP (ví dụ nút Buy testProductId trên Debug Panel), một cửa sổ popup native của Unity sẽ xuất hiện đè lên Game View.
  • Cửa sổ có 2 lựa chọn:
    • Buy: Giả lập thanh toán thành công. Unity IAP sẽ gửi biên lai giả lập và gọi tới PurchaseGrantHandler (trong GameWallet.GrantProduct). Bạn kiểm tra xem số coin có tăng lên và được lưu vào LocalStorage không.
    • Cancel: Giả lập người dùng bấm hủy. Callback nhận kết quả PurchaseResult.Cancelled.

Test trên thiết bị thật với tài khoản Sandbox

  1. Trên Android (Google Play):
    • Thêm tài khoản Gmail của tester vào mục Setup → License Testing trên Google Play Console.
    • Chọn loại phản hồi: RESPOND_NORMALLY.
    • Tải bản build thông qua đường dẫn Internal Testing Track. Khi thực hiện mua, popup Google Play sẽ có thông báo: "Đây là giao dịch thử nghiệm, bạn sẽ không bị tính phí".
  2. Trên iOS (Apple App Store):
    • Tạo một tài khoản Sandbox Tester trong trang App Store Connect (mục Users and Access → Sandbox Testers).
    • Trên iPhone/iPad test, vào Settings → App Store → Sandbox Account và đăng nhập tài khoản tester vừa tạo (không đăng nhập vào iCloud chính của máy).
    • Khi bấm mua trong game, hộp thoại xác nhận của Apple sẽ hiển thị nhãn [Environment: Sandbox].

6. Kiểm nghiệm khả năng chơi Offline (Mất mạng hoàn toàn)

Một tiêu chuẩn chất lượng bắt buộc của TapBrain là: Game phải chơi được hoàn toàn trơn tru khi không có kết nối Internet!

🧪 Bài test ngắt mạng (Airplane Mode Smoke-test)

Hãy bật chế độ máy bay (Airplane Mode) trên điện thoại hoặc ngắt kết nối mạng trên máy tính rồi nhấn Play từ scene Loading. Quá trình kiểm nghiệm sẽ diễn ra như sau:

  1. Tất cả các bước cần mạng (Firebase, Anonymous Auth, Remote Config, RealtimeDb, Ads, IAP) sẽ chạm mốc timeout tối đa (được khai báo trong CoreConfig: 8s, 4s, 6s...).
  2. Khi hết timeout, mỗi bước ghi log warning: [Boot] <step> timed out after Xs, continuing và lập tức nhường quyền cho bước tiếp theo.
  3. Sau khi chạy hết pipeline và đủ minLoadingSeconds (1.5s), màn hình Loading mượt mà mở scene Home. Tuyệt đối không crash, không đứng hình vô hạn!
  4. Hệ thống tự động sử dụng bộ giá trị offline mặc định trong CoreConfig.remoteConfigDefaults (ví dụ: inter_interval_sec = 30, inter_first_delay_sec = 60).
  5. Trạng thái quảng cáo báo not ready. Nút xem video nhận thưởng có thể ẩn đi hoặc thông báo mất kết nối một cách thân thiện.

7. Bảng tra cứu sự cố & Cách khắc phục toàn diện

Bảng tổng hợp chi tiết từ thực tế vận hành dự án, giúp bạn chẩn đoán nhanh nguyên nhân và cách xử lý khi gặp log cảnh báo hoặc lỗi:

Hiện tượng / Thông báo Log Nguyên nhân gốc rễ Các bước khắc phục chi tiết
Auth timed out
hoặc restricted to administrators
Chưa bật tính năng đăng nhập ẩn danh (Anonymous Authentication) trong Firebase Project của game. Mở Firebase Console → Authentication → tab Sign-in method → Tìm mục Anonymous và nhấn Enable.
RTDB Set ... failed: Internal task faulted Chưa tạo Realtime Database trên Firebase Console, hoặc luật bảo mật (Rules) đang chặn quyền ghi của ứng dụng. Vào Firebase Console → Realtime Database → Nhấn Create Database. Chuyển sang tab Rules và cấu hình cho phép ghi khi đã xác thực: ".write": "auth != null".
MAX SDK key not set in CoreConfig Trường maxSdkKey trong CoreConfig.asset vẫn đang giữ chuỗi mặc định REPLACE_ME. Nhấn menu TapBrain → Select Core Config, điền AppLovin MAX SDK Key cho cả hai nền tảng Android và iOS. Đồng thời kiểm tra cấu hình trong AppLovin → Integration Manager.
AppMetrica disabled Trường appMetricaApiKey trong CoreConfig.asset còn là REPLACE_ME. Lấy API Key từ dashboard AppMetrica và điền vào trường tương ứng trong CoreConfig.asset. Nếu game không dùng AppMetrica, bỏ tích enableAppMetrica.
Invalid receipt ... not granted Khóa giải mã hóa đơn (Receipt Validation Tangle) bị sai, thường do dùng lại tangle của game cũ sau khi clone repo. Mở menu Services → In-App Purchasing → Receipt Validation Obfuscator. Dán chuỗi Google Play Public License Key mới của game vào và nhấn Obfuscate để sinh lại code giải mã.
[IAP] ... kept pending Game chưa đăng ký hàm cấp phát vật phẩm (SetGrantHandler), hoặc hàm handler đang trả về false. Kiểm tra trong GameEntry.cs xem đã gọi App.Iap.SetGrantHandler(wallet.GrantProduct) chưa. Kiểm tra logic switch-case trong GrantProduct, đảm bảo Product ID khớp 100% với IAP Catalog và hàm trả về true sau khi lưu.
Missing prefab Resources/Dialogs/X Tên file prefab trong thư mục Resources/Dialogs/ không trùng khớp với tên class C# kế thừa DialogBase. Đổi tên file prefab sao cho trùng khít 100% với tên class (ví dụ class ShopDialog thì prefab bắt buộc phải là Assets/_Project/Resources/Dialogs/ShopDialog.prefab).
Service X is not registered Gọi hàm ServiceLocator.Get<T>() trước khi service đó được khởi tạo, hoặc quên đăng ký service trong GameEntry. Đảm bảo mọi game service được đăng ký trong GameEntry.Init(). Nếu service có thể chưa sẵn sàng ở thời điểm gọi, hãy sử dụng ServiceLocator.TryGet<T>(out var s) để kiểm tra an toàn.
Curl error 35: Cert verify failed (Trong Editor) Lỗi xác thực chứng chỉ SSL trong môi trường mạng nội bộ của máy phát triển (do proxy hoặc tường lửa công ty). Lỗi này chỉ xuất hiện cục bộ trong Unity Editor của một số máy dev và hoàn toàn không ảnh hưởng đến các bản build chạy trên thiết bị thật.
Build release báo lỗi:
[CoreConfig] Placeholders left
Chốt chặn build (Build Guard trong CoreConfigTools.cs) phát hiện dự án vẫn còn trường giữ chỗ REPLACE_ME hoặc Bundle ID mẫu. Chạy menu TapBrain → Validate Core Config để xem danh sách các trường chưa điền. Điền đầy đủ Store ID, Package Name, SDK Key thật trước khi xuất bản bản Release.
Compile báo lỗi không tìm thấy:
DOTween hoặc GameAnalyticsILRD
Việc cập nhật SDK bên thứ ba đã ghi đè và xóa mất các file Assembly Definition (asmdef) phụ trợ mà template thiết lập. Tạo lại các file asmdef bị thiếu theo đúng vị trí quy định trong docs/AGENT_GUIDE.md: DOTween.Modules, GameAnalyticsSDK.ILRD và UnityPurchasing.Tangles.

8. Những hiện tượng BÌNH THƯỜNG trong Template mới

⚠️ Đừng hoảng hốt khi vừa mở template!

Khi bạn vừa clone repository mẫu của template và nhấn Play lần đầu tiên, trong cửa sổ Console chắc chắn sẽ xuất hiện một vài thông báo cảnh báo màu vàng. Theo docs/AGENT_GUIDE.md mục 8, đây là hành vi hoàn toàn bình thường và KHÔNG PHẢI LỖI BUG:

  • Auth timed out / restricted to administrators: Vì dự án Firebase mẫu kèm theo template đang tắt provider Anonymous Auth để bảo mật.
  • RTDB Set ... failed: Vì luật của Realtime Database yêu cầu phải có phiên đăng nhập hợp lệ (auth != null) mới cho phép ghi dữ liệu profile.
  • MAX SDK key not set in CoreConfig & AppMetrica disabled: Vì các trường cấu hình trong CoreConfig.asset đang để giá trị chuỗi giữ chỗ REPLACE_ME để tránh lộ key của công ty.

Sau khi bạn thực hiện các bước cấu hình dự án mới theo hướng dẫn tại Chương 7, toàn bộ các cảnh báo này sẽ tự động biến mất hoàn toàn!

9. Checklist Nghiệm Thu Thủ Công Sau Mỗi Lần Sửa Code

Trước khi gửi Pull Request hoặc chuyển giao tính năng cho tester, lập trình viên bắt buộc phải chạy qua 5 bước nghiệm thu vàng sau:

  1. Kiểm tra biên dịch sạch (Clean Compile):
    • Nhấn Ctrl + R trong Unity để nạp lại script. Console không được xuất hiện bất kỳ dòng lỗi error CS nào.
  2. Chạy từ Scene Loading:
    • Mở scene Assets/_Project/Scenes/Loading.unity và nhấn Play.
    • Kiểm tra Console: Xuất hiện đầy đủ log [Boot] <step> done hoặc timed out cho từng bước, kết thúc bằng dòng [Boot] Completed, user=... và sự kiện home_open. Màn hình Loading chuyển sang Home mượt mà.
  3. Kiểm tra vòng lặp Gameplay (Gameplay Loop):
    • Từ Home nhấn nút Play → Chuyển sang Main (log xuất hiện level_start).
    • Nhấn nút WIN hoặc LOSE mẫu → Popup ResultDialog mở lên với animation mượt mà.
    • Bấm Next Level hoặc Home → Chuyển cảnh chính xác, không văng bất kỳ Exception nào.
    • Kiểm tra đặc biệt: Mở trực tiếp scene Main hoặc Home rồi nhấn Play, game vẫn phải khởi động bình thường không crash!
  4. Kiểm tra Debug Panel (F1):
    • Nhấn F1 mở Debug Panel.
    • Kiểm tra dòng trạng thái: IAP=True, Ads init=True.
    • Bấm nút mua gói coin thử nghiệm → Fake Store popup hiện lên, bấm Buy và kiểm tra số coin tăng đúng 100.
  5. Quét mã nguồn cấm (Forbidden Patterns Audit):
    • Tìm kiếm trong toàn bộ thư mục Assets/_Project/ xem có vi phạm các anti-pattern bị cấm không:
    • Không có FindObjectOfType (chỉ cho phép duy nhất bên trong GameAnalyticsSink).
    • Không có static .* Instance (Singleton).
    • Không có SceneManager.LoadScene (bắt buộc dùng App.Scenes.Load).
    • Không có key hardcode như ca-app-pub-....
    • Không có LINQ hay GetComponent trong hàm Update().