Panduan Node.js untuk Sebenarnya Melakukan Tes Integrasi

Diterbitkan: 2022-03-11

Tes integrasi bukanlah sesuatu yang harus ditakuti. Mereka adalah bagian penting untuk menguji aplikasi Anda sepenuhnya.

Ketika berbicara tentang pengujian, kami biasanya memikirkan pengujian unit di mana kami menguji sebagian kecil kode secara terpisah. Namun, aplikasi Anda lebih besar dari potongan kecil kode itu dan hampir tidak ada bagian dari aplikasi Anda yang bekerja secara terpisah. Di sinilah tes integrasi membuktikan pentingnya mereka. Tes integrasi mengambil tempat di mana tes unit gagal, dan mereka menjembatani kesenjangan antara tes unit dan tes ujung ke ujung.

Anda tahu Anda perlu menulis tes integrasi, jadi mengapa Anda tidak melakukannya?
Menciak

Dalam artikel ini, Anda akan mempelajari cara menulis pengujian integrasi yang dapat dibaca dan disusun dengan contoh dalam aplikasi berbasis API.

Meskipun kami akan menggunakan JavaScript/Node.js untuk semua contoh kode dalam artikel ini, sebagian besar ide yang dibahas dapat dengan mudah disesuaikan dengan pengujian integrasi pada platform apa pun.

Tes Unit vs Tes Integrasi: Anda Membutuhkan Keduanya

Tes unit fokus pada satu unit kode tertentu. Seringkali, ini adalah metode khusus atau fungsi dari komponen yang lebih besar.

Tes ini dilakukan secara terpisah, di mana semua dependensi eksternal biasanya dimatikan atau diejek.

Dengan kata lain, dependensi diganti dengan perilaku yang telah diprogram sebelumnya, memastikan bahwa hasil tes hanya ditentukan oleh kebenaran unit yang diuji.

Anda dapat mempelajari lebih lanjut tentang pengujian unit di sini.

Tes unit digunakan untuk memelihara kode berkualitas tinggi dengan desain yang baik. Mereka juga memungkinkan kita untuk dengan mudah menutupi kasus sudut.

Kekurangannya, bagaimanapun, adalah bahwa unit test tidak dapat mencakup interaksi antar komponen. Di sinilah tes integrasi menjadi berguna.

Tes Integrasi

Jika pengujian unit ditentukan dengan menguji unit kode terkecil secara terpisah, maka pengujian integrasi adalah kebalikannya.

Tes integrasi digunakan untuk menguji beberapa unit (komponen) yang lebih besar dalam interaksi, dan terkadang bahkan dapat menjangkau banyak sistem.

Tujuan dari tes integrasi adalah untuk menemukan bug dalam koneksi dan dependensi antara berbagai komponen, seperti:

  • Melewati argumen yang tidak valid atau salah dipesan
  • Skema database rusak
  • Integrasi cache tidak valid
  • Cacat dalam logika bisnis atau kesalahan dalam aliran data (karena pengujian sekarang dilakukan dari pandangan yang lebih luas).

Jika komponen yang kami uji tidak memiliki logika yang rumit (misalnya komponen dengan kompleksitas siklomatik minimal), pengujian integrasi akan jauh lebih penting daripada pengujian unit.

Dalam hal ini, pengujian unit akan digunakan terutama untuk menegakkan desain kode yang baik.

Sementara pengujian unit membantu memastikan bahwa fungsi ditulis dengan benar, pengujian integrasi membantu memastikan bahwa sistem bekerja dengan baik secara keseluruhan. Jadi, baik pengujian unit maupun pengujian integrasi masing-masing memiliki tujuan yang saling melengkapi, dan keduanya penting untuk pendekatan pengujian yang komprehensif.

Tes unit dan tes integrasi seperti dua sisi mata uang yang sama. Koin tidak valid tanpa keduanya.

Oleh karena itu, pengujian belum selesai sampai Anda menyelesaikan pengujian integrasi dan pengujian unit.

Siapkan Suite untuk Tes Integrasi

Meskipun menyiapkan rangkaian pengujian untuk pengujian unit cukup mudah, menyiapkan rangkaian pengujian untuk pengujian integrasi seringkali lebih menantang.

