當你的應用程式需要在數十台真實手機、不同廠商、Android 版本與螢幕解析度的「裝置動物園」中正常運作時,手動測試很快就會變成一場惡夢。以下是一個 Python 管道,它能自動發現所有已連接的裝置、在所有裝置上並行安裝 APK、執行儀器測試、錄製每次測試的影片,並透過 FFmpeg 將這些錄影拼接成單一影片報告。
整個技術棧 > Python + ADB + FFmpeg < 是標準的 QA 工具。沒有魔法,只是將繁瑣的工作自動化。

管道架構
1adb devices ──► 序號列表2 │3 ▼4APK 安裝 (在所有裝置上並行執行,使用 ThreadPoolExecutor)5 │6 ▼7針對每台裝置:8 螢幕錄製 (背景執行) → am instrument (執行測試) → 停止 + 拉取影片9 │10 ▼11FFmpeg:在每個片段上疊加序號 + 拼接 ──► test_report.mp4
你需要準備什麼
- ADB (Android Debug Bridge) 來自 Android Platform Tools -> 裝置控制。
- Python 3.10+ -> 編排 (我使用 list[str]、tuple[...] 而不需要 from __future__)。
- FFmpeg -> 影片處理與組裝。
- 已啟用 USB 偵錯 的裝置,透過 USB 連接 (或透過 adb tcpip 經由 Wi-Fi 連接)。
所有程式碼貫穿一個原則:我將參數以 列表 形式傳遞給 subprocess,並且不使用 shell=True。這樣更安全 (不會因檔案名稱造成注入攻擊),也不會因路徑中的空格或特殊字元而中斷。
1. 裝置發現

