Bagaimana kami menguji Sberbank Online di iOS



Pada artikel sebelumnya, kami berkenalan dengan piramida pengujian dan manfaat pengujian otomatis. Tetapi teori biasanya berbeda dari praktik. Hari ini kami ingin berbicara tentang pengalaman kami dalam menguji kode aplikasi yang digunakan oleh jutaan pengguna iOS. Dan juga tentang jalur sulit yang harus dilalui tim kami untuk mencapai kode stabil.

Situasinya adalah ini: misalkan pengembang berhasil meyakinkan diri mereka sendiri dan bisnis kebutuhan untuk menutupi basis kode dengan tes. Seiring waktu, proyek ini telah menjadi lebih dari selusin ribu unit - dan lebih dari seribu tes UI. Basis uji yang begitu besar memunculkan beberapa masalah, solusi yang ingin kami sampaikan.

Di bagian pertama artikel, kita akan berkenalan dengan kesulitan yang muncul saat bekerja dengan tes unit bersih (non-integrasi), di bagian kedua kita akan mempertimbangkan tes UI. Untuk mengetahui bagaimana kami meningkatkan stabilitas uji coba, selamat datang di Cat.

Dalam dunia yang ideal, dengan kode sumber yang tidak berubah, tes unit harus selalu menunjukkan hasil yang sama, terlepas dari jumlah dan urutan awal. Dan tes yang terus-menerus jatuh seharusnya tidak melewati penghalang Continuous Integration server (CI).


Pada kenyataannya, seseorang mungkin menemukan fakta bahwa unit-test yang sama akan menunjukkan hasil positif atau negatif - yang berarti "berkedip". Alasan perilaku ini terletak pada implementasi yang buruk dari kode tes. Selain itu, tes semacam itu dapat lulus CI dengan menjalankan yang sukses, dan kemudian akan mulai jatuh pada Tarik Permintaan orang lain (PR). Dalam situasi yang serupa, ada keinginan untuk menonaktifkan tes ini atau memainkan roulette dan menjalankan CI run lagi. Namun, pendekatan ini anti-produktif, karena merusak kredibilitas tes dan memuat CI dengan pekerjaan yang tidak berarti.

Masalah ini disorot tahun ini di konferensi internasional WWDC Apple:

  • Sesi ini berbicara tentang pengujian paralel, analisis cakupan kode target individu dengan tes, dan juga tentang urutan tes yang berjalan.
  • Di sini Apple berbicara tentang pengujian permintaan jaringan, peretasan, pengujian pemberitahuan dan kecepatan pengujian.

Tes unit


Untuk memerangi tes yang berkedip, kami menggunakan urutan tindakan berikut:

gambar

0. Kami mengevaluasi kode uji kualitas sesuai dengan kriteria dasar: isolasi, kebenaran moka, dll. Kami mengikuti aturan: dengan tes yang berkedip, kami mengubah kode tes, dan bukan kode tes.

Jika item ini tidak membantu, maka lakukan sebagai berikut:

1. Kami memperbaiki dan mereproduksi kondisi di mana tes jatuh;
2. Temukan alasan mengapa jatuh;
3. Ubah kode uji atau kode uji;
4. Pergi ke langkah pertama dan periksa apakah penyebab kejatuhan telah dieliminasi.

Mainkan jatuh


Opsi paling sederhana dan paling jelas adalah menjalankan tes masalah pada versi iOS yang sama dan pada perangkat yang sama. Sebagai aturan, dalam hal ini tes berhasil, dan muncul pemikiran: "Semuanya bekerja untuk saya secara lokal, saya akan memulai kembali perakitan pada CI". Itu sebenarnya masalah belum terselesaikan, dan tes terus jatuh ke orang lain.

Oleh karena itu, pada langkah verifikasi berikutnya, Anda harus menjalankan semua tes unit aplikasi secara lokal untuk mengidentifikasi dampak potensial dari satu tes pada tes lainnya. Tetapi bahkan setelah verifikasi semacam itu, hasil tes Anda mungkin positif, tetapi masalahnya tetap tidak terdeteksi.