Misalnya, komponen dalam pengujian integrasi dapat memiliki dependensi yang berada di luar proyek, seperti database, sistem file, penyedia email, layanan pembayaran eksternal, dan sebagainya.

Kadang-kadang, tes integrasi perlu menggunakan layanan dan komponen eksternal ini, dan kadang-kadang dapat di-stub.

Ketika mereka dibutuhkan, itu dapat menyebabkan beberapa tantangan.

  • Eksekusi pengujian yang rapuh: Layanan eksternal dapat tidak tersedia, mengembalikan respons yang tidak valid, atau berada dalam status tidak valid. Dalam beberapa kasus, ini dapat menghasilkan positif palsu, di lain waktu dapat menghasilkan negatif palsu.
  • Eksekusi lambat: Mempersiapkan dan menghubungkan ke layanan eksternal bisa lambat. Biasanya, pengujian dijalankan pada server eksternal sebagai bagian dari CI.
  • Penyiapan pengujian yang rumit: Layanan eksternal harus dalam kondisi yang diinginkan untuk pengujian. Misalnya, database harus dimuat sebelumnya dengan data uji yang diperlukan, dll.

Petunjuk Yang Harus Diikuti Saat Menulis Tes Integrasi

Tes integrasi tidak memiliki aturan ketat seperti tes unit. Meskipun demikian, ada beberapa petunjuk umum yang harus diikuti saat menulis tes integrasi.

Tes Berulang

Urutan pengujian atau dependensi tidak boleh mengubah hasil pengujian. Menjalankan tes yang sama beberapa kali harus selalu mengembalikan hasil yang sama. Ini bisa sulit dicapai jika pengujian menggunakan Internet untuk terhubung ke layanan pihak ketiga. Namun, masalah ini dapat diselesaikan melalui penghentian dan ejekan.

Untuk dependensi eksternal yang dapat Anda kendalikan lebih, menyiapkan langkah-langkah sebelum dan sesudah pengujian integrasi akan membantu memastikan bahwa pengujian selalu dijalankan mulai dari status yang sama.

Menguji Tindakan yang Relevan

Untuk menguji semua kemungkinan kasus, pengujian unit adalah pilihan yang jauh lebih baik.

Tes integrasi lebih berorientasi pada koneksi antar modul, maka pengujian skenario bahagia biasanya merupakan cara yang harus dilakukan karena akan mencakup koneksi penting antar modul.

Tes dan Pernyataan yang Dapat Dipahami

Satu tampilan cepat dari pengujian harus memberi tahu pembaca apa yang sedang diuji, bagaimana lingkungan diatur, apa yang di-stub, kapan pengujian dijalankan, dan apa yang ditegaskan. Pernyataan harus sederhana dan menggunakan pembantu untuk perbandingan dan pencatatan yang lebih baik.

Pengaturan Tes Mudah

Mendapatkan tes ke keadaan awal harus sesederhana dan sejelas mungkin.

Hindari Menguji Kode Pihak Ketiga

Meskipun layanan pihak ketiga dapat digunakan dalam pengujian, tidak perlu mengujinya. Dan jika Anda tidak mempercayainya, Anda mungkin tidak boleh menggunakannya.

Biarkan Kode Produksi Bebas dari Kode Tes

Kode produksi harus bersih dan lugas. Mencampur kode uji dengan kode produksi akan menghasilkan dua domain yang tidak dapat dihubungkan yang digabungkan bersama.

Pencatatan yang relevan

Tes yang gagal tidak terlalu berharga tanpa pencatatan yang baik.

Ketika tes lulus, tidak diperlukan logging tambahan. Tetapi ketika mereka gagal, penebangan ekstensif sangat penting.

Logging harus berisi semua kueri database, permintaan dan tanggapan API, serta perbandingan lengkap dari apa yang ditegaskan. Ini secara signifikan dapat memfasilitasi debugging.

Tes yang Baik Terlihat Bersih dan Dapat Dipahami

