Uygulamanız düzinelerce gerçek telefonda, farklı üreticilerde, Android sürümlerinde ve ekran çözünürlüklerinde çalışmak zorunda olduğunda, manuel test hızla bir kabusa dönüşür. Aşağıda, bağlı tüm cihazları kendi kendine keşfeden, APK'yı hepsine paralel olarak yükleyen, enstrümante testleri çalıştıran, her çalıştırmanın videosunu kaydeden ve bu kayıtları FFmpeg ile tek bir video raporunda birleştiren bir Python hattı bulacaksınız.
Tüm yığın > Python + ADB + FFmpeg < standart QA araçlarıdır. Sihir yok, sadece angaryayı otomatikleştirmek.

Hat mimarisi
1adb devices ──► seri numaraları listesi2 │3 ▼4APK yükleme (tüm cihazlarda paralel olarak, ThreadPoolExecutor ile)5 │6 ▼7her cihaz için:8 screenrecord (arka planda) → am instrument (testleri çalıştır) → durdur + videoyu çek9 │10 ▼11FFmpeg: her klibe seri numarasını ekle + birleştir ──► test_report.mp4
İhtiyacınız olacaklar
- ADB (Android Debug Bridge) Android Platform Tools'tan -> cihaz kontrolü.
- Python 3.10+ -> orkestrasyon (from __future__ olmadan list[str], tuple[...] kullanıyorum).
- FFmpeg -> video işleme ve birleştirme.
- USB hata ayıklaması etkinleştirilmiş, USB üzerinden (veya adb tcpip ile Wi-Fi üzerinden) bağlı cihazlar.
Tüm kod boyunca geçerli olan bir ilke: alt işlemlere argümanları bir liste olarak ve shell=True olmadan iletiyorum. Bu daha güvenlidir (dosya adları üzerinden enjeksiyon olmaz) ve yollardaki boşluklar veya özel karakterlerde sorun çıkarmaz.
1. Cihaz keşfi

adb devices ayrıca yetkisiz / çevrimdışı durumdaki cihazları da listeler. Yalnızca gerçekten 'device' durumunda olanları tutuyoruz.
1import subprocess23def get_devices() -> list[str]:4 """'device' durumundaki tüm cihazların seri numaralarını döndürür."""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:]: # ilk satır "List of devices" başlığıdır12 line = line.strip()13 if line.endswith("\tdevice"): # yetkisiz / çevrimdışı olanları atla14 serials.append(line.split("\t")[0])15 return serials
2. Paralel APK yükleme
50 cihaza tek tek kurulum yapmak yavaştır. İşi bir iş parçacığı havuzuna dağıtıyoruz: her adb install ayrı bir süreçtir, bu nedenle iş parçacıkları burada harika çalışır (CPU yakmak yerine G/Ç bekliyoruz).
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}")
Bayraklar: -r -> verileri koruyarak yeniden yükle, -g -> tüm çalışma zamanı izinlerini hemen ver (testlerin izin diyaloglarına takılmaması için kullanışlıdır).
3. Enstrümante testleri çalıştırma
am instrument, cihazda Espresso/JUnit testlerini çalıştırır. Başarı durumunda OK, başarısızlık durumunda FAILURES!!! yazdırır -> sonucu bu şekilde belirleriz.
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, test paketi kimliğidir, genellikle com.example.app.test.
4. Test sırasında ekranı kaydetme
screenrecord, videoyu doğrudan cihazda kaydeder. Akılda tutulması gereken sınırlamalar: dosya başına yaklaşık 3 dakikalık bir sınır ve ses yok. Kaydı arka planda başlatır, testi çalıştırır, ardından temiz bir şekilde durdurur ve dosyayı ana bilgisayara çekeriz.
Kaydı durdurmanın en güvenilir yolu, yerel adb'ye sinyal göndermek değil, cihazın kendisinde pkill kullanmaktır -> bu şekilde screenrecord MP4 kabını doğru şekilde sonlandırır.
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 # Cihazdaki SIGINT, screenrecord'un dosyayı düzgün şekilde kapatmasını sağlar15 subprocess.run(["adb", "-s", serial, "shell", "pkill", "-SIGINT", "screenrecord"])16 proc.wait(timeout=10)17 time.sleep(1) # cihaza kabı sonlandırması için bir an ver18 subprocess.run(["adb", "-s", serial, "pull", remote, local], check=True)
5. FFmpeg ile video raporunu birleştirme
Farklı cihazların farklı ekran çözünürlükleri vardır, bu nedenle bunları -c copy ile birleştiremezsiniz. Her klibi ortak bir formata (1080×1920) normalleştirir ve bu arada drawtext ile seri numarasını üst üste bindiririz. Bundan sonra tüm klipler aynıdır ve son birleştirme, yeniden kodlama gerektirmeyen hızlı bir concat işlemidir.
1def label_clip(src: str, dst: str, label: str) -> None:2 """1080x1920'ye ölçekle ve bir etiket (cihaz seri numarası) ekle."""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. Hepsini bir araya getirmek
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("Cihaz bulunamadı. USB'yi ve 'adb devices' çıktısını kontrol edin.")9 return1011 print(f"Bulunan cihaz sayısı: {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} üzerinde testler")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("Tamamlandı: test_report.mp4")2728if __name__ == "__main__":29 main()
Burada "kaydet + test" döngüsü cihazlar arasında sırayla çalışır -> bu şekilde daha okunabilirdir. Gerçek bir cihaz çiftliği için bu bloğu da bir ThreadPoolExecutor'a sarmak istersiniz, böylece tüm cihazlar aynı anda test edilir; mantık bölüm 2'deki kurulumla aynıdır.
Tekerleği yeniden icat etmeyin: hazır araçlar

