Jika Anda menguasai daftar kecil kesalahan khas yang terjadi saat menulis unit test, Anda bahkan dapat menulisnya. Hari ini ketua kelompok pengembangan Yandex.Browser untuk Android Konstantin
kzaikin Zaikin akan berbagi pengalamannya dengan pembaca Habr.
- Saya punya laporan praktis. Saya berharap itu akan bermanfaat bagi Anda semua - mereka yang sudah menulis tes unit, dan mereka yang hanya berpikir untuk menulis, dan mereka yang mencoba, dan yang belum berhasil.
Kami memiliki proyek yang cukup besar. Salah satu proyek seluler terbesar di Rusia. Kami memiliki banyak kode, banyak tes. Tes dikejar pada setiap permintaan kumpulan, mereka tidak jatuh pada saat yang sama.
Siapa yang tahu cakupan tes apa yang dia miliki di proyek? Nol, oke. Siapa yang memiliki tes unit dalam proyek? Dan siapa yang percaya bahwa unit test tidak diperlukan? Saya tidak melihat ada yang salah dengan itu, ada orang yang dengan tulus meyakini hal ini, dan kisah saya harus membantu mereka untuk diyakinkan tentang hal ini.
Untungnya, ribuan tes hijau - kami tidak langsung datang. Tidak ada peluru perak, dan gagasan utama laporan saya di layar:

Pepatah Cina ditulis dalam hieroglif bahwa perjalanan seribu atau lebih dimulai dengan satu langkah. Tampaknya ada analogi dari ungkapan ini.
Kami sudah lama membuat keputusan bahwa kami perlu meningkatkan produk kami, kode kami, dan kami bergerak ke arah ini dengan sengaja. Dalam perjalanan ini kami bertemu banyak gundukan, garu bawah air, dan berkumpul bersama beberapa keyakinan ini.

Mengapa kita perlu tes?
Agar fitur lama tidak jatuh saat kami memperkenalkan yang baru. Untuk memiliki lencana di GitHub. Untuk memperbaiki fitur yang ada - pemikiran yang mendalam, perlu diungkapkan kepada mereka yang tidak menulis tes. Agar fitur yang ada tidak jatuh selama refactoring, kami akan melindungi diri dengan tes. Agar bos mengirim permintaan kumpulan, ya.
Pendapat saya - tolong jangan menghubungkannya dengan pendapat tim saya - bahwa tes membantu kami. Mereka memungkinkan Anda menjalankan kode tanpa memproduksinya, tanpa menginstalnya di perangkat, Anda meluncurkan dan menjalankannya dengan sangat cepat. Anda dapat melarikan diri semua kasing pojok yang tidak Anda pakai seumur hidup pada perangkat dan dalam produksi, dan tester Anda tidak akan menemukannya. Tetapi Anda, sebagai pengembang, akan menemukan mereka, memeriksa mereka dan memperbaiki bug pada tahap awal.
Sangat penting: tes memberi tahu bagaimana, menurut pengembang, kode harus bekerja dan apa, menurut pengembang, metode Anda harus lakukan. Ini bukan komentar yang mengusir dan setelah beberapa saat dari yang bermanfaat menjadi berbahaya. Kebetulan di komentar ada satu hal yang ditulis, dan dalam kode sama sekali berbeda. Unit test dalam pengertian ini tidak bisa berbohong. Jika tes berwarna hijau, itu mendokumentasikan apa yang terjadi di sana. Tes gagal - Anda melanggar maksud utama pengembang.
Kontrak komitmen. Ini bukan kontrak yang ditandatangani dan dicap, tetapi kontrak perangkat lunak untuk perilaku kelas. Jika Anda menolak, dalam hal ini kontrak akan dilanggar dan tes akan jatuh jika Anda memutuskannya. Jika kontrak disimpan, tes akan tetap hijau, Anda akan lebih yakin bahwa refactoring Anda benar.