Tes sederhana yang mengikuti pedoman di sini bisa terlihat seperti ini:

 const co = require('co'); const test = require('blue-tape'); const factory = require('factory'); const superTest = require('../utils/super_test'); const testEnvironment = require('../utils/test_environment_preparer'); const path = '/v1/admin/recipes'; test(`API GET ${path}`, co.wrap(function* (t) { yield testEnvironment.prepare(); const recipe1 = yield factory.create('recipe'); const recipe2 = yield factory.create('recipe'); const serverResponse = yield superTest.get(path); t.deepEqual(serverResponse.body, [recipe1, recipe2]); }));

Kode di atas sedang menguji API ( GET /v1/admin/recipes ) yang mengharapkannya mengembalikan larik resep yang disimpan sebagai respons.

Anda dapat melihat bahwa pengujian, sesederhana mungkin, bergantung pada banyak utilitas. Ini umum untuk semua rangkaian uji integrasi yang baik.

Komponen pembantu memudahkan penulisan tes integrasi yang dapat dipahami.

Mari kita tinjau komponen apa yang diperlukan untuk pengujian integrasi.

Komponen Pembantu

Rangkaian pengujian yang komprehensif memiliki beberapa bahan dasar, termasuk: kontrol aliran, kerangka pengujian, penangan database, dan cara untuk terhubung ke API backend.

Alur kontrol

Salah satu tantangan terbesar dalam pengujian JavaScript adalah aliran asinkron.

Panggilan balik dapat mendatangkan malapetaka dalam kode dan janji saja tidak cukup. Di sinilah flow helper menjadi berguna.

Sambil menunggu async/menunggu untuk didukung sepenuhnya, perpustakaan dengan perilaku serupa dapat digunakan. Tujuannya adalah untuk menulis kode yang dapat dibaca, ekspresif, dan kuat dengan kemungkinan memiliki aliran asinkron.

Co memungkinkan kode untuk ditulis dengan cara yang bagus sambil membuatnya tetap non-blocking. Hal ini dilakukan melalui pendefinisian fungsi co generator dan kemudian menghasilkan hasil.

Solusi lain adalah dengan menggunakan Bluebird. Bluebird adalah perpustakaan janji yang memiliki fitur yang sangat berguna seperti penanganan array, kesalahan, waktu, dll.

Co dan Bluebird coroutine berperilaku mirip dengan async/menunggu di ES7 (menunggu resolusi sebelum melanjutkan), satu-satunya perbedaan adalah bahwa ia akan selalu mengembalikan janji, yang berguna untuk menangani kesalahan.

Kerangka Pengujian

Memilih kerangka pengujian hanya tergantung pada preferensi pribadi. Preferensi saya adalah kerangka kerja yang mudah digunakan, tidak memiliki efek samping, dan keluarannya mudah dibaca dan disalurkan.

Ada beragam kerangka pengujian dalam JavaScript. Dalam contoh kami, kami menggunakan Tape. Tape, menurut saya, tidak hanya memenuhi persyaratan ini, tetapi juga lebih bersih dan sederhana daripada kerangka pengujian lain seperti Mocha atau Jasmin.

Tape didasarkan pada Test Anything Protocol (TAP).

TAP memiliki variasi untuk sebagian besar bahasa pemrograman.

Tape mengambil tes sebagai input, menjalankannya, dan kemudian mengeluarkan hasil sebagai TAP. Hasil TAP kemudian dapat disalurkan ke reporter pengujian atau dapat dikeluarkan ke konsol dalam format mentah. Tape dijalankan dari baris perintah.

Tape memiliki beberapa fitur bagus, seperti mendefinisikan modul yang akan dimuat sebelum menjalankan seluruh rangkaian pengujian, menyediakan pustaka pernyataan kecil dan sederhana, dan menentukan jumlah pernyataan yang harus dipanggil dalam pengujian. Menggunakan modul untuk pramuat dapat menyederhanakan persiapan lingkungan pengujian, dan menghapus kode yang tidak perlu.

Perpustakaan Pabrik

Pustaka pabrik memungkinkan Anda mengganti file perlengkapan statis dengan cara yang jauh lebih fleksibel untuk menghasilkan data untuk pengujian. Pustaka semacam itu memungkinkan Anda untuk menentukan model dan membuat entitas untuk model tersebut tanpa menulis kode yang rumit dan berantakan.

