Tes terkelupas

Apa yang lebih tidak menyenangkan daripada "ujian merah"? Tes ini hijau atau merah, dan tidak jelas mengapa. Pada konferensi Moskow Heisenbug 2017 kami, Andrei Solntsev (Codeborne) berbicara tentang mengapa mereka mungkin muncul dan bagaimana mengurangi jumlah mereka. Contoh dalam laporannya adalah Anda merasakan sakit langsung di kulit ketika Anda bertabrakan dengannya. Dan tipsnya bermanfaat - dan perlu untuk mengenal baik penguji maupun pengembang. Ada sesuatu yang tidak terduga: Anda dapat mengetahui bagaimana kadang-kadang Anda dapat menemukan masalah jika Anda melepaskan diri dari layar dan bermain batu dengan putri Anda.

Sebagai hasilnya, para penonton menghargai laporan itu, dan kami memutuskan tidak hanya untuk mempublikasikan video, tetapi juga untuk membuat versi teks dari laporan untuk Habr.



Menurut pendapat saya, tes serpihan adalah topik yang paling relevan di dunia otomasi. Karena pertanyaan "apa yang sedang dilakukan di dunia, bagaimana dengan otomatisasi?" semua menjawab: “Tidak ada stabilitas! Tes kami jatuh secara berkala. "

Anda menjalankan tes di tempat Anda, itu hijau, dua hari lagi hijau, dan kemudian sekali dan tiba-tiba jatuh pada Jenkins. Anda mencoba mengulanginya, memulainya, dan berwarna hijau lagi. Dan pada akhirnya, Anda tidak pernah tahu: apakah itu bug atau hanya tes glukan? Dan setiap kali Anda perlu mengerti.

Seringkali, setelah peluncuran tes Jenkins setiap malam, penguji pertama kali melihat "30 tes telah jatuh, Anda perlu belajar", tetapi semua orang tahu apa yang terjadi selanjutnya ...



Anda, tentu saja, menebak kata tidak senonoh yang menyamar: "Saya akan memulai kembali." Seperti, "hari ini tidak ada keengganan untuk memahami ..." Inilah yang biasanya terjadi, dan ini adalah bencana nyata.

Tidak ada statistik yang pasti, tetapi saya sering mendengar dari orang yang berbeda bahwa mereka memiliki sekitar 30% dari tes - bersisik. Secara kasar, mereka meluncurkan seribu, 300 di antaranya berwarna merah, dan kemudian mereka memeriksa dengan tangan apakah benar-benar jatuh.

Google menerbitkan sebuah artikel beberapa tahun: ia mengatakan bahwa mereka memiliki 1,5% persen dari tes yang rapuh, dan memberi tahu bagaimana mereka berjuang untuk mengurangi jumlah mereka. Saya bisa sesumbar sedikit dan mengatakan bahwa proyek saya di Codeborne sekarang 0,1%. Namun faktanya, semua ini buruk, bahkan 0,1%. Mengapa

Ambil 1,5%, jumlah ini tampaknya kecil, tetapi apa artinya dalam praktik? Katakanlah ada seribu tes dalam suatu proyek. Ini mungkin berarti bahwa 15 tes jatuh dalam satu bangunan, 12 berikutnya, lalu 18. Dan ini sangat buruk, karena dalam kasus ini hampir semua bangunan berwarna merah, dan Anda perlu terus-menerus memeriksa dengan tangan Anda apakah itu benar atau tidak.

Dan bahkan satu ppm kami (0,1%) masih buruk. Misalkan kita memiliki 1000 tes, maka 0,1% berarti bahwa secara teratur satu build dari sepuluh jatuh dengan 1-2 tes merah. Ini adalah gambaran nyata dari Jenkins kami, dan ternyata: dengan satu kali uji coba, satu tes serpihan jatuh, dengan tes lain memulai yang lain.



Ternyata kami tidak punya hari tanpa bangunan merah. Karena ada banyak warna hijau, semuanya tampak baik-baik saja, tetapi klien memiliki hak untuk bertanya kepada kami: "Guys, kami membayar Anda uang, dan Anda selalu memberi kami merah!" Apa yang kamu lakukan? "