Jika seluruh urutan pengujian berhasil dan penurunan yang diharapkan tidak dicatat, Anda dapat mengulangi proses tersebut beberapa kali secara signifikan.
Untuk melakukan ini, pada baris perintah, Anda perlu menjalankan loop dengan xcodebuild:

#! /bin/sh x=0 while [ $x -le 100 ]; do xcodebuild -configuration Debug -scheme "TargetScheme" -workspace App.wcworkspace -sdk iphonesimulator -destination "platfrom=iOS Simulator, OS=11.3, name=iPhone 7" test >> "report.txt"; x=$(( $x +1 )); done 

Sebagai aturan, ini cukup untuk mereproduksi jatuhnya dan beralih ke langkah berikutnya - mengidentifikasi penyebab jatuhnya yang tercatat.

Alasan kejatuhan dan kemungkinan solusi


Pertimbangkan penyebab utama berkedipnya unit-tes yang mungkin Anda temui dalam pekerjaan Anda, alat untuk mengidentifikasi mereka, dan kemungkinan solusi.

Ada tiga kelompok utama alasan jatuhnya tes:

Isolasi yang buruk

Yang kami maksud dengan isolasi adalah kasus enkapsulasi khusus, yaitu: mekanisme bahasa yang memungkinkan pembatasan akses beberapa komponen program ke yang lain.

Isolasi lingkungan memainkan peran penting, karena untuk kemurnian tes, tidak ada yang mempengaruhi entitas yang diuji. Perhatian khusus harus diberikan pada tes yang ditujukan untuk memeriksa kode. Mereka menggunakan entitas negara global, seperti: variabel global, Keychain, Network, CoreData, Singleton, NSUserDefaults, dan sebagainya. Di daerah-daerah inilah sejumlah besar tempat potensial untuk manifestasi isolasi yang buruk muncul. Misalkan, saat membuat lingkungan pengujian, keadaan global diatur, yang secara implisit digunakan dalam kode pengujian lain. Dalam kasus ini, tes yang memeriksa kode yang sedang diuji mungkin mulai "berkedip" - karena tergantung pada urutan tes, dua situasi dapat muncul - ketika negara global diatur dan ketika tidak diatur. Seringkali, dependensi yang dijelaskan adalah implisit, sehingga Anda mungkin tidak sengaja lupa untuk mengatur / mengatur ulang negara global tersebut.

Untuk membuat dependensi terlihat jelas, Anda dapat menggunakan prinsip Dependency Injection (DI), yaitu: meneruskan dependensi melalui parameter konstruktor, atau properti objek. Ini akan membuatnya mudah untuk menggantikan dependensi tiruan alih-alih objek nyata.

Hubungi asynchrony

Semua tes unit dilakukan secara sinkron. Kesulitan pengujian asinkron muncul karena panggilan metode pengujian dalam tes “membeku” untuk mengantisipasi penyelesaian ruang lingkup unit tes. Hasilnya akan menjadi tes yang stabil.

 //act [self.testService loadImageFromUrl:@"www.google.ru" handler:^(UIImage * _Nullable image, NSError * _Nullable error) { //assert OCMVerify([cacheMock imageAtPath:OCMOCK_ANY]); OCMVerify([cacheMock dateOfFileAtPath:OCMOCK_ANY]); OCMVerify([imageMock new]); [imageMock stopMocking]; }]; [self waitInterval:0.2]; 

Untuk menguji tes semacam itu, ada beberapa pendekatan:

  1. Jalankan NSRunLoop
  2. waitForExpectationsWithTimeout

Kedua opsi mengharuskan Anda untuk menentukan argumen dengan batas waktu. Namun, tidak dapat dijamin bahwa interval yang dipilih akan cukup. Secara lokal, tes Anda akan berlalu, tetapi pada CI yang penuh mungkin tidak ada daya yang cukup dan akan jatuh - dari sini "blink" akan muncul.