JavaScript memiliki factory_girl untuk ini - perpustakaan yang terinspirasi dari permata dengan nama yang mirip, yang awalnya dikembangkan untuk Ruby on Rails.

 const factory = require('factory-girl').factory; const User = require('../models/user'); factory.define('user', User, { username: 'Bob', number_of_recipes: 50 }); const user = factory.build('user');

Untuk memulai, model baru harus didefinisikan di factory_girl.

Ini ditentukan dengan nama, model dari proyek Anda, dan objek dari mana instance baru dihasilkan.

Sebagai alternatif, alih-alih mendefinisikan objek dari mana instance baru dihasilkan, fungsi dapat disediakan yang akan mengembalikan objek atau janji.

Saat membuat instance model baru, kita dapat:

  • Ganti nilai apa pun dalam instance yang baru dibuat
  • Berikan nilai tambahan ke opsi fungsi build

Mari kita lihat contohnya.

 const factory = require('factory-girl').factory; const User = require('../models/user'); factory.define('user', User, (buildOptions) => { return { name: 'Mike', surname: 'Dow', email: buildOptions.email || '[email protected]' } }); const user1 = factory.build('user'); // {"name": "Mike", "surname": "Dow", "email": "[email protected]"} const user2 = factory.build('user', {name: 'John'}, {email: '[email protected]'}); // {"name": "John", "surname": "Dow", "email": "[email protected]"}

Menghubungkan ke API

Memulai server HTTP lengkap dan membuat permintaan HTTP yang sebenarnya, hanya untuk menghapusnya beberapa detik kemudian – terutama saat melakukan beberapa pengujian – sama sekali tidak efisien dan dapat menyebabkan pengujian integrasi memakan waktu lebih lama dari yang diperlukan.

SuperTest adalah pustaka JavaScript untuk memanggil API tanpa membuat server aktif baru. Ini didasarkan pada SuperAgent, perpustakaan untuk membuat permintaan TCP. Dengan perpustakaan ini, tidak perlu membuat koneksi TCP baru. API hampir secara instan dipanggil.

SuperTest, dengan dukungan untuk janji, adalah supertest-seperti yang dijanjikan. Saat permintaan seperti itu mengembalikan janji, ini memungkinkan Anda menghindari beberapa fungsi panggilan balik bersarang, membuatnya jauh lebih mudah untuk menangani alurnya.

 const express = require('express') const request = require('supertest-as-promised'); const app = express(); request(app).get("/recipes").then(res => assert(....));

SuperTest dibuat untuk kerangka kerja Express.js, tetapi dengan sedikit perubahan dapat digunakan dengan kerangka kerja lain juga.

Utilitas Lainnya

Dalam beberapa kasus, ada kebutuhan untuk meniru beberapa ketergantungan dalam kode kita, menguji logika di sekitar fungsi menggunakan mata-mata, atau menggunakan rintisan di tempat-tempat tertentu. Di sinilah beberapa paket utilitas ini berguna.

SinonJS adalah perpustakaan hebat yang mendukung mata-mata, rintisan, dan tiruan untuk pengujian. Ini juga mendukung fitur pengujian berguna lainnya, seperti waktu tekuk, kotak pasir uji, dan pernyataan yang diperluas, serta server dan permintaan palsu.

Dalam beberapa kasus, ada kebutuhan untuk meniru beberapa ketergantungan dalam kode kita. Referensi ke layanan yang ingin kami tiru digunakan oleh bagian lain dari sistem.

Untuk mengatasi masalah ini, kita dapat menggunakan injeksi ketergantungan atau, jika itu bukan pilihan, kita dapat menggunakan layanan mocking seperti Mockery.

Ejekan membantu mengejek kode yang memiliki ketergantungan eksternal. Untuk menggunakannya dengan benar, Mockery harus dipanggil sebelum memuat tes atau kode.

 const mockery = require('mockery'); mockery.enable({ warnOnReplace: false, warnOnUnregistered: false }); const mockingStripe = require('lib/services/internal/stripe'); mockery.registerMock('lib/services/internal/stripe', mockingStripe);

Dengan referensi baru ini (dalam contoh ini, mockingStripe ), lebih mudah untuk mengolok-olok layanan nanti dalam pengujian kami.

 const stubStripeTransfer = sinon.stub(mockingStripe, 'transferAmount'); stubStripeTransfer.returns(Promise.resolve(null));