Ini adalah ide umum dari seluruh laporan saya. Anda dapat menunjukkan baris pertama dan pergi.
Banyak orang berpikir bahwa kode uji adalah kode begitu-begitu, bukan untuk produksi, sehingga Anda dapat menuliskannya begitu-begitu. Saya sangat tidak setuju dengan ini dan saya pikir tes harus didekati pertama-tama secara bertanggung jawab, serta kode produksi. Jika Anda mendekati mereka dengan cara yang sama, maka tes akan menguntungkan Anda. Kalau tidak, itu akan menjadi satu cabul.
Lebih khusus, dua baris di bawah ini mengacu pada kode apa saja, tampaknya.
KISS - jadikan itu sederhana, bodoh. Tidak perlu repot. Tes harus sederhana. Dan kode produksi harus sederhana, tetapi tes khususnya. Jika Anda memiliki tes yang mudah dibaca, maka ini akan menjadi tes yang kemungkinan besar ditulis dengan baik, mereka dinyatakan dengan baik, mereka akan mudah untuk diuji. Bahkan selama permintaan pool, seseorang yang melihat tes baru Anda akan mengerti apa yang ingin Anda katakan. Dan jika sesuatu rusak, Anda dapat dengan mudah memahami apa yang terjadi.
KERING - jangan ulangi diri Anda sendiri. Dalam pengujian, pengembang sering cenderung menggunakan teknik terlarang yang tampaknya tidak ada yang digunakan dalam produksi - copy paste. Dalam produksi pengembang yang akan secara aktif menyalin-menempel, mereka tidak akan mengerti. Dalam tes, sayangnya ini adalah praktik yang normal. Tidak perlu melakukan ini, karena - baris pertama. Jika Anda menulis tes dengan jujur, seperti kode yang benar-benar bagus, tes itu akan berguna bagi Anda.
Sementara kami mengembangkan ratusan ribu baris kode, menulis ribuan tes, mengumpulkan rake, saya telah mengumpulkan komentar khas pada tes. Saya cukup malas, dan ketika saya pergi ke kolam meminta dan mengamati kesalahan yang sama, berdasarkan prinsip KERING, saya memutuskan untuk menuliskan masalah-masalah khas ini, dan saya melakukannya terlebih dahulu di Wiki internal dan kemudian memposting bau tes praktis pada GitHub yang dapat Anda ikuti ketika Anda menulis tes.

Saya akan mendaftar berdasarkan poin. Tambahkan penghitung di pikiran Anda jika Anda mengingat bau tes seperti itu. Jika Anda menghitung sampai lima, Anda dapat mengangkat tangan dan berteriak "Bingo!" Dan pada akhirnya, saya bertanya-tanya siapa yang menghitung berapa. Penghitung saya akan sama dengan jumlah poin, saya mengumpulkan semuanya sendiri.

Hal paling sulit dalam pemrograman lho. Dan dalam tes ini sangat penting. Jika Anda tidak menyebutkan nama tes dengan baik, kemungkinan besar Anda tidak akan dapat merumuskan apa yang diperiksa tes.
Manusia adalah makhluk yang cukup sederhana, mereka mudah terjebak dalam nama. Karena itu, saya meminta Anda untuk memanggil tes dengan baik. Merumuskan tes untuk memverifikasi dan mengikuti aturan sederhana.
no_action_or_assertion
Jika nama tes tidak berisi deskripsi tentang apa yang tes periksa, misalnya, Anda memiliki kelas Controller dan Anda menulis tes testController, apa yang Anda periksa? Apa yang harus dilakukan tes ini? Kemungkinan besar, tidak ada atau terlalu banyak hal untuk diperiksa. Tidak ada yang cocok dengan kita. Karena itu, perlu dituliskan atas nama ujian apa yang kami periksa.
long_name
Anda tidak dapat pergi ke ekstrim lainnya. Nama tes harus cukup pendek sehingga seseorang dapat dengan mudah menguraikannya. Dalam pengertian ini, Kotlin hebat karena memungkinkan Anda untuk menulis nama uji dalam tanda kutip dengan spasi dalam bahasa Inggris normal. Mereka lebih mudah dibaca. Tapi tetap saja, nama-nama panjang berbau.
Jika nama tes Anda terlalu panjang, kemungkinan besar Anda memasukkan terlalu banyak metode pengujian dalam satu kelas tes, dan Anda perlu mengklarifikasi apa yang Anda periksa. Dalam hal ini, Anda perlu membagi kelas tes menjadi beberapa. Tidak perlu takut dengan ini. Anda akan memiliki nama kelas uji yang memeriksa nama kode produksi Anda, dan akan ada nama tes pendek.
older_prefix
Ini atavisme. Sebelumnya, di Jawa, semua orang diuji menggunakan JUnit, di mana hingga versi keempat ada kesepakatan bahwa metode pengujian harus dimulai dengan tes kata. Kebetulan, semua orang masih menyebutnya begitu. Tetapi ada masalah, dalam bahasa Inggris kata test adalah kata kerja "check". Orang-orang mudah terjebak dalam jebakan ini, dan tidak lagi menulis kata kerja lainnya. Tulis testController. Mudah untuk memeriksa diri Anda sendiri: jika Anda tidak menulis kata kerja apa yang harus dilakukan kelas tes Anda, kemungkinan besar Anda tidak memeriksa sesuatu, Anda tidak menulisnya dengan cukup baik. Oleh karena itu, saya selalu meminta Anda untuk menghapus kata tes dari nama-nama metode pengujian.
Saya mengatakan hal-hal yang sangat sederhana, tetapi anehnya, mereka membantu. Jika tes disebut baik, kemungkinan besar di bawah tenda mereka akan terlihat bagus. Ini sangat sederhana.