Saya akan merasa tidak puas di tempat klien, dan menjelaskan "secara umum, ini normal di industri, semuanya berwarna merah untuk semua orang" tidak baik, bukan? Oleh karena itu, menurut pendapat saya, ini adalah masalah yang sangat mendesak, dan mari kita pahami bersama bagaimana menghadapinya.

Rencananya adalah ini:

  1. Koleksi tes tidak stabil saya (dari praktik saya, kasus yang benar-benar nyata, kisah detektif yang kompleks dan menarik)
  2. Penyebab ketidakstabilan (beberapa bahkan membutuhkan waktu bertahun-tahun untuk penelitian)
  3. Bagaimana cara menghadapinya? (semoga ini akan menjadi bagian yang paling berguna)

Jadi, mari kita mulai dengan koleksi saya, yang sangat saya hargai: saya harus menghabiskan berjam-jam kehidupan dan debug. Mari kita mulai dengan contoh sederhana.

Contoh 1: klasik


Untuk seed - skrip Selenium klasik:

driver.navigate().to("https://www.google.com/"); driver.findElement(By.name("q")).sendKeys("selenide"); driver.findElement(By.name("btnK")).click(); assertEquals(9, driver.findElements(By.cssSelector("#ires .g")).size()); 

  1. Kami membuka WebDriver;
  2. Temukan elemen q, arahkan kata untuk mencari di sana;
  3. Temukan elemen "Tombol" dan klik;
  4. Pastikan jawabannya adalah sembilan hasil.

Pertanyaan: baris mana yang bisa putus di sini?

Itu benar, kita semua tahu benar itu! Setiap baris dapat diputus, untuk alasan yang sangat berbeda:

Baris pertama adalah Internet yang lambat, layanan macet, admin tidak mengkonfigurasi sesuatu.

Baris kedua - elemen belum memiliki waktu untuk merender jika ditarik secara dinamis.
Apa yang bisa pecah di baris ketiga? Di sini tidak terduga bagi saya: Saya menulis tes ini untuk konferensi, menjalankannya secara lokal, dan jatuh di baris ketiga dengan kesalahan ini:



Ini mengatakan bahwa elemen pada titik ini tidak dapat diklik. Tampaknya bentuk Google dasar yang sederhana. Rahasianya adalah bahwa pada baris kedua kita menekan kata, dan ketika kita memasukkannya, Google sudah menemukan hasil pertama, menunjukkan beberapa hasil pertama dalam pop-up, dan mereka menutup tombol berikutnya. Dan ini tidak terjadi di semua browser dan tidak selalu. Ini terjadi pada saya dengan naskah ini sekitar satu dari lima.

Baris keempat mungkin jatuh, misalnya, karena elemen ini digambar secara dinamis dan belum punya waktu untuk menggambar.

Dalam contoh ini, saya ingin mengatakan bahwa, dalam pengalaman saya, 90% dari tes serpihan didasarkan pada alasan yang sama:

  • Kecepatan permintaan Ajax: kadang-kadang berjalan lebih lambat, kadang lebih cepat;
  • Urutan permintaan Ajax;
  • Kecepatan js.

