Anda akan menjawab untuk semuanya! Kontrak yang Didorong Konsumen melalui mata pengembang

Pada artikel ini, kita akan berbicara tentang masalah yang dipecahkan oleh Kontrak yang Didorong Konsumen, dan menunjukkan cara menerapkannya dengan menggunakan contoh Pakta dengan Node.js dan Spring Boot. Dan bicarakan keterbatasan pendekatan ini.


Masalah


Saat menguji produk, pengujian skenario sering digunakan di mana integrasi berbagai komponen sistem dalam lingkungan yang dipilih khusus diperiksa. Tes semacam itu pada layanan langsung memberikan hasil yang paling dapat diandalkan (tidak termasuk tes dalam pertempuran). Tetapi pada saat yang sama, mereka adalah salah satu yang paling mahal.

  • Sering keliru dipercaya bahwa lingkungan integrasi seharusnya tidak toleran terhadap kesalahan. SLA, jaminan untuk lingkungan seperti itu jarang diucapkan, tetapi jika itu tidak tersedia, tim harus menunda rilis, atau berharap untuk yang terbaik dan pergi ke pertempuran tanpa ujian. Meskipun semua orang tahu bahwa harapan bukanlah strategi . Dan teknologi infrastruktur bermodel baru hanya mempersulit pekerjaan dengan lingkungan integrasi.
  • Nyeri lain bekerja dengan data uji . Banyak skenario membutuhkan kondisi sistem tertentu, perlengkapan. Seberapa dekat mereka dengan data pertempuran? Bagaimana cara memperbaruinya sebelum ujian dan membersihkannya setelah selesai?
  • Tes terlalu tidak stabil . Dan bukan hanya karena infrastruktur yang kami sebutkan di paragraf pertama. Tes mungkin gagal karena tim tetangga meluncurkan cek sendiri yang merusak kondisi sistem yang diharapkan! Banyak cek negatif palsu, tes tidak pasti mengakhiri hidup mereka di @Ignored . Juga, berbagai bagian integrasi dapat didukung oleh tim yang berbeda. Mereka meluncurkan kandidat rilis baru dengan kesalahan - mereka menghancurkan semua konsumen. Seseorang memecahkan masalah ini dengan loop tes khusus. Tetapi dengan biaya mengalikan biaya dukungan.
  • Tes semacam itu membutuhkan banyak waktu . Bahkan dengan pemikiran otomatis, hasil dapat diharapkan selama berjam-jam.
  • Dan to top it off, jika tes benar-benar jatuh, maka jauh dari selalu mungkin untuk segera menemukan penyebab masalahnya. Itu bisa bersembunyi jauh di balik lapisan integrasi. Atau mungkin merupakan hasil kombinasi tak terduga dari banyak komponen sistem.

Tes yang stabil dalam lingkungan integrasi memerlukan investasi serius dari QA, dev, dan bahkan ops. Tidak heran mereka berada di puncak piramida tes . Tes semacam itu berguna, tetapi ekonomi sumber daya tidak memungkinkan mereka memeriksa semuanya. Sumber utama nilainya adalah lingkungan.

Di bawah piramida yang sama adalah tes lain di mana kami bertukar kepercayaan untuk sakit kepala dukungan yang lebih kecil - menggunakan pemeriksaan isolasi. Granular, semakin kecil skala tes, semakin sedikit ketergantungan pada lingkungan eksternal. Di bagian paling bawah piramida adalah unit test. Kami memeriksa fungsi individu, kelas, kami beroperasi tidak begitu banyak dengan semantik bisnis seperti halnya dengan konstruksi implementasi tertentu. Tes-tes ini memberikan umpan balik cepat.

Tapi begitu kita turun piramida, kita harus mengganti lingkungan dengan sesuatu. Rintisan bertopik muncul - sebagai keseluruhan layanan, dan entitas individu dari bahasa pemrograman. Dengan bantuan colokan, kita dapat menguji komponen secara terpisah. Tetapi mereka juga mengurangi validitas cek. Bagaimana cara memastikan bahwa rintisan mengembalikan data yang benar? Bagaimana memastikan kualitasnya?

Solusinya dapat berupa dokumentasi komprehensif yang menggambarkan berbagai skenario dan kemungkinan status komponen sistem. Tetapi setiap formulasi masih meninggalkan kebebasan interpretasi. Oleh karena itu, dokumentasi yang baik adalah artefak hidup yang terus membaik seiring tim memahami bidang masalahnya. Lalu bagaimana memastikan kepatuhan dengan bertopik dokumentasi?

