Pengujian integrasi untuk memeriksa kebocoran memori

Kami menulis banyak pengujian unit, mengembangkan aplikasi SoundCloud untuk iOS. Tes unit terlihat sangat cantik. Mereka pendek, (mudah-mudahan) dapat dibaca, dan mereka memberi kita keyakinan bahwa kode yang kita tulis berfungsi seperti yang diharapkan. Tetapi tes unit, seperti namanya, mencakup hanya satu blok kode, paling sering fungsi atau kelas. Jadi, bagaimana Anda menangkap kesalahan yang ada dalam interaksi antar kelas - kesalahan seperti kebocoran memori ?


Kebocoran Memori


Terkadang cukup sulit untuk mendeteksi kesalahan kebocoran memori. Ada kemungkinan referensi kuat untuk delegasi, tetapi ada juga kesalahan yang jauh lebih sulit untuk dideteksi. Misalnya, apakah jelas bahwa kode berikut ini mungkin berisi kebocoran memori?

final class UseCase { weak var delegate: UseCaseDelegate? private let service: Service init(service: Service) { self.service = service } func run() { service.makeRequest(handleResponse) } private func handleResponse(response: ServiceResponse) { // some business logic and then... delegate.operationDidComplete() } } 


Karena Layanan sudah dilaksanakan, tidak ada jaminan mengenai perilakunya. Melewati fungsi handleResponse ke fungsi pribadi, yang menangkap sendiri , kami memberikan Layanan dengan referensi yang kuat untuk UseCase . Jika Layanan memutuskan untuk menyimpan tautan ini - dan kami tidak memiliki jaminan bahwa ini tidak akan terjadi - maka terjadi kebocoran memori. Tetapi dengan studi sepintas kode, tidak jelas bahwa ini benar-benar bisa terjadi.

Ada juga posting indah oleh John Sandell tentang menggunakan unit test untuk mendeteksi kebocoran memori untuk kelas. Tetapi dengan contoh di atas, di mana sangat mudah untuk melewatkan kebocoran memori, tidak selalu jelas bagaimana menulis unit test seperti itu. (Tentu saja, kami tidak berbicara di sini dalam hal pengalaman.)

Seperti yang ditulis Guilherme dalam sebuah posting baru-baru ini, fitur-fitur baru dalam aplikasi SoundCloud untuk iOS ditulis menurut "pola arsitektur bersih" - paling sering ini adalah jenis VIPER . Sebagian besar modul VIPER ini dibangun menggunakan apa yang kita sebut ModuleFactory . ModuleFactory semacam itu membutuhkan input, dependensi, dan konfigurasi - dan membuat UIViewController yang sudah terhubung ke modul lainnya dan dapat didorong ke tumpukan navigasi.

Modul VIPER ini dapat memiliki beberapa delegasi, pengamat, dan kesalahan pelarian, yang masing-masing dapat menyebabkan pengontrol tetap berada di memori setelah dihapus dari tumpukan navigasi. Ketika ini terjadi, jumlah memori akan meningkat, dan sistem operasi mungkin memutuskan untuk menghentikan aplikasi.

Jadi mungkinkah menutup kemungkinan banyak kebocoran dengan menulis sesedikit mungkin unit test? Jika tidak, maka semua ini adalah buang-buang waktu.

Tes integrasi


Jawabannya, seperti yang sudah Anda tebak dari judul posting ini, adalah ya. Dan kami melakukan ini melalui pengujian integrasi. Tujuan dari tes integrasi adalah untuk menguji bagaimana benda berinteraksi satu sama lain. Tentu saja, modul VIPER adalah kelompok objek, kebocoran memori adalah salah satu bentuk interaksi yang pasti ingin kita hindari.

Rencana kami sederhana: Kami akan menggunakan ModuleFactory kami untuk instantiate modul VIPER . Kemudian kita akan menghapus tautan ke UIViewController dan memastikan bahwa semua bagian penting dari modul dihancurkan bersama dengan itu.

Masalah pertama yang kita hadapi adalah bahwa pada dasarnya kita tidak dapat dengan mudah mengakses bagian apa pun dari modul VIPER selain UIViewController . Satu-satunya fungsi publik di ModuleFactory kami adalah func make () -> UIViewController . Tetapi bagaimana jika kita menambahkan titik masuk lain hanya untuk pengujian kita? Metode baru ini akan dideklarasikan melalui internal , jadi kita hanya dapat mengaksesnya melalui impor @testable , framework ModuleFactory . Ini akan mengembalikan tautan ke semua bagian terpenting dari modul, yang kemudian dapat kami tahan untuk tautan yang lemah untuk masuk dalam pengujian kami. Ini akhirnya terlihat seperti ini:

 public final class ModuleFactory { //     ,   ... public func make() -> UIViewController { makeAndExpose().view } typealias ModuleComponents = ( view: UIViewController, presenter: Presenter, Interactor: Interactor ) func makeAndExpose() -> ModuleComponents { // Set up code, and then... return ( view: viewController, presenter: presenter, interactor: interactor ) } } 


Ini memecahkan masalah kurangnya akses langsung ke data objek. Jelas, ini tidak ideal, tetapi memenuhi kebutuhan kita, jadi mari kita lanjutkan menulis tes. Ini akan terlihat seperti ini:

 final class ModuleMemoryLeakTests: XCTestCase { //      .     //    . private var view: UIViewController? //        //   ,    // UIKit,   UIViewController  . private weak var presenter: Presenter? private weak var interactor: Interactor? //   setUp    ModuleFactory  //   makeAndExpose.     ,   //     ModuleComponents // ,          . //     . func setUp() { super.setUp() let moduleFactory = ModuleFactory(/* mocked dependencies & config */) let components = moduleFactory.makeAndExpose() view = components.view presenter = components.presenter interactor = components.interactor } //   ,   tearDown   , //        ,     ,   //     . func tearDown() { view = nil presenter = nil interactor = nil super.tearDown() } func test_module_doesNotLeakMemory() { //   ,      . //      ,  //          setUp. XCTAssertNotNil(presenter) XCTAssertNotNil(interactor) //        . //    ,   //     ,    //      . view = nil // ,  ,    //  Presenter  Interactor   . //  ,       //  ,    . XCTAssertNil(presenter) XCTAssertNil(interactor) } } 


Jadi, kami memiliki cara mudah untuk mendeteksi kebocoran memori dalam modul VIPER . Ini sama sekali tidak ideal dan membutuhkan pekerjaan pengguna tertentu untuk setiap modul baru yang ingin kami uji, tetapi ini tentu saja jauh lebih sedikit daripada menulis unit test terpisah untuk setiap kemungkinan kebocoran memori. Ini juga membantu mengidentifikasi kebocoran memori yang bahkan tidak kami duga. Faktanya, setelah menulis beberapa tes ini, terungkap bahwa kami memiliki tes yang tidak lulus, dan setelah beberapa penelitian, kami menemukan kebocoran memori dalam modul. Setelah koreksi, tes harus diulang.

Ini juga memberi kita titik awal untuk menulis serangkaian tes integrasi yang lebih umum untuk modul. Pada akhirnya, jika kita hanya menyimpan tautan yang kuat ke Presenter dan mengganti UIViewController dengan tiruan , kita dapat memalsukan input pengguna, lalu memanggil metode presenter dan memeriksa tampilan dummy data dalam View .

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


All Articles