Untungnya, ada obat untuk alasan ini! Selenide memecahkan masalah ini. Bagaimana keputusannya? Kami menulis ulang tes Google kami di Selenide - hampir semuanya tampak seperti itu, hanya tanda $ yang digunakan:

 @Test public void userCanLogin() { open(“http://localhost:8080/login”); $(By.name(“username”).setValue(“john”); $(“#submit”).click(); $(“.menu”).shouldHave(text(“Hello, John!”)); } 

Tes ini selalu berlalu. Karena kenyataan bahwa metode setValue (), klik () dan shouldHave () cerdas: jika sesuatu tidak punya waktu untuk melukis, mereka menunggu sedikit dan mencoba lagi (ini disebut "harapan pintar").

Jika Anda terlihat sedikit lebih detail, maka semua metode * ini harus pintar:



Mereka bisa menunggu jika perlu. Secara default, mereka menunggu hingga 4 detik, dan batas waktu ini, tentu saja, dapat dikonfigurasi, Anda dapat menentukan yang lain. Misalnya, seperti ini: mvn -Dselenide.timeout = 8000.

Contoh 2: nbob


Jadi, 90% masalah dengan tes serpihan diselesaikan dengan Selenide. Tetapi 10% dari kasus yang jauh lebih canggih tetap dengan alasan yang kompleks dan membingungkan. Justru tentang mereka yang ingin saya bicarakan hari ini, karena itu adalah "daerah abu-abu". Biarkan saya memberi Anda satu contoh: tes yang rapuh, yang segera saya temui dalam proyek baru. Sekilas, ini tidak bisa terjadi, tetapi ini sesuatu yang menarik.

Kami menguji aplikasi keyboard untuk login di kios. Tes ingin masuk sebagai pengguna "bob", yaitu, masukkan tiga huruf di bidang "login": bob. Untuk melakukan ini, tombol pada layar digunakan. Sebagai aturan, ini berhasil, tetapi terkadang tes macet, dan nilai "nbob" tetap berada di bidang "login":



Tentu saja, Anda berjuang untuk mencari berdasarkan kode di mana kami bisa menulis "nbob" - tetapi dalam keseluruhan proyek ini sama sekali tidak (baik dalam database, atau dalam kode, atau bahkan dalam file Excel). Bagaimana ini mungkin?

Kami melihat kode lebih terinci - sepertinya semuanya sederhana, tanpa teka-teki:

 @Test public void loginKiosk() { open(“http://localhost:9000/kiosk”); $(“body”).click(); $(By.name(“username”)).sendKeys(“bob”); $(“#login”).click(); } 

Kami mulai berdebat lebih lanjut, untuk pergi selangkah demi selangkah, dan dengan metode ini kami berhasil memahami: kesalahan ini terkadang muncul setelah baris $ ("tubuh") .Klik (). Yaitu, pada langkah ini, "n" muncul di bidang "login", lalu "bob" ditambahkan pada langkah-langkah berikutnya. Siapa yang sudah menebak dari mana "n" berasal?

Kebetulan huruf N ada di tengah layar, dan fungsi click () setidaknya di Chrome berfungsi seperti ini: ia menghitung koordinat pusat suatu elemen dan mengkliknya. Karena tubuh adalah elemen besar, dia mengklik di tengah seluruh layar.



Dan ini tidak selalu jatuh. Siapa yang tahu kenapa? Padahal, saya sendiri belum sepenuhnya tahu. Mungkin karena fakta bahwa jendela browser terbuka sepanjang waktu dalam berbagai ukuran, dan ini tidak selalu termasuk dalam huruf N.

Anda mungkin memiliki pertanyaan: mengapa seseorang menghasilkan $ ("tubuh") .Klik ()? Saya juga tidak tahu sampai akhir, tetapi saya kira untuk menghapus fokus dari bidang. Ada masalah seperti itu di Selenium bahwa klik () adalah, tetapi hapus centang () tidak. Jika ada fokus di bidang, maka itu tidak dapat dihapus dari sana, Anda hanya bisa mengklik elemen lain. Dan karena tidak ada elemen masuk akal lainnya, mereka mengklik tubuhnya, dan mendapat efek seperti itu.

Karenanya moral: jangan masukkan apa pun yang masuk ke dalam <body>. Dengan kata lain, Anda tidak perlu melakukan gerakan ekstra dengan panik. Bahkan, ini sering terjadi: karena saya berurusan dengan Selenide, saya sering menerima keluhan "sesuatu tidak bekerja", dan ternyata di suatu tempat dalam metode pengaturan ada 15 baris tambahan yang tidak melakukan apa pun yang berguna dan mengganggu . Tidak perlu repot dan memasukkan pula ke dalam tes seperti "tiba-tiba itu akan lebih dapat diandalkan".

Akibatnya, kami memperluas daftar alasan untuk pengujian yang tidak stabil:

  • Kecepatan permintaan Ajax;
  • Urutan permintaan Ajax;
  • Kecepatan js;
  • Ukuran jendela browser;
  • Kesombongan!

Dan pada saat yang sama, rekomendasi saya adalah: jangan menjalankan tes dimaksimalkan (yaitu, jangan membuka browser di jendela penuh). Sebagai aturan, semua orang melakukan ini, dan di Selenide secara default (atau masih). Sebagai gantinya, saya menyarankan Anda untuk selalu meluncurkan browser dengan resolusi layar yang ditentukan secara ketat, karena faktor acak ini dikecualikan. Dan saya menyarankan Anda untuk mengatur ukuran minimum yang didukung aplikasi Anda sesuai dengan spesifikasi.

Contoh 3: akun hantu


Contoh yang menarik adalah bahwa segala sesuatu yang hanya bisa bertepatan segera bertepatan.

Ada tes yang memeriksa bahwa harus ada 5 akun di layar ini.



Sebagai aturan, itu berwarna hijau, tapi kadang-kadang tidak jelas dalam kondisi apa jatuh dan mengatakan bahwa tidak ada lima, tetapi enam diperhitungkan di layar.

Saya mulai meneliti dari mana tagihan tambahan itu berasal. Benar-benar tidak bisa dimengerti. Timbul pertanyaan: mungkin kita memiliki tes lain, yang selama tes membuat akun baru? Ternyata ya, ada LoansTest. Dan antara itu dan AccountsTest jatuh (yang mengharapkan lima akun) mungkin ada satu juta dari beberapa tes lainnya.

Kami mencoba memahami bagaimana keadaannya: bukankah seharusnya LoansTest, yang membuat akun, menghapusnya di akhir? Kami melihat kodenya - ya, seharusnya, pada akhirnya ada fungsi Setelah untuk ini. Lalu, secara teori, semuanya harus baik-baik saja, apa masalahnya?

Mungkin tes menghapusnya, tetapi masih tersimpan di suatu tempat? Kami melihat kode produksi yang memuat akun - ini benar-benar memiliki penjelasan @CacheFor, kode ini menyimpan akun selama lima menit.

Timbul pertanyaan: tetapi bukankah tes harus menghapus cache ini? Itu akan logis, tidak bisakah ada tiang seperti itu? Kami melihat kodenya - ya, itu benar-benar membersihkan cache sebelum setiap pengujian. Ada apa? Di sini Anda sudah hilang, karena hipotesisnya sudah selesai: objek dihapus, cache dihapus, batang pohon, apa lagi yang bisa menjadi masalah? Lalu dia mulai memanjat kode itu, butuh waktu, bahkan mungkin beberapa hari. Sampai akhirnya saya melihat kelas dan superclass ini, dan menemukan satu hal yang mencurigakan di sana:



Seseorang sudah memperhatikan, kan? Itu benar: di anak dan di kelas induk ada metode dengan nama yang sama, dan itu tidak memanggil super.

Dan di Jawa sangat mudah dilakukan: Anda menekan Alt + Enter atau Ctrl + Insert di IntelliJ IDEA atau Eclipse, secara default ia menciptakan metode setUp () untuk Anda, dan Anda tidak memperhatikan bahwa itu menimpa metode di superclass. Artinya, cache masih belum dipanggil. Ketika saya melihat ini, saya sangat marah. Itu menyenangkan bagi saya sekarang.

Karena itu moral:

  1. Dalam tes, sangat penting untuk memantau kode bersih. Jika dalam kode produksi setiap orang memperhatikan hal ini, mereka melakukan peninjauan kode, kemudian dalam pengujian - tidak selalu.
  2. Jika kode produksi diverifikasi oleh tes, lalu siapa yang akan menguji tes? Oleh karena itu, sangat penting untuk menggunakan cek di IDE.

Setelah kejadian ini, saya menemukan di IDEA inspeksi seperti itu, dinonaktifkan secara default, yang memeriksa: jika metode ini ditimpa di suatu tempat, tetapi tidak ada penjelasan @ Overrid, maka ini menandainya sebagai kesalahan. Sekarang saya selalu histeris mencentang kotak ini.

Mari kita simpulkan lagi: bagaimana ini terjadi, mengapa tes gagal tidak selalu? Pertama, itu tergantung pada urutan dua tes ini, mereka selalu dijalankan secara acak. Tes lain tergantung pada berapa banyak waktu yang berlalu di antara mereka. Akun di-cache selama lima menit, jika lebih banyak lulus, tesnya hijau, dan jika kurang, jatuh, dan ini jarang terjadi.

Kami memperluas daftar mengapa tes bisa tidak stabil:

  • Kecepatan permintaan Ajax;
  • Urutan permintaan Ajax;
  • Kecepatan js;
  • Ukuran jendela browser;
  • Cache aplikasi;
  • Data dari tes sebelumnya;
  • Waktu

Contoh 4: Waktu Java


Ada tes yang berfungsi pada semua komputer kita dan pada Jenkins kita, tetapi kadang-kadang gagal pada pelanggan Jenkins. Kami melihat tes, mengerti mengapa. Ternyata itu jatuh, karena ketika memeriksa "tanggal pembayaran seharusnya sekarang atau di masa lalu," ternyata "di masa depan."

 assert payment.time <= new Date(); 

Kami melihat kode, tiba-tiba, dalam beberapa kondisi, dapatkah kami menetapkan tanggal di masa mendatang? Kami tidak dapat: di satu-satunya tempat di mana waktu pembayaran diinisialisasi, Tanggal baru () digunakan, dan ini selalu waktu saat ini (dalam kasus yang ekstrim, mungkin di masa lalu jika tes sangat lambat). Bagaimana ini mungkin? Mereka memukuli kepala mereka untuk waktu yang lama, mereka tidak bisa mengerti.

Dan begitu mereka melihat ke dalam log aplikasi. Oleh karena itu moral pertama - sangat berguna ketika memeriksa tes untuk melihat log aplikasi itu sendiri. Angkat tanganmu, siapa yang melakukannya. Secara umum, bukan mayoritas, sayangnya. Dan ada informasi yang berguna: misalnya, log permintaan, URL ini-dan-itu dieksekusi pada saat itu, berikan jawaban ini-dan-itu.



Apakah ada sesuatu yang mencurigakan di sini, perhatikan? Kami melihat waktu: permintaan ini diproses minus tiga detik. Bagaimana ini bisa terjadi? Mereka bertarung untuk waktu yang lama, tidak bisa mengerti. Akhirnya, ketika kami kehabisan teori, kami membuat keputusan bodoh: Jenkins menulis skrip sederhana yang mencatat waktu saat ini dalam siklus satu detik sekali. Meluncurkannya. Keesokan harinya, ketika tes serpihan ini jatuh sekali pada malam hari, mereka mulai menonton kutipan dari file ini pada saat jatuh:



Jadi: 34 detik, 35, 36, 37, 35, 39 ... Keren sekali kami menemukannya, tapi bagaimana itu mungkin? Teori berakhir lagi, dua hari lagi menggaruk-garuk kepala. Ini benar-benar kasus ketika Matrix bercanda dengan Anda, bukan?

Sampai akhirnya satu ide menghantamku ... Dan itu ternyata. Linux memiliki layanan sinkronisasi waktu yang berjalan pada server pusat dan bertanya "berapa milidetik sekarang?" Dan ternyata dua layanan berbeda diluncurkan pada Jenkins khusus ini. Tes mulai macet ketika Ubuntu diperbarui pada server ini.

Di sana, layanan ntp sebelumnya dikonfigurasi, yang mengakses server perbankan khusus dan mengambil waktu dari sana. Dan dengan versi baru Ubuntu, layanan ringan baru dimasukkan secara default, misalnya, systemd-timesyncd. Dan keduanya bekerja. Tidak ada yang memperhatikan ini. Untuk beberapa alasan, server pusat perbankan dan beberapa server pusat Ubuntu mengeluarkan respons dengan selisih 3 detik. Tentu, kedua layanan ini saling mengganggu. Di suatu tempat jauh di dalam dokumentasi Ubuntu dikatakan bahwa, tentu saja, jangan biarkan situasi ini ... Yah, terima kasih atas informasinya :)

Ngomong-ngomong, pada saat yang sama saya belajar satu nuansa Jawa yang menarik, yang sebelumnya, terlepas dari pengalaman bertahun-tahun, tidak tahu. Salah satu metode paling dasar di Jawa disebut System.currentTimeMillis (), dengan bantuan yang biasanya diatur waktunya untuk memanggil sesuatu, banyak yang menulis kode seperti itu:

 long start = System.currentTimeMillis(); // ... long end = System.currentTimeMillis(); log.info("Loaded in {} ms", end-start); 

Kode tersebut ada di Apache Commons, perpustakaan Guava. Artinya, jika Anda perlu mendeteksi berapa milidetik yang diperlukan untuk memanggil sesuatu, mereka biasanya melakukannya. Dan banyak yang mungkin mendengar bahwa ini tidak boleh dilakukan. Saya juga mendengar, tetapi tidak tahu mengapa, dan terlalu malas untuk mengerti. Saya pikir pertanyaannya persis karena System.nanoTime () muncul di beberapa versi Jawa - lebih akurat, ia menghasilkan nanodetik yang sejuta kali lebih akurat. Dan karena, sebagai aturan, panggilan saya berlangsung satu detik atau setengah detik, keakuratan ini tidak penting bagi saya, dan saya terus menggunakan System.currentTimeMillis (), yang kami lihat di log di mana itu -3 detik. Jadi, sebenarnya, cara yang benar adalah ini, dan sekarang saya tahu mengapa:

 long start = System.nanoTime(); // ... long end = System.nanoTime(); log.info("Loaded in {} ms", (end-start)/1000000); 

Sebenarnya, ini ditulis dalam dokumentasi metode, tetapi saya tidak pernah membacanya. Saya sudah berpikir sepanjang hidup saya bahwa System.currentTimeMillis () dan System.nanoTime () adalah hal yang sama, hanya dengan perbedaan jutaan kali. Tetapi ternyata ini adalah hal-hal yang pada dasarnya berbeda.

System.currentTimeMillis () mengembalikan tanggal aktual saat ini - berapa milidetik sekarang sejak 1 Januari 1970. Dan System.nanoTime () adalah sejenis penghitung abstrak yang tidak terikat dengan waktu nyata: ya, dijamin akan tumbuh setiap nanosecond per unit, tetapi tidak terhubung dengan waktu saat ini, bahkan bisa negatif. Pada awal JVM, suatu titik waktu entah bagaimana dipilih secara acak, dan mulai tumbuh. Itu merupakan kejutan bagi saya. Untuk kamu juga? Yah, tidak sia-sia dia datang.

Contoh 5: Kutukan Tombol Hijau


Di sini, pengujian kami mengisi formulir tertentu, mengklik tombol Konfirmasi hijau, dan kadang-kadang tidak melangkah lebih jauh. Mengapa tidak pergi tidak bisa dimengerti.



Kami mengemudi di empat nol dan menggantung, jangan pergi ke halaman berikutnya. Mengklik terjadi tanpa kesalahan. Saya melihat semuanya: permintaan Ajax, menunggu, batas waktu, log aplikasi, cache - saya tidak menemukan apa pun. Perpustakaan Perekam Video yang ditulis oleh Sergey Pirogov belum muncul. Itu memungkinkan, menambahkan satu anotasi ke kode, merekam video. Kemudian saya dapat merekam video tes ini, menontonnya dalam gerakan lambat, dan ini akhirnya menjelaskan situasi yang tidak dapat saya pecahkan selama beberapa bulan sebelum video.



Bilah progres memblokir tombol untuk sepersekian detik, dan klik bekerja tepat pada saat itu dan tekan bilah progres ini. Artinya, bilah kemajuan diklik dan menghilang! Dan itu tidak akan terlihat di tangkapan layar apa pun, di log apa pun, Anda tidak akan pernah tahu apa yang terjadi.

Pada prinsipnya, ini dalam beberapa hal adalah bug aplikasi: bilah kemajuan muncul karena aplikasi benar-benar merangkak keluar dari tepi layar, dan jika Anda menggulir, itu ternyata banyak data yang berguna. Tetapi pengguna tidak mengeluh tentang itu, karena semuanya muat di layar lebar, itu tidak cocok hanya pada yang kecil.

Contoh 6: mengapa Chrome dibekukan?


Investigasi detektif dua tahun adalah kasus yang benar-benar nyata. Situasinya adalah ini: pengujian kami cukup sering terkelupas dan jatuh, dan dalam tumpukan jejak jelas bahwa Chrome membeku: bukan pengujian kami, yaitu Chrome. Dalam log terlihat "Build sedang berjalan 36 jam ..." Mereka mulai menghapus dump benang dan jejak stack - mereka menunjukkan bahwa semuanya baik-baik saja dalam pengujian, panggilan ke Chromedriver hang dan, sebagai aturan, pada saat penutupan (kami menyebutnya metode penutupan, dan metode ini tidak melakukan apa-apa, hang 36 jam). Jika menarik, maka jejak tumpukan tampak seperti ini:



Kami mencoba melakukan semua yang hanya dapat terlintas dalam pikiran:

  • Konfigurasikan batas waktu untuk membuka / menutup browser (jika Anda tidak dapat membuka / menutup browser dalam 15 detik, coba lagi setelah 15 detik, hingga tiga upaya). Buka dan tutup browser di utas terpisah. Hasil: ketiga upaya digantung dengan cara yang sama.
  • Bunuh proses Chrome yang lama. Mereka menciptakan pekerjaan terpisah di Jenkins 'kill-chrome', misalnya, seperti ini, Anda dapat "membunuh" semua proses yang lebih lama dari satu jam:

    killall --older-than 1h chromedriver
    killall --older-than 1h chrome

    Ini setidaknya membebaskan memori, tetapi tidak memberikan jawaban untuk pertanyaan "apa yang terjadi?". Sebenarnya, hal ini hanya menunda kita pada saat mengambil keputusan.
  • Aktifkan log aplikasi debug.
  • Aktifkan log debug WebDriver.
  • Buka kembali browser setelah setiap 20 tes. Ini mungkin tampak konyol, tetapi pemikirannya adalah: "Bagaimana jika Chrome membeku karena lelah?" Ya, memori bocor atau sesuatu yang lain.

Hasil dari upaya terakhir benar-benar tidak terduga: masalah mulai berulang lebih sering! Dan kami berharap ini akan membantu menstabilkan Chrome sehingga berfungsi lebih baik. Ini umumnya takeaway otak. Tetapi pada kenyataannya, ketika masalah mulai kambuh lagi, seseorang seharusnya tidak bersedih, tetapi bersukacitalah! Ini memungkinkan untuk mempelajarinya dengan lebih baik. Jika dia mulai mengulangi lebih sering, orang harus berpegang teguh pada itu: "Ya, ya, sekarang saya akan menambahkan sesuatu yang lain, log, breakpoints ..."

Kami mencoba mengulangi masalahnya: kami menulis siklus dari 1 hingga 1000, dalam siklus kami cukup membuka browser, dan menutup halaman pertama dalam aplikasi kami. Kami menulis siklus seperti itu, dan ... bingo! Hasil: masalah mulai berulang secara stabil (meskipun kira-kira setiap 80 iterasi)! Keren! Benar, prestasi ini tidak memberi apa-apa untuk waktu yang lama. Anda memulainya, menunggu iterasi ke-80, Chrome macet ... lalu apa yang harus dilakukan? Anda melihat jejak tumpukan, kesedihan, log - tidak ada yang berguna di sana. Alat Pengembang di Chrome mungkin membantu, tetapi hingga September 2017 alat ini tidak berfungsi dengan Selenium (port-portnya dalam konflik: Anda meluncurkan Chrome dari Selenium dan DevTools tidak terbuka). Untuk waktu yang lama saya tidak bisa memikirkan apa yang harus dilakukan.

Dan di sini dalam cerita ini dimulai saat yang luar biasa. Suatu kali, setelah upaya yang tak terbatas, saya menjalankan tes ini lagi, itu tergantung lagi pada beberapa iterasi seperti ke-56, saya pikir "mari kita gali sesuatu yang lain" (walaupun saya tidak tahu di mana lagi meletakkan breakpoint atau apa tambahkan log). Pada saat ini, putri saya menawarkan untuk bermain kubus, tetapi ujian saya hanya bertahan di sini. Saya berkata, "Tunggu," katanya kepada saya, "Apa, Anda tidak mengerti, saya punya huruf a dan huruf a di sini!"

Apa yang harus dilakukan, dengan sedih meninggalkan komputer, pergi bermain kubus ... Dan tiba-tiba, setelah sekitar 20 menit, saya tidak sengaja melirik layar, dan saya melihat gambar yang sama sekali tidak terduga:



Apa yang terjadi: ada hitungan mundur, setelah berapa menit sesi berakhir, dan saya membangun menara kubus, ada dua, satu ... sesi berakhir, tes berlanjut, berjalan sampai akhir dan jatuh (tidak ada lagi elemen, sesi telah berakhir).

Apa yang terjadi: Chrome tidak benar-benar membeku, seperti yang kami pikir selama ini, sudah menunggu sesuatu selama ini. Ketika sesi berakhir, menunggu, berlanjut. Apa sebenarnya yang diharapkan Chrome - benar-benar tidak dapat dipahami untuk memahami hal ini, saya harus menyekop semua kode menggunakan metode pencarian biner: membuang setengah dari JavaScript dan HTML, coba ulangi 80 iterasi lagi - itu tidak menggantung, oh, itu berarti di suatu tempat di luar sana ... Secara umum, kami memahami secara eksperimental bahwa masalahnya ada di sini:

 var timeout = setTimeout(sessionWatcher); 

JavaScript — , , . , JavaScript- , : , <script> . , , , , . JavaScript — jQuery, $, function , :

 var timeout; $(function() { timeout = setTimeout(...); }); 

-, , , . , . 1000 , .

, : , , , . , Chrome, . , .

, flaky- , , , . , — , , ( ). , . , : ?

Chrome flaky-: -, , , .

UI- : , . click(), , . , , : click() , . - , ? :)

. , , , . , , , , Docker.

, - , . :

  • Ajax-;
  • Ajax-;
  • JS;
  • ;
  • ;
  • ;
  • ;
  • ;
  • UI-;
  • ( ).

flaky- , . , -, : , .

. «» . , flaky- , ID, flaky- . .

, : , , .

, flaky- usability, -, flaky- . .

, , … , , , flaky- security-, . , !

, , flaky-:

  • ;
  • Selenide;
  • ;
  • .

— . flaky- , unit- , UI-? , ( ), , flaky.

Selenide .

. (, / ). , « ?». , , . , .

, . , , , , . : « », , (10 , 20 , — , — ). , flaky - .

, flaky- :

  1. ;
  2. ;
  3. Video

« » , , , : - . «», «» , : flaky-, , flaky. , , . . , Jenkins pipeline, Jenkins :

 finally { stage("Reports") { junit 'build/test-results/**/*.xml' artifacts = 'build./reports/**/*,build/test-results/**/*,logs/**/*' archiveArtifacts artifacts: artifacts } } 

finally , . : - - . Jenkins , . , Jenkins , . , .

(Selenide , ). flaky-. , Video Recorder , : video — , !

, Docker: TestContainers ( Heisenbug ). Rule , — Docker , , . .

 @Rule public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer() .withRecordingMode(RECORD_ALL, new File("build")) .withDesiredCapabilities(chrome()); 

.

. , , , , , . flaky- .

, ! , :) . , « , , », , .

— , , , , - . — , … ! :) flaky- — : !

, : 6-7 Heisenbug . , , . (, , ) .

Source: https://habr.com/ru/post/id416757/


All Articles