Pada banyak proyek, Anda dapat mengamati situasi di mana tulisan rintisan ditulis oleh orang yang sama yang mengembangkan artefak tes. Misalnya, pengembang aplikasi seluler membuat sendiri selokan untuk pengujian mereka. Akibatnya, programmer dapat memahami dokumentasi dengan caranya sendiri (yang benar-benar normal), mereka membuat tulisan rintisan dengan perilaku yang diharapkan salah, menulis kode sesuai dengan itu (dengan tes hijau), dan kesalahan terjadi selama integrasi nyata.

Selain itu, dokumentasi biasanya bergerak ke hilir - klien menggunakan spesifikasi layanan (dalam hal ini, layanan lain dapat menjadi klien dari layanan). Itu tidak mengungkapkan bagaimana konsumen menggunakan data, data apa yang dibutuhkan sama sekali, asumsi apa yang mereka buat untuk data itu. Konsekuensi dari ketidaktahuan ini adalah hukum Hyrum .



Hyrum Wright telah mengembangkan alat publik di dalam Google untuk waktu yang lama dan telah mengamati bagaimana perubahan terkecil dapat menyebabkan gangguan bagi pelanggan yang menggunakan fitur tersirat (tidak terdokumentasi) dari perpustakaannya. Konektivitas tersembunyi semacam itu menyulitkan evolusi API.

Masalah-masalah ini dapat diselesaikan sampai batas tertentu menggunakan Kontrak yang Didorong Konsumen. Seperti pendekatan dan alat apa pun, alat ini memiliki serangkaian penerapan dan biaya, yang juga akan kami pertimbangkan. Implementasi pendekatan ini telah mencapai tingkat kematangan yang cukup untuk mencoba proyek mereka.

Apa itu CDC?


Tiga elemen kunci:

  • Kontrak Dijelaskan menggunakan beberapa DSL, implementasi tergantung. Ini berisi deskripsi API dalam bentuk skenario interaksi: jika permintaan tertentu tiba, maka klien harus menerima respons tertentu.
  • Tes pelanggan . Selain itu, mereka menggunakan rintisan, yang secara otomatis dihasilkan dari kontrak.
  • Tes untuk API . Mereka juga dihasilkan dari kontrak.

Dengan demikian, kontrak dapat dieksekusi. Dan fitur utama dari pendekatan ini adalah persyaratan untuk perilaku API naik, dari klien ke server.

Kontrak berfokus pada perilaku yang benar - benar penting bagi konsumen. Jadikan asumsinya tentang API secara eksplisit.

Tujuan utama CDC adalah untuk membawa pemahaman tentang perilaku API kepada pengembang dan pengembang kliennya. Pendekatan ini dikombinasikan dengan BDD, pada pertemuan tiga amigo Anda dapat membuat sketsa kosong untuk kontrak. Pada akhirnya, kontrak ini juga berfungsi untuk meningkatkan komunikasi; berbagi pemahaman bersama tentang area masalah dan mengimplementasikan solusi di dalam dan di antara tim.

Pakta


Pertimbangkan untuk menggunakan CDC sebagai contoh dari Pact, salah satu implementasinya. Misalkan kita membuat aplikasi web untuk peserta konferensi. Dalam iterasi berikutnya, tim mengembangkan jadwal presentasi - sejauh ini tanpa cerita seperti voting atau catatan, hanya output dari grid laporan. Kode sumber untuk contoh ada di sini .

Pada pertemuan tiga empat amigo, sebuah produk, penguji, pengembang backend dan aplikasi mobile bertemu. Mereka mengatakan itu

  • Daftar dengan teks akan ditampilkan di UI: Judul laporan + Pembicara + Tanggal dan waktu.
  • Untuk melakukan ini, backend harus mengembalikan data seperti pada contoh di bawah ini.

 { "talks":[ { "title":"      ", "speakers":[ { "name":" " } ], "time":"2019-05-27T12:00:00+03:00" } ] } 

Setelah itu pengembang frontend menulis kode klien (backend untuk frontend). Dia menginstal pakta kontrak kontrak dalam proyek:

 yarn add --dev @pact-foundation/pact 