adb devices 也會列出處於 unauthorized / offline 狀態的裝置。我們只保留實際處於 device 狀態的裝置。
1import subprocess23def get_devices() -> list[str]:4 """回傳所有處於 'device' 狀態的裝置序號。"""5 out = subprocess.run(6 ["adb", "devices"],7 capture_output=True, text=True, check=True,8 ).stdout910 serials: list[str] = []11 for line in out.splitlines()[1:]: # 第一行是 "List of devices" 標題12 line = line.strip()13 if line.endswith("\tdevice"): # 排除 unauthorized / offline14 serials.append(line.split("\t")[0])15 return serials
2. 並行 APK 安裝
在一台接一台的 50 台裝置上安裝速度很慢。我們將工作分散到執行緒池中:每個 adb install 都是一個獨立的程序,因此執行緒在這裡運作得很好 (我們在等待 I/O,而不是消耗 CPU)。
1from concurrent.futures import ThreadPoolExecutor, as_completed23def install_apk(serial: str, apk_path: str) -> tuple[str, bool, str]:4 r = subprocess.run(5 ["adb", "-s", serial, "install", "-r", "-g", apk_path],6 capture_output=True, text=True,7 )8 ok = r.returncode == 0 and "Success" in r.stdout9 return serial, ok, (r.stdout + r.stderr).strip()1011def install_on_all(apk_path: str, serials: list[str]) -> None:12 with ThreadPoolExecutor(max_workers=len(serials) or 1) as pool:13 futures = [pool.submit(install_apk, s, apk_path) for s in serials]14 for f in as_completed(futures):15 serial, ok, log = f.result()16 print(f"[{'OK' if ok else 'FAIL'}] {serial}")17 if not ok:18 print(f" {log}")
標誌說明:-r -> 重新安裝並保留資料,-g -> 立即授予所有執行時期權限 (方便測試不會被權限對話框干擾)。
3. 執行儀器測試
am instrument 在裝置上執行 Espresso/JUnit 測試。成功時會輸出 OK,失敗時會輸出 FAILURES!!! 到 stdout -> 我們據此判斷結果。
1def run_instrumented_tests(2 serial: str,3 package: str,4 runner: str = "androidx.test.runner.AndroidJUnitRunner",5) -> tuple[str, bool]:6 r = subprocess.run(7 ["adb", "-s", serial, "shell", "am", "instrument", "-w",8 f"{package}/{runner}"],9 capture_output=True, text=True,10 )11 ok = "FAILURES!!!" not in r.stdout and r.returncode == 012 return serial, ok
package 是測試套件的 ID,通常是 com.example.app.test。
4. 在測試期間錄製螢幕
screenrecord 直接在裝置上錄製影片。需要注意的限制:每個檔案約 3 分鐘的上限,且沒有音訊。我們在背景開始錄製,執行測試,然後乾淨地停止錄製並將檔案拉取到主機。
停止錄製最可靠的方式不是透過本機 adb 發送訊號,而是在裝置本身上使用 pkill -> 這樣 screenrecord 才能正確完成 MP4 容器的封裝。
1import time23def start_recording(serial: str, remote: str = "/sdcard/run.mp4") -> subprocess.Popen:4 return subprocess.Popen(5 ["adb", "-s", serial, "shell", "screenrecord", remote]6 )78def stop_recording(9 serial: str,10 proc: subprocess.Popen,11 remote: str = "/sdcard/run.mp4",12 local: str = "run.mp4",13) -> None:14 # 在裝置上發送 SIGINT 讓 screenrecord 正確關閉檔案15 subprocess.run(["adb", "-s", serial, "shell", "pkill", "-SIGINT", "screenrecord"])16 proc.wait(timeout=10)17 time.sleep(1) # 給裝置一點時間完成容器封裝18 subprocess.run(["adb", "-s", serial, "pull", remote, local], check=True)
5. 使用 FFmpeg 組裝影片報告
不同裝置有不同的螢幕解析度,因此你不能直接用 -c copy 將它們拼接起來。我們將每個片段標準化為通用格式 (1080×1920),並在此過程中透過 drawtext 疊加序號。之後所有片段都相同,最終組裝時可以快速拼接,無需重新編碼。
1def label_clip(src: str, dst: str, label: str) -> None:2 """縮放至 1080x1920 並疊加標籤 (裝置序號)。"""3 vf = (4 "scale=1080:1920:force_original_aspect_ratio=decrease,"5 "pad=1080:1920:(ow-iw)/2:(oh-ih)/2,"6 f"drawtext=text='{label}':x=20:y=20:fontsize=42:"7 "fontcolor=white:box=1:[email protected]"8 )9 subprocess.run(10 ["ffmpeg", "-y", "-i", src, "-vf", vf,11 "-an", "-c:v", "libx264", "-preset", "veryfast", "-crf", "23", dst],12 check=True,13 )1415def concat_report(clips: list[str], out: str = "test_report.mp4") -> None:16 with open("concat_list.txt", "w") as f:17 for c in clips:18 f.write(f"file '{c}'\n")19 subprocess.run(20 ["ffmpeg", "-y", "-f", "concat", "-safe", "0",21 "-i", "concat_list.txt", "-c", "copy", out],22 check=True,23 )
6. 整合所有步驟
1def main() -> None:2 apk = "app-debug.apk"3 package = "com.example.app.test"4 runner = "androidx.test.runner.AndroidJUnitRunner"56 serials = get_devices()7 if not serials:8 print("未找到裝置。請檢查 USB 連接和 'adb devices' 的輸出。")9 return1011 print(f"找到裝置數量:{len(serials)}")12 install_on_all(apk, serials)1314 labeled: list[str] = []15 for serial in serials:16 proc = start_recording(serial)17 _, ok = run_instrumented_tests(serial, package, runner)18 stop_recording(serial, proc, local=f"{serial}.mp4")19 print(f"[{'PASS' if ok else 'FAIL'}] 在 {serial} 上的測試")2021 out = f"{serial}_labeled.mp4"22 label_clip(f"{serial}.mp4", out, serial)23 labeled.append(out)2425 concat_report(labeled, "test_report.mp4")26 print("完成:test_report.mp4")2728if __name__ == "__main__":29 main()
這裡的「錄製 + 測試」迴圈是依序在裝置上執行 -> 這樣更具可讀性。對於真正的裝置農場,你可能會想將這個區塊也包裝在 ThreadPoolExecutor 中,以便同時測試所有裝置;邏輯與第 2 節的安裝相同。
不要重新發明輪子:現成的工具