Mari kita memiliki semacam layanan pemrosesan data. Kami ingin memverifikasi bahwa setelah menerima respons dari server, ini akan mentransfer data ini untuk diproses lebih lanjut.

Untuk mengirim permintaan melalui jaringan, layanan menggunakan klien untuk bekerja dengannya.

Tes semacam itu dapat ditulis secara tidak sinkron menggunakan server tiruan untuk menjamin respons jaringan yang stabil.

 @interface Service : NSObject @property (nonatomic, strong) id<APIClient> apiClient; @end @protocol APIClient <NSObject> - (void)getDataWithCompletion:(void (^)(id responseJSONData))completion; @end - (void)testRequestAsync { // arrange __auto_type service = [Service new]; service.apiClient = [APIClient new]; XCTestExpectation *expectation = [self expectationWithDescription:@"Request"]; // act id receivedData = nil; [self.service receiveDataWithCompletion:^(id responseJSONData) { receivedData = responseJSONData; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) { expect(receivedData).notTo.beNil(); expect(error).to.beNil(); }]; } 

Tetapi versi uji sinkron akan lebih stabil dan memungkinkan Anda untuk tidak bekerja dengan timeout.

Untuknya kita perlu APIClient tiruan tiruan

 @interface APIClientMock : NSObject <APIClient> @end @implementation - (void)getDataWithCompletion:(void (^)(id responseJSONData))completion { __auto_type fakeData = @{ @"key" : @"value" }; if (completion != nil) { completion(fakeData); } } @end 

Maka tes akan terlihat lebih sederhana dan bekerja lebih stabil

 - (void)testRequestSync { // arrange __auto_type service = [Service new]; service.apiClient = [APIClientMock new]; // act id receivedData = nil; [self.service receiveDataWithCompletion:^(id responseJSONData) { receivedData = responseJSONData; }]; expect(receivedData).notTo.beNil(); expect(error).to.beNil(); } 

Operasi asinkron dapat diisolasi dengan mengenkapsulasi entitas terpisah, yang dapat diuji secara independen. Sisa dari logika perlu diuji secara serempak. Pendekatan ini akan menghindari sebagian besar jebakan yang dibawa oleh asynchrony.

Atau, dalam hal memperbarui lapisan UI dari utas latar belakang, Anda dapat memeriksa untuk melihat apakah kami ada di utas utama dan apa yang akan terjadi jika kami melakukan panggilan dari pengujian:

 func performUIUpdate(using closure: @escaping () -> Void) { // If we are already on the main thread, execute the closure directly if Thread.isMainThread { closure() } else { DispatchQueue.main.async(execute: closure) } } 

Untuk penjelasan terperinci, lihat artikel oleh D. Sandell .

Menguji kode di luar kendali Anda
Seringkali kita melupakan hal-hal berikut:

  • implementasi metode mungkin tergantung pada lokalisasi aplikasi,
  • ada metode pribadi di SDK yang bisa dipanggil oleh kelas framework,
  • implementasi metode mungkin tergantung pada versi SDK


Kasus-kasus di atas menimbulkan ketidakpastian saat menulis dan menjalankan tes. Untuk menghindari konsekuensi negatif, Anda perlu menjalankan tes di semua lokal, serta pada versi iOS yang didukung oleh aplikasi Anda. Secara terpisah, perlu dicatat bahwa tidak perlu menguji kode yang implementasinya tersembunyi dari Anda.

Dengan ini, kami ingin menyelesaikan bagian pertama artikel tentang pengujian otomatis aplikasi Sberbank Online iOS, yang didedikasikan untuk pengujian unit.

Di bagian kedua artikel, kita akan berbicara tentang masalah yang terjadi saat menulis 1500 tes UI, serta resep untuk mengatasinya.

Artikel ini ditulis dengan regno - Anton Vlasov, kepala pengembangan dan pengembang iOS.

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


All Articles