- scrcpy -> bir cihazı PC'nizden gerçek zamanlı olarak yansıtma ve kontrol etme. Başarısız testlerde hata ayıklarken vazgeçilmezdir.
- Appium / Espresso / UI Automator -> tam teşekküllü UI test çerçeveleri; yukarıdaki am instrument onların motorudur.
- Gradle Managed Devices -> testleri doğrudan derlemeden emülatörlerde çalıştırın, manuel ADB uğraşması yok.
- Firebase Test Lab / AWS Device Farm -> kendi donanımınızı bulundurmak istemiyorsanız bulut tabanlı gerçek cihaz filosu.
- GNU parallel -> Python yerine bash'tan orkestrasyon yapmayı tercih ediyorsanız.
Ekonomi: maliyeti ve tasarrufu
Test otomasyonu "havadan para kazanmak"la ilgili değildir -> en pahalı iki kalemi kesmekle ilgilidir: insan-saati ve bulut dakikası. Aşağıda üç tipik ölçekte bir tahmin bulunmaktadır. Rakamlar gösterge niteliğindedir ve bölgeye, cihaz satıcısına ve sağlayıcı fiyatlandırmasına bağlıdır; satın almadan önce güncel oranları kontrol edin.
Kendi çiftliğiniz -> tek seferlik yatırım
Öğe
10 cihaz için
30 cihaz için
50 cihaz için
Kullanılmış Android telefonlar (~$60/adet)
~$600
~$1,800
~$3,000
Güçlü USB hub'lar
~$100
~$250
~$400
Mini-PC / ana bilgisayar
~$400
~$400
~$500
Kablolar, raf, diğer malzemeler
~$80
~$150
~$250
Tek seferlik toplam
~$1,200
~$2,600
~$4,150
Aylık elektrik
yok denecek kadar az
~$10–20
~$20–40
Bu bir sermaye harcamasıdır: bir kez ödeyin ve çiftlik yıllarca neredeyse hiç maliyetsiz çalışsın.
Bulut -> dakika başına ödeme yaparsınız
Firebase Test Lab, AWS Device Farm, BrowserStack ve benzerleri, cihaz-dakikası başına yaklaşık $0.05–0.20 ücret alır. 30 cihazda her biri 5 dakika süren tek bir regresyon çalıştırması, 150 cihaz-dakikasıdır, yani çalıştırma başına ~$7.5–30.
Şimdi CI sıklığıyla çarpın:
Çalıştırma sıklığı
Aylık çalıştırma sayısı
Maliyet (~$15/çalıştırma)
Günde 2× kez
~44
~$660/ay
Günde 10× kez
~220
~$3,300/ay
Her push'ta (aktif ekip)
500+
$7,500+/ay
Başabaş noktası: 30 cihazlı bir çiftlik (~$2,600), mütevazı bir günde 2 çalıştırma ile yaklaşık 4 ayda kendini amorti eder; aktif CI ile bir aydan kısa sürede. Ondan sonra bulut faturası her ay akmaya devam ederken, çiftlik akmaz.
Manuel iş gücü -> serbest kalan zaman
30 cihazda tek bir regresyon senaryosunun manuel olarak çalıştırılması, yaklaşık bir QA mühendisinin bir iş günü kadardır. Haftada iki kez regresyon çalıştırmak, ayda yaklaşık 8 kişi-gününe tekabül eder. Yaklaşık $1,600–2,700/ay olan bir QA maliyetiyle, bu, hattın "bağla–kur–dokun–kaydet" angaryasını ×30 yerine anlamlı işler için serbest bıraktığı maaşın önemli bir kısmıdır.
Bunun paraya nasıl dönüştüğü
Burada "komut dosyasından" doğrudan bir gelir yoktur -> ancak üç dolaylı ama çok gerçek mekanizma vardır:
- Daha hızlı sürümler. Bir gün yerine dakikalar içinde regresyon → özellikleri daha sık yayınlarsınız → pazara daha hızlı yanıt verirsiniz. Bir abonelik ürünü için bu, doğrudan elde tutma ve gelire bağlanır.
- Üretimde daha az hata. Belirli bir Samsung'ta bir çökmeyi sürümden önce yakalamak kuruşlara mal olur; aynı çökmenin kullanıcılara ulaşması, düşen mağaza puanlamaları, kayıp ve iadeler anlamına gelir. Erken yakalanan her hata, asla yazılmayan bir avuç olumsuz yorumdur.
- Bir hizmet olarak satılır. Bir cihaz çiftliği kurmak ve CI testi yapmak, serbest çalışma ve dış kaynak kullanımında aranan bir roldür. Yukarıdaki hat, böyle bir teklif için hazır bir çekirdektir.
"Etkileşim çiftçiliği planlarından" temel fark: orada para, algoritmaları aldatmaktan gelir ve bir yasakla biter. Burada, kurtarılan saatlerden ve önlenen kayıplardan gelir. Birincisi çöker; ikincisi, bir müşteriye göstermekten utanmayacağınız sürdürülebilir bir iş senaryosudur.
Toparlama
Sonuç, tekrarlanabilir bir hattır: tek bir komut ve uygulama, tüm cihaz filosunda test edilir ve sonunda her bir cihazdaki davranışı gösteren bir video raporu elde edilir.