Dengan bantuan perpustakaan Sinon, mudah untuk diejek. Satu-satunya masalah di sini adalah bahwa rintisan ini akan menyebar ke tes lain. Untuk sandbox itu, sandbox sinon dapat digunakan. Dengan itu, pengujian selanjutnya dapat mengembalikan sistem ke kondisi awal.

 const sandbox = require('sinon').sandbox.create(); const stubStripeTransfer = sandbox.sinon.stub(mockingStripe, 'transferAmount'); stubStripeTransfer.returns(Promise.resolve(null)); // after the test, or better when starting a new test sandbox.restore();

Ada kebutuhan untuk komponen lain untuk fungsi seperti:

  • Mengosongkan database (dapat dilakukan dengan satu kueri pre-build hierarki)
  • Mengaturnya ke status kerja (sequelize-fixtures)
  • Mengejek permintaan TCP ke layanan pihak ketiga (nock)
  • Menggunakan pernyataan yang lebih kaya (chai)
  • Tanggapan tersimpan dari pihak ketiga (mudah diperbaiki)

Tes yang tidak begitu sederhana

Abstraksi dan ekstensibilitas adalah elemen kunci untuk membangun rangkaian uji integrasi yang efektif. Segala sesuatu yang menghilangkan fokus dari inti pengujian (persiapan data, tindakan, dan pernyataannya) harus dikelompokkan dan diabstraksikan ke dalam fungsi utilitas.

Meskipun tidak ada jalur yang benar atau salah di sini, karena semuanya bergantung pada proyek dan kebutuhannya, beberapa kualitas utama masih umum untuk setiap rangkaian uji integrasi yang baik.

Kode berikut menunjukkan cara menguji API yang membuat resep dan mengirim email sebagai efek samping.

Ini mematikan penyedia email eksternal sehingga Anda dapat menguji apakah email akan dikirim tanpa benar-benar mengirimnya. Pengujian juga memverifikasi apakah API merespons dengan kode status yang sesuai.

 const co = require('co'); const factory = require('factory'); const superTest = require('../utils/super_test'); const basicEnv = require('../utils/basic_test_enivornment'); const path = '/v1/admin/recipes'; basicEnv.test(`API POST ${path}`, co.wrap(function* (t, assert, sandbox) { const chef = yield factory.create('chef'); const body = { chef_id: chef.id, recipe_name: 'cake', Ingredients: ['carrot', 'chocolate', 'biscuit'] }; const stub = sandbox.stub(mockery.emailProvider, 'sendNewEmail').returnsPromise(null); const serverResponse = yield superTest.get(path, body); assert.spies(stub).called(1); assert.statusCode(serverResponse, 201); }));

Tes di atas dapat diulang karena dimulai dengan lingkungan yang bersih setiap saat.

Ini memiliki proses pengaturan yang sederhana, di mana segala sesuatu yang berhubungan dengan pengaturan dikonsolidasikan di dalam fungsi basicEnv.test .

Ini hanya menguji satu tindakan - satu API. Dan itu dengan jelas menyatakan harapan tes melalui pernyataan tegas sederhana. Juga, tes tidak melibatkan kode pihak ketiga dengan mematikan/mengejek.

Mulai Menulis Tes Integrasi

Saat mendorong kode baru ke produksi, pengembang (dan semua peserta proyek lainnya) ingin memastikan bahwa fitur baru akan berfungsi dan yang lama tidak akan rusak.

Ini sangat sulit dicapai tanpa pengujian, dan jika dilakukan dengan buruk dapat menyebabkan frustrasi, kelelahan proyek, dan akhirnya kegagalan proyek.

Tes integrasi, dikombinasikan dengan tes unit, adalah garis pertahanan pertama.

Menggunakan hanya satu dari keduanya tidak cukup dan akan meninggalkan banyak ruang untuk kesalahan yang tidak terungkap. Selalu memanfaatkan keduanya akan membuat komitmen baru menjadi kuat, dan memberikan kepercayaan diri dan menginspirasi kepercayaan pada semua peserta proyek.