- scrcpy -> 從你的電腦即時鏡像和控制裝置。在除錯失敗的測試時不可或缺。
- Appium / Espresso / UI Automator -> 功能完整的 UI 測試框架;上述的 am instrument 就是它們的引擎。
- Gradle Managed Devices -> 直接從建置流程在模擬器上執行測試,無需手動操作 ADB。
- Firebase Test Lab / AWS Device Farm -> 如果你不想自己維護硬體,可以使用雲端真實裝置機群。
- GNU parallel -> 如果你更傾向於從 bash 而非 Python 進行編排。
經濟效益:成本與節省
測試自動化不是關於「憑空賺錢」-> 而是關於削減兩個最昂貴的項目:人力工時和雲端分鐘數。以下是三種典型規模的估算。數字僅供參考,取決於地區、裝置供應商和提供商定價,購買前請查詢當前費率。
你自己的裝置農場 -> 一次性投資
項目
10 台裝置
30 台裝置
50 台裝置
二手 Android 手機 (~每台 $60)
~$600
~$1,800
~$3,000
供電 USB 集線器
~$100
~$250
~$400
迷你 PC / 主機
~$400
~$400
~$500
線材、機架、雜項
~$80
~$150
~$250
一次性總計
~$1,200
~$2,600
~$4,150
每月電費
幾乎為零
~$10–20
~$20–40
這是資本支出:一次性支付,然後裝置農場可以運行數年,幾乎沒有額外成本。
雲端 -> 按分鐘付費
Firebase Test Lab、AWS Device Farm、BrowserStack 等服務按裝置分鐘收費 -> 大約 每裝置分鐘 $0.05–0.20。在 30 台裝置上執行一次回歸測試,每次 5 分鐘,就是 150 個裝置分鐘,即每次約 $7.5–30。
現在乘以 CI 的執行頻率:
執行頻率
每月執行次數
成本 (以每次 ~$15 計算)
每天 2 次
~44
~$660/月
每天 10 次
~220
~$3,300/月
每次推送 (活躍團隊)
500+
$7,500+/月
損益平衡點: 一個 30 台裝置的裝置農場 (~$2,600) 相對於雲端,在每天僅執行 2 次測試的適度使用下,大約 4 個月即可回本;對於活躍的 CI,則不到一個月。之後雲端帳單每個月持續累積,而裝置農場則不會。
人力 -> 被釋放的工作量
在 30 台裝置上手動執行一次單一回歸測試場景,大約需要 一位 QA 工程師的一個工作日。每週執行兩次回歸測試,每月累計約 8 個人天。以 QA 成本每月約 $1,600–2,700 計算,這是一筆可觀的薪資支出,而這個管道可以將其釋放出來,用於更有意義的工作,而不是 ×30 的「連接–安裝–點擊–記錄」苦工。
這如何轉化為金錢
這裡沒有「來自腳本」的直接收入 -> 但有三種間接且非常真實的機制:
- 更快的發布。 回歸測試從一天縮短到幾分鐘 → 你可以更頻繁地發布功能 → 更快地回應市場。對於訂閱制產品,這直接關係到留存率和收入。
- 更少的生產環境錯誤。 在發布前捕獲特定 Samsung 機型的崩潰只需幾美分;同樣的崩潰影響到用戶則意味著商店評分下降、用戶流失和退款。每個及早捕獲的錯誤都意味著一些永遠不會出現的負面評論。
- 它可以作為服務出售。 建立裝置農場和 CI 測試是自由職業和外包領域中需求旺盛的角色。上述管道是此類服務的現成核心。
與「互動農場計畫」的關鍵區別:在那裡,收入來自欺騙演算法,最終以封號告終。在這裡,收入來自節省的工時和避免的損失。前者會崩潰;後者是一個可持續的商業案例,你可以自豪地向客戶展示。
總結
結果是一個可重現的管道:一個命令,應用程式就能在整個裝置機群上進行測試,最終得到一個影片報告,顯示每台特定裝置上的行為。

![使用 Fable 5 與 note 在 6 個月內存下 500 萬日圓的完整流程 [附複製貼上提示詞]](/cdn-cgi/image/width=1920,quality=90,format=auto,metadata=none/https%3A%2F%2Fcms-assets.youmind.com%2Fmedia%2F1783533977044_mlxvvu_HMoBqNQaMAA7EsU.jpg)


![Claude Fable 5 如何讓我 3 分鐘內賺進 560,000 日圓 [內含 Prompt]](/cdn-cgi/image/width=1920,quality=90,format=auto,metadata=none/https%3A%2F%2Fcms-assets.youmind.com%2Fmedia%2F1783534184712_mr5ds6_HMn0yn-bIAA9fiR.jpg)