Saya sebenarnya membaca id tes bau seperti pada GitHub. Tautan di bawah, Anda bisa berjalan dan menggunakan.
multiple_asserts
Dalam metode tes ada banyak pernyataan. Jadi mungkin atau tidak? Mungkin Apakah itu baik atau buruk? Saya pikir ini sangat buruk. Jika Anda menulis beberapa pernyataan dalam suatu metode pengujian, maka Anda memeriksa beberapa pernyataan. Jika Anda menguji tes Anda dan pernyataan pertama jatuh, akankah tes mencapai pernyataan kedua? Tidak akan mencapai. Anda sudah setelah jatuhnya perakitan Anda di suatu tempat di CI mendapatkan bahwa tes jatuh, pergi perbaiki sesuatu, isi lagi, itu akan jatuh pada pernyataan berikutnya. Mungkin saja.
Dalam hal ini, akan jauh lebih keren jika Anda menggergaji metode pengujian ini menjadi beberapa, dan semua metode dengan beberapa penegasan jatuh pada saat yang sama, karena mereka akan diluncurkan secara independen satu sama lain.
Beberapa pernyataan lagi dapat menutupi tindakan berbeda yang dilakukan dengan kelas tes. Saya sarankan menulis satu tes - satu menegaskan. Pada saat yang sama, pernyataan bisa sangat kompleks. Kolega saya, dalam laporan pertama, mendemonstrasikan sepotong kode di mana ia menggunakan konstruksi yang menyatakan dengan sangat baik. Saya sangat suka pertarungan di JUnit, jadi Anda bisa menggunakannya juga. Untuk pembaca tes, ternyata hanya satu pernyataan singkat. GitHub memiliki contoh semua bau ini dan cara memperbaikinya. Ada contoh kode buruk dan beberapa kode bagus. Ini semua dilakukan dalam bentuk proyek yang dapat Anda unduh, buka, kompilasi, dan jalankan semua tes.
many_tests_in_one
Bau selanjutnya sangat erat kaitannya dengan sebelumnya. Anda melakukan sesuatu dengan sistem - Anda menegaskan. Melakukan sesuatu yang lain dengan sistem, beberapa operasi panjang - melakukan penegasan - melakukan sesuatu yang lain. Bahkan, Anda hanya melihat ke dalam beberapa metode, dan Anda mendapatkan metode pengujian yang solid dan bagus.
repeating_setup
Ini mengacu pada verbositas. Jika Anda memiliki kelas tes, dan setiap metode pengujian menjalankan metode yang sama di awal.
Kelas uji di mana metode yang sama dijalankan di awal. Ini sepertinya sedikit, tetapi dalam setiap metode pengujian, sampah ini ada. Dan jika itu umum untuk semua metode pengujian, maka mengapa tidak menyeretnya ke konstruktor atau
Sebelum blok atau
Sebelum Setiap blok di JUnit 5. Jika Anda melakukan ini, maka keterbacaan masing-masing metode akan meningkat, ditambah Anda akan menyingkirkan dosa KERING. Tes semacam itu lebih mudah untuk dipertahankan dan lebih mudah dibaca.

