Migrasi Vidio Android CI/CD dengan Jenkins, Google Compute Engine dan Genymotion Cloud
Tim Vidio Android melakukan migrasi infrastruktur CI/CD untuk memfasilitasi kami dalam melakukan TDD yang lebih cepat dan optimal. Tulisan ini akan membahas seperti apa dan bagaimana kami melakukannya.
Test-Driven Development dan CI/CD di Vidio Android
Dalam melakukan pekerjaan setiap harinya, tim Vidio Android menggunakan metode Test-Driven Development (TDD). Dengan kata lain, tes, baik itu unit test atau UI test, adalah bagian vital dari pekerjaan kami.
Seperti halnya di tim engineering manapun, kami membutuhkan sistem continuous integration (CI) dan continuous delivery (CD) untuk memfasilitasi pekerjaan kami.
Continuous integration dalam artian kode yang di-commit oleh para engineer harus secepat mungkin diintegrasikan di master branch, dan continuous delivery dalam artian kode yang telah di-commit secepat mungkin harus menghasilkan sebuah deliverable, atau dalam konteks kami, berbentuk aplikasi Vidio Android.
Vidio Android sendiri adalah sebuah project aplikasi Android yang modular, artinya dalam satu project Android tersebut, kami memecahnya menjadi beberapa module/library yang bertugas mengurusi fitur yang spesifik.
Gambar 1 di atas mengilustrasikan apa saja yang kami butuh lakukan di dalam sistem CI/CD. Kami me-maintain 5 modul: Vidio Android, Vidio Android TV, KMK Player (library video player in-house), Plenty (library analytics in-house), dan Stump (library logging in-house).
Semua modul tersebut (kecuali Android TV) di-maintain di repository yang terpisah, dan semua repository tersebut membutuhkan CI/CD yang akan menjalankan test dan membuat artifact. Selain itu, Test Engineer (TE) juga butuh untuk menjalankan serangkaian robot test dari artifact Vidio Android yang telah lolos unit dan UI test.
Release Cycle Vidio Android
Vidio adalah sebuah tim yang agile. Dalam release cycle, kami membutuhkan sistem yang dapat menghasilkan artifact yang cepat untuk di tes, agar tim engineer segera mendapatkan feedback dan kualitas kode kami tetap terjaga.
Ketika engineer melakukan commit kode ke master branch:
Infrastruktur CI/CD Vidio Android yang Lama
Sebelum migrasi, tim Vidio Android menggunakan sistem CI/CD bersama dengan tim Vidio iOS. Jadi bisa dikatakan sebenarnya kami tidak memiliki sistem CI/CD sendiri sebelumnya.
Infrastruktur sistem CI/CD tim Vidio Mobile App untuk tim Android sebelum migrasi adalah diilustrasikan seperti di gambar 3.
Kami menggunakan Jenkins, server automation open-source paling populer, yang di-host di platform Amazon Web Services (AWS) dalam bentuk sebuah virtual machine (VM) bertipe Amazon Elastic Compute Cloud 2 (EC2). VM di mana Jenkins itu sendiri di-host, untuk selanjutnya disebut master node.
Ketika sebuah Jenkins job dijalankan, master node akan mendelegasikan job tersebut ke sebuah VM EC2 lain yang akan melakukan clone repository, menjalankan tes (small, medium, large), membuat artifact, dan sebagainya. VM yang bertugas menjalankan job tersebut untuk selanjutnya disebut slave node.
Untuk dicatat bahwa slave node yang dipakai untuk menjalankan semua job itu adalah VM yang sama. Artinya, jika ada sebuah job yang sedang menjalankan small test, kami tidak dapat menjalankan job lain, misal medium test, di waktu yang bersamaan. Kami harus menunggu sampai job small test tersebut selesai. Begitu juga untuk job-job yang lain.
Medium test dan large test adalah android UI test (instrumentation test), yang artinya kami membutuhkan emulator untuk menjalankannya. Kami menggunakan Firebase Test Lab untuk menyediakan android device bagi tes-tes ini. Karena kami menggunakan akun Firebase bertipe Spark, kami dibatasi hanya boleh menggunakan maksimal 10 android device setiap harinya.
Selain itu, ada slave node lain yang bertugas menjalankan robot test milik TE. Slave node ini adalah sebuah local machine (komputer/PC) yang ada di kantor kami. Karena robot test juga membutuhkan android device, kami juga menyediakan android device sendiri di slave node tersebut.
Bagaimana Kami Menjalankan Tes Vidio Android di CI/CD yang Lama
Di sistem kami yang lama, kami memecah tes-tes di Vidio Android menjadi 3 jenis, yaitu small, medium, dan large. Small test adalah unit test, medium test adalah UI test, dan large test adalah acceptance test.
Karena memakai slave node yang sama, ketiga job tes tersebut akan dijalankan bergiliran setelah job sebelumnya sukses. Medium test dan large test akan melakukan reservasi device di Firebase Test Lab ketika dijalankan.
Test Reporting di CI/CD yang Lama
Setelah menjalankan tes, report untuk tes yang gagal, sukses, atau yang diabaikan (ignored) semuanya disediakan oleh Firebase Test Lab.
Laporan yang disediakan oleh Firebase Test Lab ini cukup bagus dan informatif. Secara high-level kita bisa melihat di emulator mana tes kami dijalankan dan statusnya. Secara low-level, kami bisa melihat test case mana yang gagal dan penyebabnya. Kami juga bisa melihat logCat device dari tiap testcase. Yang menarik, Firebase Test Lab menyediakan fitur untuk melihat video tes kami dan analisis performanya.
Migrasi ke Sistem CI/CD yang Baru
Di sistem CI/CD yang baru ini, kami melakukan perubahan besar, di mana kami tidak lagi menggunakan platform AWS dan Firebase Test Lab. Sebagai gantinya, kami menggunakan Google Cloud Platform sebagai penyedia VM dan Genymotion Cloud sebagai penyedia Android device. Namun kami masih tetap menggunakan Jenkins sebagai automation server-nya.
Dari desain infrastruktur sebenarnya hampir sama dari CI/CD kami yang lama, hanya saja, kali ini ada 3 jenis slave node. Yang pertama adalah slave node yang khusus untuk menjalankan tes, kami sebut sebagai test runner (warna merah muda pada gambar 6). Kemudian ada artifact builder, slave node yang digunakan khusus untuk membuat artifact (warna hijau), dan terakhir slave node untuk menjalankan robot test TE (warna kuning).
Karena kali ada slave node khusus untuk jenis job berbeda, maka kami dapat melakukan, misalnya, menjalankan job untuk tes dan job untuk membuat artifact tanpa perlu saling menunggu satu sama lain. Hal ini tentunya meningkatkan produktivitas kami. Antrian hanya terjadi pada job-job yang berjalan di slave node yang tipenya sama, dan itu pun jika jumlah instance slave node tipe tersebut sudah mencapai batas maksimal yang telah kami tentukan.
Untuk test runner dan robot test node sama-sama menggunakan Genymotion Cloud untuk menyediakan Android device. Dengan Genymotion Cloud, kami dapat menjalankan sampai 100 Android device secara bersamaan.
Semua node, baik master node maupun slave node adalah sebuah VM bertipe Google Compute Engine (GCE). Setiap ada job baru yang akan dijalankan, master node akan membuat sebuah instance GCE baru untuk dijadikan slave node sesuai tipe job. Ketika job tersebut sudah selesai, dan tidak ada job lain dengan tipe sama yang mengantri, maka VM tersebut akan dimatikan. Jika ada job baru yang akan dijalankan, master akan membuat lagi yang baru, begitu seterusnya.
Bagaimana Instance Slave Node Baru Dibuat
Master node memiliki sebuah job yang khusus yang bernama “create image”. Ketika job ini dijalankan, ia akan melakukan clone ke sebuah repository kami bernama “skilla”, yang di dalamnya terdapat script playbook Ansible berisi semua dependency yang dibutuhkan sebuah job ketika dijalankan, seperti Android SDK, Gradle, Genymotion tools, dll.
Melalui job create image, script ini kemudian dijalankan, melakukan provisioning dan menghasilkan output berupa image. Image ini adalah sebuah cetak biru VM yang akan dipakai oleh sebuah job.
Jadi ketika misalnya sebuah job bertipe test runner akan dijalankan, Jenkins akan mencari image untuk job test runner (yang membutuhkan Android SDK dan Genymotion tools) di GCP (hasil dari job create image), kemudian akan diluncurkan menjadi VM dan dipakai job test runner tadi sebagai slave node.
Ketika selesai, VM ini akan menunggu selama waktu tertentu yang telah kami tentukan (disebut retention time) apakah ada job test runner lain yang akan dijalankan. Jika ada, maka VM ini akan dipakai lagi, dan jika tidak, ia akan dimatikan.
Bagaimana Vidio Android Menjalankan Tes di CI/CD Yang Baru
Kali ini kami tidak lagi memecah tes menjadi small, medium dan large, namun kami membaginya menjadi per modul.
Kami membuat sebuah pipeline, membaginya menjadi empat tahap: unit test (untuk semua modul), platform UI test, app UI test, dan TV unit test.
Ketika job ini jalan, ia akan mereservasi 4 buah emulator dari Genymotion Cloud, 3 berupa smartphone, dan 1 berupa tablet.
Unit test akan dijalankan pertama kali, jika ada tes yang gagal maka job akan dibatalkan. Dilanjutkan dengan menjalankan UI test untuk modul platform, yang merupakan salah satu modul vital Vidio Android dan merupakan dependency dari modul app dan tv. Modul platform akan memakai keempat emulator yang telah direservasi tadi untuk menjalankan UI test.
Jika lolos, job akan menjalankan UI test untuk modul app dan tv secara paralel, karena kedua modul tersebut tidak bergantung satu sama lain. Module app akan memakai ketiga smartphone emulator, sedangkan tv akan memakai 1 tablet emulator. Konfigurasi ini dapat berubah di masa yang akan datang.
Spoon
Dalam menjalankan UI test, kami memutuskan untuk menggunakan Spoon, sebuah aplikasi UI test-runner open source buatan Square, sebagai test-runner UI test kami karena beberapa fitur yang telah disediakan olehnya, antara lain:
- UI Test Sharding
- UI Test Isolation
- High Level UI Test Reporting
Kenapa kami membutuhkan fitur-fitur ini tentunya karena banyak faktor, dan salah satunya adalah efisiensi waktu dan resource.
Beberapa paragraf di bawah akan membandingkan antara unsharded test vs test sharding dan unisolated test vs isolated test.
Unsharded Test
Ketika kita menjalankan serangkaian UI test, misal dalam sebuah modul, tes tersebut akan dijalankan satu-persatu terhadap device yang tersedia, seperti diilustrasikan pada gambar berikut:
Pada gambar 10, test A akan dijalankan pertama kali, test J akan dijalankan setelah test I selesai, test K akan dijalankan setelah test J, dan seterusnya. Hal ini tentunya akan membutuhkan banyak waktu.
Test Sharding
Test sharding akan membagi serangkaian tes tersebut tadi menjadi beberapa grup sebanyak device yang tersedia, kemudian menjalankannya secara paralel.
Bagaimana tes-tes tersebut dibagi, Spoon memiliki algoritma sendiri, dan selama kami pantau, sepertinya hasilnya cukup imbang untuk tiap grupnya.
Unisolated Test
Salah satu fitur Spoon adalah test isolation. Untuk memahaminya kita perlu memahami unisolated test terlebih dulu.
Ketika kita menjalankan UI test Android, yang terjadi di belakangnya adalah, Android SDK akan membuat dua buah file APK. Yang pertama adalah APK aplikasi kita, kita sebut sebagai app APK, dan yang kedua adalah APK yang berisi semua test yang akan dijalankan beserta test runner nya, kita sebut sebagai test APK.
Kemudian sebuah program kecil bernama ADB, atau Android Debug Bridge, yang merupakan tools bawaan Android SDK akan menjalankan dua buah perintah (command) : push, di mana ADB akan meng-copy dua APK tadi ke device tempat tes akan dijalankan, dan install, di mana ADB akan meng-install kedua APK di device tersebut.
Setelah kedua APK terpasang, ADB akan menjalankan perintah berikutnya yaitu shell, yang berarti ADB akan mengakses terminal milik device tempat tes kita dijalankan, karena pada dasarnya Android sendiri adalah sebuah sistem operasi Linux.
Kemudian, di terminal tersebut, akan dijalankan sebuah aplikasi bernama AM, atau Activity Manager, dan AM akan menjalankan sebuah perintah, instrument, yang berarti akan menjalankan instrumentation test untuk kedua APK yang baru kita install tadi.
Dalam serangkaian tes tersebut bisa ada beberapa test class, dan untuk setiap class, bisa ada beberapa test case, dan untuk diingat, satu perintah adb shell am instrument … tadi, akan dijalankan untuk semua test case. Bukan setiap test case.
Pada gambar 14, dalam sebuah skenario, ada sebuah test case, testA, yang membuat app APK crash dan tes tersebut gagal. Di dalam antriannya, masih ada beberapa test case lain yang menunggu untuk dijalankan, apa yang terjadi berikutnya?
Karena perintah am instrument... tadi dijalankan untuk semua test case, ketika tiba giliran testB akan dijalankan, karena app APK nya sudah dalam keadaan crash, maka yang terjadi adalah testB akan ikut gagal, meskipun sebenarnya bisa saja testB lolos, hal yang sama akan terjadi di setiap antrian berikutnya.
Test Isolation
Pada test isolation, perintah am instrumentation... akan dijalankan untuk setiap test case, yang artinya setiap test case akan memiliki akses terhadap satu instance app APK dan terisolasi dari test case lainnya.
Jadi ketika ada sebuah test case yang menyebabkan app APK menjadi crash, karena setiap test case memiliki instance app APK masing-masing, maka test case berikutnya tidak akan terpengaruh, karena ia akan menjalankan instance app APK milik dia sendiri.
Test Reporting
Karena kami sudah tidak menggunakan Firebase Test Lab lagi, maka kami harus membuat sistem test reporting sendiri, untungnya hal tersebut disediakan oleh Spoon dan dapat diintegrasikan dengan baik dengan Jenkins.
Ketika serangkaian UI test sudah dijalankan, Spoon akan membuat test report berupa sejumlah file html, css, dan javascript di sebuah direktori. Direktori ini kemudian oleh Jenkins akan diarsipkan dan dapat diakses di halaman depan job kami.
Level pertama test report-nya akan memperlihatkan beberapa bar sejumlah device yang digunakan pada test sharding, pada gambar 17, terlihat ada 3 device yang digunakan. dalam setiap bar, ada beberapa blok yang menandakan test case yang dijalankan di tiap device. Blok hijau menandakan test yang sukses, merah yang gagal, dan abu-abu yang diabaikan (ignored).
Setiap blok test case akan mengarahkan pada informasi yang lebih detail mengenai test case tersebut, seperti failed cause, logcat, dan instrumentation logs.
Concurrent Test Run
Kemampuan lain di sistem CI/CD kami yang baru adalah concurrent test run, artinya kami dapat menjalankan job test runner tanpa perlu menunggu job test runner lain selesai terlebih dulu.
Di sistem CI/CD yang baru, terdapat satu job bertipe multibranch pipeline, bernama PR. Job PR ini, sesuai namanya, bertugas untuk menjalankan tes untuk pull request dari GitHub Vidio Android.
Ketika ada pair engineer yang ingin mencoba sebuah kode namun tidak ingin mengubah kode yang ada di branch master, maka mereka dapat membuat sebuah branch sendiri dan membuat sebuah pull request untuk branch mereka. Jenkins kemudian akan mendeteksi otomatis akan pull request yang baru dibuat tersebut, kemudian membuat job sendiri dengan format “PR-x” dimana “x” adalah nomor pull request di GitHub. Pair engineer tadi kemudian dapat menjalankan test untuk branch mereka tanpa harus berbagi resource dengan job master.
Seperti halnya job master, job PR akan menggunakan 4 device yang berupa 3 smartphone dan 1 tablet.
Time Comparison Between Old and New CI/CD
Setelah selesai membangun sistem CI/CD yang baru, kami kemudian melakukan pengukuran waktu yang dibutuhkan untuk menjalankan serangkaian tes dan membuat artifact.
Time Needed to Run The Tests
Kami membandingkan waktu yang dibutuhkan untuk menjalankan satu job master di CI/CD baru dibandingkan dengan job-job yang relevan di CI/CD lama.
Di sistem CI/CD yang lama, karena job untuk menjalankan unit test dan UI test terpisah, kami menjumlahkan waktunya, yang besarnya kurang lebih sekitar 19 menit.
Sedangkan untuk CI/CD yang baru, karena CI/CD yang lama hanya menjalankan medium test untuk UI test-nya, kami juga hanya memasukkan waktu yang dibutuhkan untuk menjalankan medium test. Ketika dijumlah, besarnya sekitar 10 menit. Untuk UI test di modul platform dan tv hanya baru ada di CI/CD yang baru, yang membutuhkan waktu 2 menit.
Kami menambah tes baru untuk modul platform dan tv, namun waktu yang dibutuhkan lebih sedikit dibandingkan CI/CD yang lama.
Time Needed to Build Artifacts
Kami melakukan komparasi antara job artifact debug dan artifact proguarded antara CI/CD yang baru dengan yang lama.
hasilnya, di sistem CI/CD yang baru kami membutuhkan waktu yang sama dengan CI/CD yang lama. Namun kami masih bisa mereduksinya lagi karena setelah kami periksa, di CI/CD yang baru, sebagian besar waktu habis untuk mengunduh dependency gradlew ketika job artifact debug & proguarded dijalankan. Hal ini bisa diatasi dengan melakukan dependency caching ketika kami menjalankan job “create image” untuk membuat image baru.
Apakah kami puas dengan sistem CI/CD kami yang baru? Ya dan Tidak.
Ya karena waktu yang dibutuhkan untuk menjalankan tes menjadi berkurang yang otomatis berefek meningkatnya produktivitas kami.
Tidak, karena kami yakin sistem CI/CD kami yang baru ini masih bisa dioptimalkan lagi performanya.
Akhir kata, semoga tulisan ini bermanfaat dan dapat menambah wawasan bagi anda yang ingin mengetahui bagaimana sistem CI/CD tim Vidio Android dibangun dan bekerja.