Ketika saya bertemu Kotlin DSL, saya berpikir: hal yang hebat, sangat disayangkan dalam pengembangan produk ini tidak akan berguna. Namun, saya salah: dia membantu kami membuat cara yang sangat ringkas dan elegan untuk menulis tes UI End-to-end di Android.

Tentang layanan, data uji, dan mengapa tidak sesederhana itu
Untuk mulai dengan, sedikit konteks tentang layanan kami, sehingga Anda mengerti mengapa kami membuat keputusan tertentu.
Kami membantu pencari kerja dan pengusaha untuk saling menemukan:
- majikan mendaftarkan perusahaan mereka dan memposting lowongan
- pencari kerja mencari pekerjaan, menambahkannya ke favorit, berlangganan hasil pencarian, membuat resume dan mengirim umpan balik
Untuk mensimulasikan skenario pengguna nyata dan memastikan bahwa aplikasi bekerja dengan benar pada mereka, kita perlu membuat semua data pengujian ini di server. Anda akan mengatakan: "Jadi buat majikan ujian dan pencari kerja terlebih dahulu, dan kemudian bekerja sama dengan mereka dalam ujian." Tetapi ada beberapa masalah:
- selama pengujian kami mengubah data;
- tes berjalan secara paralel.
Lingkungan dan perlengkapan uji
Tes ujung ke ujung dijalankan di bangku tes. Mereka hampir memiliki lingkungan militer, tetapi tidak ada data nyata. Dalam hal ini, ketika menambahkan data baru, pengindeksan terjadi hampir secara instan.
Untuk menambahkan data ke dudukan, kami menggunakan metode fixture khusus. Mereka menambahkan data langsung ke database dan langsung mengindeksnya:
interface TestFixtureUserApi { @POST("fx/employer/create") fun createEmployerUser(@Body employer: TestEmployer): Call<TestEmployer> }
Fixture hanya tersedia dari jaringan lokal dan hanya untuk test stand. Metode dipanggil dari tes segera sebelum memulai Kegiatan mulai.
DSL
Jadi kami sampai ke juiciest. Bagaimana set data untuk pengujian?
initialisation{ applicant { resume { title = "Resume for similar Vacancy" isOptional = true resumeStatus = ResumeStatus.APPROVED } resume { title = "Some other Resume" } } employer { vacancy { title = "Resume for similar Vacancy" } vacancy { title = "Resume for similar Vacancy" description = "Working hard" } vacancy { title = "Resume for similar Vacancy" description = "Working very hard" } } }
Di blok inisialisasi, kami memulai entitas yang diperlukan untuk pengujian: dalam contoh di atas, kami membuat satu pelamar dengan dua resume, serta satu majikan yang menyediakan beberapa lowongan.
Untuk menghilangkan kesalahan yang terkait dengan persimpangan data pengujian, kami membuat pengidentifikasi unik untuk pengujian dan untuk setiap entitas.
Hubungan antar Entitas
Apa batasan utama ketika bekerja dengan DSL? Karena struktur pohonnya, agak sulit untuk membangun koneksi antara cabang-cabang pohon yang berbeda.
Misalnya, dalam aplikasi kami untuk pelamar ada bagian "Lowongan yang cocok untuk resume". Agar lowongan muncul di daftar ini, kita perlu mengaturnya sedemikian rupa sehingga terkait dengan resume pengguna saat ini.
initialisation { applicant { resume { title = "TEST_VACANCY_$uniqueTestId" } } employer { vacancy { title = "TEST_VACANCY_$uniqueTestId" } } }
Pengidentifikasi tes unik digunakan untuk ini. Jadi, ketika bekerja dengan aplikasi, lowongan yang ditentukan direkomendasikan untuk resume ini. Selain itu, penting untuk dicatat bahwa tidak ada lowongan lain akan muncul di daftar ini.
Inisialisasi data dengan tipe yang sama
Tetapi bagaimana jika Anda perlu membuat banyak lowongan? Apakah setiap blok jadi salinan? Tentu tidak! Kami membuat metode dengan blok lowongan, yang menunjukkan jumlah lowongan yang diperlukan dan transformator untuk mendiversifikasi mereka tergantung pada pengidentifikasi unik.
initialisation { employer { vacancyBlock { size = 10 transformer = { it.also { vacancyDsl -> vacancyDsl.description = "Some description with text ${vacancyDsl.uniqueVacancyId}" } } } } }
Di blok lowongan, kami menunjukkan berapa banyak klon lowongan yang perlu kami buat dan cara mengubahnya tergantung pada nomor seri.
Bekerja dengan data dalam tes
Selama tes, bekerja dengan data menjadi sangat sederhana. Semua data yang kami buat tersedia untuk kami. Dalam implementasi kami, mereka disimpan dalam pembungkus khusus untuk koleksi. Data dapat diperoleh dari mereka berdua dengan nomor pesanan pekerjaan (lowongan [0]), jadi dengan tag yang dapat diatur dalam dsl (lowongan ["lowongan saya"]), dan dengan pintasan (vacancies.first ()
TaggedItemContainer class TaggedItemContainer<T>( private val items: MutableList<TaggedItem<T>> ) { operator fun get(index: Int): T { return items[index].data } operator fun get(tag: String): T { return items.first { it.tag == tag }.data } operator fun plusAssign(item: TaggedItem<T>) { items += item } fun forEach(action: (T) -> Unit) { for (item in items) action.invoke(item.data) } fun first(): T { return items[0].data } fun second(): T { return items[1].data } fun third(): T { return items[2].data } fun last(): T { return items[items.size - 1].data } }
Dalam hampir 100% kasus, saat menulis tes, kami menggunakan metode pertama () dan kedua (), sisanya disimpan untuk fleksibilitas. Di bawah ini adalah contoh dari tes dengan inisialisasi dan langkah-langkah pada Kakao
initialisation { applicant { resume { title = "TEST_VACANCY_$uniqueTestId" } } }.run { mainScreen { positionField { click() } jobPositionScreen { positionEntry(vacancies.first().title) } searchButton { click() } } }
Apa yang tidak cocok di DSL
Bisakah semua data masuk ke dalam DSL? Tujuan kami adalah menjaga DSL sesingkat dan sesederhana mungkin. Dalam implementasi kami, karena fakta bahwa urutan pekerjaan pelamar dan pengusaha tidak penting, tidak mungkin untuk mencocokkan hubungan mereka - tanggapan.
Pembuatan respons sudah dilakukan di blok berikutnya oleh operasi pada entitas yang sudah dibuat di server.
Implementasi DSL
Seperti yang Anda pahami dari artikel tersebut, algoritma untuk menentukan data tes dan melakukan tes adalah sebagai berikut:
- Bagian dari DSL diurai dalam inisialisasi;
- Berdasarkan nilai yang diperoleh, data uji dibuat di server;
- Blok transformasi opsional dijalankan, di mana Anda dapat mengatur respons;
- Tes dilakukan dengan dataset yang sudah final.
Parsing data dari blok inisialisasi
Sihir macam apa yang terjadi di sana? Pertimbangkan bagaimana elemen TestCaseDsl tingkat atas dibangun:
@TestCaseDslMarker class TestCaseDsl { val applicants = mutableListOf<ApplicantDsl>() val employers = mutableListOf<EmployerDsl>() val uniqueTestId = CommonUtils.unique fun applicant(block: ApplicantDsl.() -> Unit = {}) { val applicantDsl = ApplicantDsl( uniqueTestId, uniqueApplicantId = CommonUtils.unique applicantDsl.block() applicants += applicantDsl } fun employer(block: EmployerDsl.() -> Unit = {}) { val employerDsl = EmployerDsl( uniqueTestId = uniqueTestId, uniqueEmployerId = CommonUtils.unique employerDsl.block() employers += employerDsl } }
Dalam metode pelamar, kami membuat ApplicantDsl.
ApplicantDsl @TestCaseDslMarker class ApplicantDsl( val uniqueTestId: String, val uniqueApplicantId: String, var tag: String? = null, var login: String? = null, var password: String? = null, var firstName: String? = null, var middleName: String? = null, var lastName: String? = null, var email: String? = null, var siteId: Int? = null, var areaId: Int? = null, var resumeViewLimit: Int? = null, var isMailingSubscription: Boolean? = null ) { val resumes = mutableListOf<ResumeDsl>() fun resume(block: ResumeDsl.() -> Unit = {}) { val resumeDslBuilder = ResumeDsl( uniqueTestId = uniqueTestId, uniqueApplicantId = uniqueApplicantId, uniqueResumeId = CommonUtils.unique ) resumeDslBuilder.apply(block) this.resumes += resumeDslBuilder } }
Kemudian kami melakukan operasi di atasnya dari blok blok: ApplicantDsl. () -> Unit. Desain inilah yang memungkinkan kami untuk dengan mudah beroperasi dengan bidang ApplicantDsl di DSL kami.
Harap perhatikan bahwa uniqueTestId dan uniqueApplicantId (pengidentifikasi unik untuk menghubungkan entitas di antara mereka sendiri) pada saat pelaksanaan blok sudah ditetapkan dan kami dapat mengaksesnya.
Blok inisialisasi secara internal memiliki struktur yang serupa:
fun initialisation(block: TestCaseDsl.() -> Unit): Initialisation { val testCaseDsl = TestCaseDsl().apply(block) val testCase = TestCaseCreator.create(testCaseDsl) return Initialisation(testCase) }
Kami membuat tes, menerapkan tindakan blokir untuk itu, lalu menggunakan TestCaseCreator untuk membuat data di server dan memasukkannya ke dalam koleksi. Fungsi TestCaseCreator.create () cukup sederhana - kita beralih pada data dan membuatnya di server.
Perangkap dan ide
Beberapa tes sangat mirip dan hanya berbeda dalam data input dan cara mengendalikan tampilan mereka (misalnya, ketika mata uang yang berbeda ditunjukkan dalam lowongan).
Dalam kasus kami, ada beberapa tes seperti itu, dan kami memutuskan untuk tidak mengacaukan DSL dengan sintaks khusus
Pada hari-hari sebelum DSL, kami mengindeks data untuk waktu yang lama, dan untuk menghemat waktu kami melakukan banyak tes dalam satu kelas dan membuat semua data dalam blok statis.
Jangan lakukan ini - itu akan membuat tidak mungkin bagi Anda untuk memulai kembali tes yang jatuh. Faktanya adalah bahwa selama peluncuran uji jatuh, kita bisa mengubah data awal di server. Misalnya, kami dapat menambahkan lowongan ke favorit Anda. Kemudian, ketika Anda memulai ulang tes, mengklik tanda bintang akan sebaliknya mengarah pada penghapusan lowongan dari daftar favorit, dan ini adalah perilaku yang tidak kami harapkan.
Ringkasan
Metode menentukan data uji sangat menyederhanakan pekerjaan dengan tes:
Saat menulis tes, Anda tidak perlu memikirkan apakah ada server dan dalam urutan apa Anda perlu menginisialisasi data;
Semua entitas yang dapat diatur di server dengan mudah muncul dalam petunjuk IDE;
Ada satu cara untuk menginisialisasi dan berkomunikasi data satu sama lain.
Materi Terkait
Jika Anda tertarik pada pendekatan kami untuk pengujian UI, maka sebelum Anda mulai, saya sarankan Anda membiasakan diri dengan materi berikut:
Apa selanjutnya
Artikel ini adalah yang pertama dari seri tentang alat dan kerangka kerja tingkat tinggi untuk menulis dan mendukung tes UI di Android. Ketika bagian-bagian baru tersedia, saya akan menautkannya ke artikel ini.