Keandalan tes sangat penting. Ada tanda-tanda dimana dapat ditentukan bahwa tes akan menangis, menjadi hijau atau merah. Ketika pengembang menulisnya, dia yakin itu hijau, dan kemudian karena beberapa alasan tes berubah menjadi hijau atau merah, yang memberi kita rasa sakit dan ketidakpastian pada umumnya bahwa tes itu berguna. Kami tidak yakin tentang tes, yang berarti kami tidak yakin bahwa itu berguna.
acak
Saya sendiri pernah menulis tes yang memiliki Math.random () di dalam, melakukan angka acak, melakukan sesuatu dengan mereka. Tidak perlu melakukan ini. Kami berharap bahwa sistem pengujian memasuki sistem pengujian dalam konfigurasi yang sama, dan output darinya juga harus sama. Karena itu, dalam pengujian unit, misalnya, Anda tidak perlu melakukan operasi apa pun dengan jaringan. Karena server mungkin tidak merespons, mungkin ada timing yang berbeda, sesuatu yang lain.
Jika Anda memerlukan tes yang berfungsi dengan jaringan, lakukan proxy, lokal, apa saja, tetapi tidak perlu pergi ke jaringan nyata. Ini adalah acak yang sama. Dan tentu saja, Anda tidak dapat menggunakan data acak. Jika Anda harus melakukan sesuatu, lakukan beberapa contoh dengan syarat batas, dengan kondisi buruk, tetapi harus dikodekan dengan keras.
tread_sleep
Masalah klasik yang dihadapi pengembang saat mencoba menguji beberapa jenis kode asinkron. Itu yang saya lakukan sesuatu dalam tes, dan kemudian saya harus menunggu sampai selesai. Bagaimana membuat? Thread.sleep (), tentu saja.
Ada masalah. Ketika Anda mengembangkan tes Anda, misalnya, Anda melakukannya pada beberapa mesin tik Anda, itu bekerja pada kecepatan tertentu. Anda menjalankan tes di komputer lain. Dan apa yang akan terjadi jika sistem Anda tidak berhasil selama Thread.sleep ()? Tes berubah merah. Ini tidak terduga. Oleh karena itu, rekomendasi di sini adalah, jika Anda melakukan operasi asinkron, jangan mengujinya sama sekali. Hampir semua operasi asinkron dapat digunakan sehingga Anda memiliki semacam mekanisme kondisional yang menyediakan operasi asinkron, dan blok kode yang dijalankan secara sinkron. Misalnya, AsyncTask di dalam memiliki blok kode yang dijalankan secara sinkron. Anda dapat dengan mudah mengujinya secara sinkron, tanpa sinkronisasi apa pun. Tidak perlu menguji AsyncTask sendiri, ini adalah kelas kerangka kerja, mengapa mengujinya? Braket dan hidup Anda akan lebih mudah.
Thread.sleep () banyak kesakitan. Selain fakta bahwa itu memperburuk keandalan tes, karena memungkinkan mereka untuk menangis karena perbedaan waktu pada perangkat, itu juga memperlambat pelaksanaan tes Anda. Siapa yang mau tes unitnya, yang harus dijalankan dalam milidetik, akan berjalan selama lima detik, karena saya mengatur tidur tapak?
memodifikasi_global
Bau khas bahwa kami mengubah beberapa variabel statis global di awal pengujian untuk memeriksa apakah sistem kami berfungsi dengan benar, tetapi tidak kembali pada akhirnya. Lalu kami mendapatkan situasi yang keren: pada mesin, pengembang menjalankan tes dalam satu urutan, pertama-tama memeriksa variabel global dengan nilai default, kemudian mengubahnya dalam tes lain, lalu melakukan sesuatu yang lain. Kedua tes berwarna hijau. Dan pada CI, hal itu terjadi, tes dimulai dengan urutan terbalik. Dan salah satu atau kedua tes akan berwarna merah, meskipun semuanya berwarna hijau.
Anda perlu membersihkan diri sendiri. Aturan pramuka dalam pengertian ini: mengubah variabel global - kembali ke keadaan semula. Lebih baik lagi, pastikan bahwa negara-negara global tidak digunakan. Tapi ini pemikiran yang lebih dalam. Ini tentang fakta bahwa tes terkadang menyoroti cacat dalam arsitektur. Jika kita harus mengubah keadaan global dan mengembalikannya ke keadaan semula untuk menulis tes, apakah kita semua baik-baik saja dalam arsitektur kita? Apakah kita benar-benar membutuhkan variabel global, misalnya? Sebagai aturan, Anda dapat melakukannya tanpa mereka dengan menyuntikkan beberapa kelas konteks atau sesuatu, sehingga Anda dapat menginisialisasi ulang, menyuntikkan dan menginisialisasi ulang mereka setiap kali dalam tes.
@VisibleForTesting
Uji bau untuk lanjut. Kebutuhan untuk menggunakan hal seperti itu tidak muncul pada hari pertama, sebagai suatu peraturan. Anda sudah menguji sesuatu, dan kemudian Anda perlu menerjemahkan kelas ke beberapa keadaan tertentu. Dan Anda menjadikan diri Anda sebagai pintu belakang. Anda memiliki kelas produksi, dan Anda membuat metode spesifik yang tidak akan pernah dipanggil dalam produksi, dan melaluinya Anda menyuntikkan sesuatu ke dalam kelas atau mengubah kondisinya. Dengan demikian, merusak enkapsulasi berbahaya. Dalam produksi, kelas Anda bekerja entah bagaimana, tetapi dalam tes, pada kenyataannya, ini adalah kelas yang berbeda, Anda berkomunikasi dengannya melalui input dan output lain. Dan di sini Anda bisa mendapatkan situasi di mana Anda mengubah produksi, tetapi tes tidak menyadarinya. Tes terus dilakukan melalui pintu belakang dan tidak menyadari bahwa, misalnya, pengecualian mulai menembak pada konstruktor, karena mereka melewati konstruktor lain.
Secara umum, Anda harus menguji kelas Anda melalui input dan output yang sama seperti dalam produksi. Seharusnya tidak ada akses ke metode apa pun hanya untuk tes.

