Chương 9: Hướng Dẫn Test, Debug & Bảng Lỗi Thường Gặp
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
F1trên bàn phím. - Trên thiết bị di động thật (Android / iOS): Chạm đồng thời 3 ngón tay vào màn hình (phát hiện chạm ngón thứ ba bắt đầu:
Input.touchCount == 3 && Input.GetTouch(2).phase == TouchPhase.Began). - Cơ chế bảo vệ bản Release: Trong hàm
Awake(), panel kiểm tra điều kiện an toàn:
Trong bản build Release đưa lên Store, panel sẽ tự hủy hoàn toàn. Nhờ đó, phương thức// 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);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ừUserIdentityvà 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 trongInitPipeline.[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ằngLocalStorage.[Scene]: Quá trình nạp và chuyển cảnh có hiệu ứng fade bằngSceneLoader.[Dialog]: Trạng thái nạp prefab và mở/đóng popup trongDialogService.
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
[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 sangHome.
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
- Mở dự án Xcode được xuất từ Unity.
- Vào menu: Product → Scheme → Edit Scheme... (hoặc nhấn
Cmd + <). - Tại cột bên trái chọn mục Run, chuyển sang tab Arguments.
- Tại phần Arguments Passed On Launch, nhấn dấu
+và thêm đối số:-FIRDebugEnabled - (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
- Bật DebugServicePanel (nhấn F1 hoặc chạm 3 ngón tay), sau đó nhấn nút Mediation Debugger.
- 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 testProductIdtrê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(trongGameWallet.GrantProduct). Bạn kiểm tra xem số coin có tăng lên và được lưu vàoLocalStoragekhông. - Cancel: Giả lập người dùng bấm hủy. Callback nhận kết quả
PurchaseResult.Cancelled.
- 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
Test trên thiết bị thật với tài khoản Sandbox
- 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í".
- 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!
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:
- 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...). - Khi hết timeout, mỗi bước ghi log warning:
[Boot] <step> timed out after Xs, continuingvà lập tức nhường quyền cho bước tiếp theo. - Sau khi chạy hết pipeline và đủ
minLoadingSeconds(1.5s), màn hình Loading mượt mà mở sceneHome. Tuyệt đối không crash, không đứng hình vô hạn! - 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). - 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 outhoặ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
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 trongCoreConfig.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:
- Kiểm tra biên dịch sạch (Clean Compile):
- Nhấn
Ctrl + Rtrong Unity để nạp lại script. Console không được xuất hiện bất kỳ dòng lỗierror CSnào.
- Nhấn
- Chạy từ Scene Loading:
- Mở scene
Assets/_Project/Scenes/Loading.unityvà nhấn Play. - Kiểm tra Console: Xuất hiện đầy đủ log
[Boot] <step> donehoặctimed outcho từng bước, kết thúc bằng dòng[Boot] Completed, user=...và sự kiệnhome_open. Màn hình Loading chuyển sang Home mượt mà.
- Mở scene
- Kiểm tra vòng lặp Gameplay (Gameplay Loop):
- Từ
Homenhấn nút Play → Chuyển sangMain(log xuất hiệnlevel_start). - Nhấn nút WIN hoặc LOSE mẫu → Popup
ResultDialogmở 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
MainhoặcHomerồi nhấn Play, game vẫn phải khởi động bình thường không crash!
- Từ
- Kiểm tra Debug Panel (F1):
- Nhấn
F1mở 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.
- Nhấn
- 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 trongGameAnalyticsSink). - Không có
static .* Instance(Singleton). - Không có
SceneManager.LoadScene(bắt buộc dùngApp.Scenes.Load). - Không có key hardcode như
ca-app-pub-.... - Không có LINQ hay
GetComponenttrong hàmUpdate().
- Tìm kiếm trong toàn bộ thư mục