Dan mulai menulis ujian. Ini mengkonfigurasi server rintisan lokal, yang akan mensimulasikan layanan dengan jadwal laporan:

 const provider = new Pact({ //      consumer: "schedule-consumer", provider: "schedule-producer", // ,     port: pactServerPort, //  pact     log: path.resolve(process.cwd(), "logs", "pact.log"), // ,     dir: path.resolve(process.cwd(), "pacts"), logLevel: "WARN", //  DSL  spec: 2 }); 

Kontrak adalah file JSON yang menggambarkan skenario klien yang berinteraksi dengan layanan. Tetapi Anda tidak perlu mendeskripsikannya secara manual, karena ini dibentuk dari pengaturan rintisan dalam kode. Pengembang sebelum tes menjelaskan perilaku berikut.

 provider.setup().then(() => provider .addInteraction({ uponReceiving: "a request for schedule", withRequest: { method: "GET", path: "/schedule" }, willRespondWith: { status: 200, headers: { "Content-Type": "application/json;charset=UTF-8" }, body: { talks: [ { title: "      ", speakers: [ { name: " " } ], time: "2019-05-27T12:00:00+03:00" } ] } } }) .then(() => done()) ); 

Di sini, dalam contoh, kami menentukan permintaan layanan spesifik yang diharapkan, tetapi pact-js juga mendukung beberapa metode untuk menentukan kecocokan .

Akhirnya, programmer menulis tes bagian kode yang menggunakan rintisan ini. Dalam contoh berikut, kami akan menyebutnya langsung untuk kesederhanaan.

 it("fetches schedule", done => { fetch(`http://localhost:${pactServerPort}/schedule`) .then(response => response.json()) .then(json => expect(json).toStrictEqual({ talks: [ { title: "      ", speakers: [ { name: " " } ], time: "2019-05-27T12:00:00+03:00" } ] })) .then(() => done()); }); 

Dalam proyek nyata, ini bisa berupa unit tes cepat dari fungsi interpretasi respon yang terpisah atau tes UI lambat untuk menampilkan data yang diterima dari layanan.

Selama uji coba, pakta memverifikasi bahwa rintisan menerima permintaan yang ditentukan dalam tes. Perbedaan ini dapat dilihat sebagai perbedaan pada file pact.log.

 E, [2019-05-21T01:01:55.810194 #78394] ERROR -- : Diff with interaction: "a request for schedule" Diff -------------------------------------- Key: - is expected + is actual Matching keys and values are not shown { "headers": { - "Accept": "application/json" + "Accept": "*/*" } } Description of differences -------------------------------------- * Expected "application/json" but got "*/*" at $.headers.Accept 


Jika tes berhasil, kontrak dihasilkan dalam format JSON. Ini menjelaskan perilaku yang diharapkan dari API.

 { "consumer": { "name": "schedule-consumer" }, "provider": { "name": "schedule-producer" }, "interactions": [ { "description": "a request for schedule", "request": { "method": "GET", "path": "/schedule", "headers": { "Accept": "application/json" } }, "response": { "status": 200, "headers": { "Content-Type": "application/json;charset=UTF-8" }, "body": { "talks":[ { "title":"      ", "speakers":[ { "name":" " } ], "time":"2019-05-27T12:00:00+03:00" } ] }}} ], "metadata": { "pactSpecification": { "version": "2.0.0" } } } 

Dia memberikan kontrak ini kepada pengembang backend. Katakanlah API ada di Spring Boot. Pact memiliki perpustakaan pact-jvm-provider-spring yang dapat bekerja dengan MockMVC. Tetapi kita akan melihat pada Spring Cloud Contract, yang mengimplementasikan CDC dalam ekosistem Spring. Ia menggunakan format kontraknya sendiri, tetapi juga memiliki titik ekstensi untuk menghubungkan konverter dari format lain. Format kontrak aslinya hanya didukung oleh Kontrak Spring Cloud itu sendiri - tidak seperti Pact, yang memiliki perpustakaan untuk JVM, Ruby, JS, Go, Python, dll.

Misalkan, dalam contoh kita, pengembang backend menggunakan Gradle untuk membangun layanan. Ini menghubungkan dependensi berikut:

 buildscript { // ... dependencies { classpath "org.springframework.cloud:spring-cloud-contract-pact:2.1.1.RELEASE" } } plugins { id "org.springframework.cloud.contract" version "2.1.1.RELEASE" // ... } // ... dependencies { // ... testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier' } 

Dan itu menempatkan kontrak Pakta yang diterima dari frotender ke direktori src/test/resources/contracts .

Dari situ, secara default, plugin spring-cloud-contract mengurangi kontrak. Selama perakitan, tugas gradle generateContractTests dieksekusi, yang menghasilkan tes berikut di direktori build / generate-test-sources.

 public class ContractVerifierTest extends ContractsBaseTest { @Test public void validate_aggregator_client_aggregator_service() throws Exception { // given: MockMvcRequestSpecification request = given() .header("Accept", "application/json"); // when: ResponseOptions response = given().spec(request) .get("/scheduler"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).isEqualTo("application/json;charset=UTF-8"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).array("['talks']").array("['speakers']").contains("['name']").isEqualTo( /*...*/ ); assertThatJson(parsedJson).array("['talks']").contains("['time']").isEqualTo( /*...*/ ); assertThatJson(parsedJson).array("['talks']").contains("['title']").isEqualTo( /*...*/ ); } } 


Saat memulai tes ini, kita akan melihat kesalahan:

 java.lang.IllegalStateException: You haven't configured a MockMVC instance. You can do this statically 

Karena kita dapat menggunakan alat yang berbeda untuk pengujian, kita perlu memberi tahu plug-in mana yang telah kita konfigurasikan. Ini dilakukan melalui kelas dasar, yang akan mewarisi tes yang dihasilkan dari kontrak.

 public abstract class ContractsBaseTest { private ScheduleController scheduleController = new ScheduleController(); @Before public void setup() { RestAssuredMockMvc.standaloneSetup(scheduleController); } } 


Untuk menggunakan kelas dasar ini selama pembuatan, Anda perlu mengonfigurasi plugin gradle spring-cloud-contract.

 contracts { baseClassForTests = 'ru.example.schedule.ContractsBaseTest' } 


Sekarang kami memiliki tes berikut yang dihasilkan:
 public class ContractVerifierTest extends ContractsBaseTest { @Test public void validate_aggregator_client_aggregator_service() throws Exception { // ... } } 

Tes dimulai dengan sukses, tetapi gagal dengan kesalahan verifikasi - pengembang belum menulis implementasi layanan. Tapi sekarang dia bisa melakukannya berdasarkan kontrak. Dia dapat memastikan bahwa dia dapat memproses permintaan klien dan mengembalikan respons yang diharapkan.

Pengembang layanan mengetahui melalui kontrak apa yang perlu dia lakukan, perilaku apa yang harus diterapkan.

Pakta dapat diintegrasikan lebih dalam ke dalam proses pengembangan. Anda dapat menggunakan Pact-broker yang mengagregasi kontrak tersebut, mendukung versi mereka, dan dapat menampilkan grafik ketergantungan.



Mengunggah kontrak baru yang dihasilkan ke pialang dapat dilakukan pada langkah CI saat membangun klien. Dan dalam kode server menunjukkan pemuatan dinamis kontrak dengan URL. Kontrak Spring Cloud juga mendukung ini.

Penerapan CDC


Apa batasan dari Kontrak yang Didorong Konsumen?

Untuk menggunakan pendekatan ini, Anda harus membayar dengan alat tambahan seperti pakta. Kontrak per se adalah artefak tambahan, abstraksi lain yang harus dipelihara dengan hati-hati dan secara sadar menerapkan praktik-praktik rekayasa.

Mereka tidak menggantikan tes e2e , karena bertopik masih tetap bertopik - model komponen sistem nyata, yang mungkin sedikit, tetapi tidak sesuai dengan kenyataan. Melalui mereka, skenario kompleks tidak dapat diverifikasi.

Juga, CDC tidak menggantikan tes fungsional API . Mereka lebih mahal untuk didukung daripada Tes Unit Lama Biasa. Pengembang pakta merekomendasikan menggunakan heuristik berikut - jika Anda menghapus kontrak dan ini tidak menyebabkan kesalahan atau salah tafsir oleh klien, maka itu tidak diperlukan. Misalnya, tidak perlu untuk menguraikan sepenuhnya semua kode kesalahan API melalui kontrak jika klien memprosesnya dengan cara yang sama. Dengan kata lain, kontrak hanya menjelaskan untuk layanan yang penting bagi kliennya . Tidak lebih, tetapi tidak kurang.

Terlalu banyak kontrak juga mempersulit evolusi API. Setiap kontrak tambahan adalah kesempatan untuk tes merah . Adalah penting untuk merancang CDC sedemikian rupa sehingga setiap pengujian gagal membawa beban semantik yang berguna melebihi biaya dukungannya. Misalnya, jika kontrak menetapkan panjang minimum bidang teks tertentu yang tidak peduli pada konsumen (ia menggunakan teknik Pembaca Toleran ), maka setiap perubahan pada nilai minimum ini akan memutus kontrak dan saraf orang-orang di sekitarnya. Pemeriksaan semacam itu perlu ditransfer ke tingkat API itu sendiri dan dilaksanakan tergantung pada sumber pembatasan.

Kesimpulan


CDC meningkatkan kualitas produk dengan menjelaskan perilaku integrasi secara eksplisit. Ini membantu pelanggan dan pengembang layanan untuk mencapai pemahaman bersama, memungkinkan Anda untuk berbicara melalui kode. Tetapi ini dilakukan dengan biaya menambahkan alat, memperkenalkan abstraksi baru dan tindakan tambahan dari anggota tim.

Pada saat yang sama, alat dan kerangka kerja CDC sedang dikembangkan secara aktif dan telah mencapai kematangan untuk pengujian pada proyek Anda. Tes :)

Pada konferensi QualityConf pada 27-28 Mei, Andrei Markelov akan berbicara tentang teknik pengujian pada prod, dan Arthur Khineltsev akan berbicara tentang pemantauan front-end yang sangat dimuat, ketika harga bahkan kesalahan kecil adalah puluhan ribu pengguna yang sedih.

Ayo ngobrol untuk kualitas!

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


All Articles