Berapa dari 15 ribu tes kami yang dilakukan? Sekitar 20 menit, di setiap permintaan kumpulan, di Team City, pengembang dipaksa untuk menunggu. Hanya karena 15 ribu banyak tes. Dan di bagian ini, saya telah mengumpulkan bau yang memperlambat tes. Meskipun thread_sleep sudah ada di sana.
tidak perlu_android_test
Android memiliki tes instrumentasi, mereka cantik, mereka berjalan di perangkat atau emulator. Ini akan mengangkat proyek Anda sepenuhnya, nyata, tetapi sangat lambat. Dan bagi mereka, Anda perlu menaikkan emulator keseluruhan. Bahkan jika Anda membayangkan bahwa Anda memiliki emulator yang dinaikkan pada CI - itu hanya bertepatan dengan yang Anda miliki - maka menjalankan tes pada emulator akan memakan waktu lebih lama daripada pada mesin host, misalnya, menggunakan Robolectric. Meskipun ada metode lain. Ini adalah kerangka kerja yang memungkinkan Anda untuk bekerja dengan kelas-kelas dari kerangka kerja Android pada mesin host, di Jawa murni. Kami menggunakannya dengan cukup aktif. Sebelumnya, Google agak keren tentang hal itu, tetapi sekarang googler sendiri membicarakannya dalam berbagai laporan, disarankan untuk digunakan.
tidak perlu_robolektrik
Kerangka kerja Robolectric ditiru. Tidak lengkap di sana, meski implementasi semakin jauh, semakin lengkap. Ini hampir Android nyata, hanya berjalan di desktop, laptop atau CI. Tapi itu juga tidak perlu digunakan di mana-mana. Robolectric tidak gratis. Jika Anda memiliki tes yang secara heroik Anda transfer dari instrumentasi Android ke Robolectric, Anda perlu berpikir - mungkin melangkah lebih jauh, menyingkirkan Robolectric, mengubahnya menjadi tes JUnit yang paling sederhana? Tes Robolectric membutuhkan waktu untuk menginisialisasi, mencoba memuat sumber daya, menginisialisasi aktivitas, aplikasi, dan yang lainnya. Butuh beberapa waktu. Ini bukan yang kedua, itu adalah milidetik, kadang-kadang puluhan dan ratusan. Tetapi ketika ada banyak tes, bahkan itu penting.
Ada teknik yang menyingkirkan Robolectric. Anda dapat mengisolasi kode Anda melalui antarmuka dengan membungkus seluruh bagian platform dengan antarmuka. Kemudian hanya akan ada tes host JUnit. JUnit pada mesin host sangat cepat, ada jumlah minimal overhead, tes tersebut dapat dijalankan dalam ribuan dan puluhan ribu, mereka akan berjalan satu menit, beberapa menit. Sayangnya, pengujian kami berjalan untuk waktu yang lama karena kami memiliki banyak tes instrumentasi Android, karena kami memiliki bagian asli di browser dan kami terpaksa menjalankannya di emulator atau perangkat nyata. Kenapa begitu lama.
Aku tidak akan membuatmu bosan lagi. Berapa banyak aroma yang Anda miliki? Sejauh ini, maksimal tujuh. Berlangganan
saluran , letakkan bintang-bintang.
