Pengujian otomatis akan digunakan dalam tugas selanjutnya, dan juga banyak digunakan dalam proyek nyata.
Saat kita menulis suatu fungsi, kita biasanya dapat membayangkan apa yang harus dilakukannya: parameter mana yang memberikan hasil apa.
Selama pengembangan, kita dapat memeriksa fungsinya dengan menjalankannya dan membandingkan hasilnya dengan yang diharapkan. Misalnya, kita bisa melakukannya di konsol.
Jika ada yang salah – lalu kita perbaiki kodenya, jalankan kembali, periksa hasilnya – dan seterusnya hingga berhasil.
Namun “pengulangan” manual seperti itu tidaklah sempurna.
Saat menguji kode dengan menjalankan ulang secara manual, sangat mudah untuk melewatkan sesuatu.
Misalnya, kita membuat fungsi f
. Menulis beberapa kode, menguji: f(1)
berfungsi, tetapi f(2)
tidak berfungsi. Kami memperbaiki kodenya dan sekarang f(2)
berfungsi. Terlihat lengkap? Tapi kami lupa menguji ulang f(1)
. Hal ini dapat menyebabkan kesalahan.
Itu sangat khas. Saat kami mengembangkan sesuatu, kami mempertimbangkan banyak kemungkinan kasus penggunaan. Namun sulit mengharapkan seorang programmer memeriksa semuanya secara manual setelah setiap perubahan. Jadi menjadi mudah untuk memperbaiki satu hal dan merusak hal lainnya.
Pengujian otomatis berarti pengujian ditulis secara terpisah, selain kode. Mereka menjalankan fungsi kami dengan berbagai cara dan membandingkan hasil dengan yang diharapkan.
Mari kita mulai dengan teknik bernama Behavior Driven Development atau, singkatnya, BDD.
BDD adalah tiga hal dalam satu: tes DAN dokumentasi DAN contoh.
Untuk memahami BDD, kita akan mengkaji kasus praktis pembangunan.
Katakanlah kita ingin membuat fungsi pow(x, n)
yang menaikkan x
menjadi bilangan bulat pangkat n
. Kami berasumsi bahwa n≥0
.
Tugas tersebut hanyalah sebuah contoh: ada operator **
di JavaScript yang dapat melakukan itu, namun di sini kami berkonsentrasi pada alur pengembangan yang juga dapat diterapkan pada tugas yang lebih kompleks.
Sebelum membuat kode pow
, kita dapat membayangkan fungsi apa yang harus dilakukan dan mendeskripsikannya.
Deskripsi seperti ini disebut spesifikasi atau, singkatnya, spesifikasi, dan berisi deskripsi kasus penggunaan beserta pengujiannya, seperti ini:
deskripsikan("kekuatan", fungsi() { it("naik pangkat ke-n", function() { menegaskan.sama(pow(2, 3), 8); }); });
Spesifikasi memiliki tiga blok penyusun utama yang dapat Anda lihat di atas:
describe("title", function() { ... })
Fungsi apa yang kami jelaskan? Dalam kasus kami, kami menjelaskan fungsinya pow
. Digunakan untuk mengelompokkan “pekerja” – yang it
.
it("use case description", function() { ... })
Dalam it
kami menjelaskan kasus penggunaan tertentu dengan cara yang dapat dibaca manusia , dan argumen kedua adalah fungsi yang mengujinya.
assert.equal(value1, value2)
Kode di dalam blok it
, jika implementasinya benar, harus dieksekusi tanpa kesalahan.
Fungsi assert.*
digunakan untuk memeriksa apakah pow
berfungsi seperti yang diharapkan. Di sini kita menggunakan salah satu dari mereka assert.equal
, ini membandingkan argumen dan menghasilkan kesalahan jika argumen tersebut tidak sama. Di sini ia memeriksa bahwa hasil pow(2, 3)
sama dengan 8
. Ada jenis perbandingan dan pemeriksaan lainnya, yang akan kami tambahkan nanti.
Spesifikasi dapat dijalankan, dan akan menjalankan pengujian yang ditentukan di blok it
. Kita akan melihatnya nanti.
Alur perkembangannya biasanya terlihat seperti ini:
Spesifikasi awal telah ditulis, dengan pengujian untuk fungsionalitas paling dasar.
Implementasi awal dibuat.
Untuk memeriksa apakah ini berfungsi, kami menjalankan kerangka pengujian Mocha (detail lebih lanjut segera) yang menjalankan spesifikasi. Meskipun fungsinya tidak lengkap, kesalahan tetap ditampilkan. Kami melakukan koreksi hingga semuanya berfungsi.
Sekarang kami memiliki implementasi awal yang berfungsi dengan pengujian.
Kami menambahkan lebih banyak kasus penggunaan ke spesifikasi, mungkin belum didukung oleh implementasinya. Tes mulai gagal.
Lanjut ke 3, perbarui implementasinya hingga pengujian tidak memberikan kesalahan.
Ulangi langkah 3-6 hingga fungsinya siap.
Jadi, perkembangannya bersifat iteratif . Kita menulis spesifikasinya, mengimplementasikannya, memastikan tesnya lulus, kemudian menulis lebih banyak tes, memastikannya berfungsi, dll. Pada akhirnya kita memiliki implementasi dan tes yang berfungsi.
Mari kita lihat alur perkembangan ini dalam kasus praktis kita.
Langkah pertama sudah selesai: kita memiliki spesifikasi awal untuk pow
. Sekarang, sebelum melakukan implementasi, mari gunakan beberapa pustaka JavaScript untuk menjalankan pengujian, hanya untuk melihat apakah pustaka tersebut berfungsi (semuanya akan gagal).
Di sini, di tutorial kita akan menggunakan pustaka JavaScript berikut untuk pengujian:
Mocha – kerangka inti: menyediakan fungsi pengujian umum termasuk describe
it
fungsi utama yang menjalankan pengujian.
Chai – perpustakaan dengan banyak pernyataan. Hal ini memungkinkan untuk menggunakan banyak pernyataan yang berbeda, untuk saat ini kita hanya perlu assert.equal
.
Sinon – perpustakaan untuk memata-matai fungsi, meniru fungsi bawaan dan banyak lagi, kita akan membutuhkannya nanti.
Pustaka ini cocok untuk pengujian di dalam browser dan di sisi server. Di sini kita akan mempertimbangkan varian browser.
Halaman HTML lengkap dengan kerangka kerja dan spesifikasi pow
berikut:
<!DOCTYPEhtml> <html> <kepala> <!-- tambahkan mocha css, untuk menampilkan hasil --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css"> <!-- tambahkan kode kerangka moka --> <skrip src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></skrip> <skrip> mocha.setup('bdd'); // pengaturan minimal </skrip> <!-- tambahkan chai --> <skrip src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></skrip> <skrip> // chai punya banyak hal, mari kita buat tegaskan secara global biarkan menegaskan = chai.menegaskan; </skrip> </kepala> <tubuh> <skrip> fungsi kekuatan(x, n) { /* kode fungsi harus ditulis, kosongkan sekarang */ } </skrip> <!-- skrip dengan tes (jelaskan, itu...) --> <skrip src="test.js"></skrip> <!-- elemen dengan id="mocha" akan berisi hasil pengujian --> <div id="moka"></div> <!-- menjalankan tes! --> <skrip> mocha.run(); </skrip> </tubuh> </html>
Halaman ini dapat dibagi menjadi lima bagian:
<head>
– menambahkan perpustakaan dan gaya pihak ketiga untuk pengujian.
<script>
dengan fungsi yang akan diuji, dalam kasus kami – dengan kode untuk pow
.
Pengujian – dalam kasus kami, skrip eksternal test.js
yang memiliki describe("pow", ...)
dari atas.
Elemen HTML <div id="mocha">
akan digunakan oleh Mocha untuk menampilkan hasil.
Pengujian dimulai dengan perintah mocha.run()
.
Hasilnya:
Sampai sekarang, tes gagal, ada kesalahan. Itu logis: kita memiliki kode fungsi kosong di pow
, jadi pow(2,3)
mengembalikan undefined
alih-alih 8
.
Untuk kedepannya, perhatikan bahwa ada lebih banyak test-runner tingkat tinggi, seperti karma dan lainnya, yang memudahkan untuk menjalankan banyak pengujian berbeda secara otomatis.
Mari kita buat implementasi sederhana pow
, agar pengujian dapat lulus:
fungsi kekuatan(x, n) { kembali 8; // :) kita curang! }
Wow, sekarang berhasil!
Apa yang kami lakukan sudah pasti curang. Fungsi ini tidak berfungsi: upaya menghitung pow(3,4)
akan memberikan hasil yang salah, tetapi pengujian berhasil.
…Tetapi situasinya cukup umum, hal ini terjadi dalam praktiknya. Tes berhasil, tetapi fungsinya salah. Spesifikasi kami tidak sempurna. Kita perlu menambahkan lebih banyak kasus penggunaan ke dalamnya.
Mari tambahkan satu tes lagi untuk memeriksa pow(3, 4) = 81
itu.
Kita dapat memilih salah satu dari dua cara untuk mengatur tes di sini:
Varian pertama – tambahkan satu assert
lagi ke dalam it
yang sama :
deskripsikan("kekuatan", fungsi() { it("naik pangkat ke-n", function() { menegaskan.sama(pow(2, 3), 8); menegaskan.sama(pow(3, 4), 81); }); });
Yang kedua – lakukan dua tes:
deskripsikan("kekuatan", fungsi() { it("2 dipangkatkan 3 adalah 8", function() { menegaskan.sama(pow(2, 3), 8); }); it("3 dipangkatkan 4 adalah 81", function() { menegaskan.sama(pow(3, 4), 81); }); });
Perbedaan utamanya adalah ketika assert
memicu kesalahan, blok it
segera dihentikan. Jadi, pada varian pertama jika assert
pertama gagal, maka kita tidak akan pernah melihat hasil assert
kedua.
Membuat pengujian terpisah berguna untuk mendapatkan lebih banyak informasi tentang apa yang terjadi, sehingga varian kedua lebih baik.
Selain itu, ada satu aturan lagi yang sebaiknya diikuti.
Satu tes memeriksa satu hal.
Jika kita melihat tes dan melihat dua pemeriksaan independen di dalamnya, lebih baik membaginya menjadi dua yang lebih sederhana.
Jadi mari kita lanjutkan dengan varian kedua.
Hasilnya:
Seperti yang kita duga, tes kedua gagal. Tentu, fungsi kita selalu mengembalikan 8
, sedangkan assert
mengharapkan 81
.
Mari kita tulis sesuatu yang lebih nyata agar tes dapat lulus:
fungsi kekuatan(x, n) { misalkan hasilnya = 1; untuk (misalkan i = 0; i < n; i++) { hasil *= x; } hasil pengembalian; }
Untuk memastikan bahwa fungsinya berfungsi dengan baik, mari kita uji untuk mendapatkan nilai lebih banyak. Daripada it
bloknya secara manual, kita dapat membuatnya di for
:
deskripsikan("kekuatan", fungsi() { fungsi makeTest(x) { misalkan diharapkan = x * x * x; it(`${x} pangkat 3 adalah ${expected}`, function() { menegaskan.sama(pow(x, 3), diharapkan); }); } untuk (misalkan x = 1; x <= 5; x++) { makeTest(x); } });
Hasilnya:
Kami akan menambahkan lebih banyak tes. Namun sebelum itu perlu diketahui bahwa fungsi pembantu makeTest
dan for
harus dikelompokkan bersama. Kita tidak memerlukan makeTest
dalam pengujian lain, ini hanya diperlukan for
: tugas umum mereka adalah memeriksa bagaimana pow
meningkat menjadi pangkat tertentu.
Pengelompokan dilakukan dengan describe
bersarang :
deskripsikan("kekuatan", fungsi() { deskripsikan("menaikkan x pangkat 3", function() { fungsi makeTest(x) { misalkan diharapkan = x * x * x; it(`${x} pangkat 3 adalah ${expected}`, function() { menegaskan.sama(pow(x, 3), diharapkan); }); } untuk (misalkan x = 1; x <= 5; x++) { makeTest(x); } }); // ... tes lebih lanjut untuk diikuti di sini, baik yang dijelaskan maupun dapat ditambahkan });
describe
bersarang mendefinisikan “subgrup” pengujian baru. Pada output kita dapat melihat lekukan berjudul:
Di masa depan kita dapat menambahkan lebih it
dan describe
di tingkat atas dengan fungsi pembantunya sendiri, mereka tidak akan melihat makeTest
.
before/after
dan beforeEach/afterEach
Kita dapat mengatur fungsi before/after
yang dijalankan sebelum/sesudah menjalankan pengujian, dan juga fungsi beforeEach/afterEach
yang dijalankan sebelum/sesudah setiap it
.
Misalnya:
deskripsikan("uji", fungsi() { before(() => alert("Pengujian dimulai – sebelum semua pengujian")); after(() => alert("Pengujian selesai – setelah semua pengujian")); beforeEach(() => alert("Sebelum ujian – masuk ujian")); afterEach(() => alert("Setelah ujian – keluar dari ujian")); itu('ujian 1', () => peringatan(1)); itu('ujian 2', () => peringatan(2)); });
Urutan yang berjalan adalah:
Pengujian dimulai – sebelum semua pengujian (sebelum) Sebelum ujian – masuk ujian (beforeEach) 1 Setelah tes – keluar dari tes (afterEach) Sebelum ujian – masuk ujian (beforeEach) 2 Setelah tes – keluar dari tes (afterEach) Pengujian selesai – setelah semua pengujian (setelah)
Buka contoh di kotak pasir.
Biasanya, beforeEach/afterEach
dan before/after
digunakan untuk melakukan inisialisasi, penghitung nol, atau melakukan hal lain di antara pengujian (atau grup pengujian).
Fungsi dasar pow
sudah lengkap. Iterasi pertama pengembangan telah selesai. Ketika kita selesai merayakan dan minum sampanye – mari kita lanjutkan dan tingkatkan.
Seperti yang disebutkan, fungsi pow(x, n)
dimaksudkan untuk bekerja dengan nilai bilangan bulat positif n
.
Untuk menunjukkan kesalahan matematika, fungsi JavaScript biasanya mengembalikan NaN
. Mari kita lakukan hal yang sama untuk nilai n
yang tidak valid.
Pertama-tama mari tambahkan perilaku ke spec(!):
deskripsikan("kekuatan", fungsi() { // ... it("untuk n negatif hasilnya NaN", function() { menegaskan.isNaN(pow(2, -1)); }); it("untuk n yang bukan bilangan bulat hasilnya adalah NaN", function() { menegaskan.isNaN(pow(2, 1.5)); }); });
Hasil dengan tes baru:
Pengujian yang baru ditambahkan gagal karena penerapan kami tidak mendukung pengujian tersebut. Begitulah cara BDD dilakukan: pertama kita menulis tes yang gagal, dan kemudian membuat implementasi untuk tes tersebut.
Pernyataan lainnya
Harap perhatikan pernyataan assert.isNaN
: ia memeriksa NaN
.
Ada juga pernyataan lain dalam Chai, misalnya:
assert.equal(value1, value2)
– memeriksa kesetaraan value1 == value2
.
assert.strictEqual(value1, value2)
– memeriksa kesetaraan yang ketat value1 === value2
.
assert.notEqual
, assert.notStrictEqual
– pemeriksaan kebalikan dari yang di atas.
assert.isTrue(value)
– memeriksa apakah value === true
assert.isFalse(value)
– memeriksa apakah value === false
…daftar lengkapnya ada di dokumen
Jadi kita harus menambahkan beberapa baris ke pow
:
fungsi kekuatan(x, n) { jika (n < 0) kembalikan NaN; if (Matematika.bulat(n) != n) kembalikan NaN; misalkan hasilnya = 1; untuk (misalkan i = 0; i < n; i++) { hasil *= x; } hasil pengembalian; }
Sekarang berfungsi, semua tes lulus:
Buka contoh terakhir selengkapnya di kotak pasir.
Di BDD, spesifikasi diutamakan, diikuti implementasi. Pada akhirnya kami memiliki spesifikasi dan kodenya.
Spesifikasi dapat digunakan dalam tiga cara:
Sebagai Tes – mereka menjamin bahwa kode berfungsi dengan benar.
As Docs – judul describe
dan it
apa fungsinya.
Sebagai Contoh – pengujian sebenarnya merupakan contoh kerja yang menunjukkan bagaimana suatu fungsi dapat digunakan.
Dengan spesifikasi tersebut, kami dapat dengan aman meningkatkan, mengubah, bahkan menulis ulang fungsi tersebut dari awal dan memastikannya masih berfungsi dengan baik.
Hal ini sangat penting dalam proyek besar ketika suatu fungsi digunakan di banyak tempat. Saat kami mengubah fungsi tersebut, tidak ada cara untuk memeriksa secara manual apakah setiap tempat yang menggunakannya masih berfungsi dengan baik.
Tanpa tes, orang punya dua cara:
Untuk melakukan perubahan, apa pun yang terjadi. Dan kemudian pengguna kami menemui bug, karena kami mungkin gagal memeriksa sesuatu secara manual.
Atau, jika hukuman atas kesalahan sangat berat, karena tidak ada pengujian, orang menjadi takut untuk mengubah fungsi tersebut, dan kemudian kode tersebut menjadi usang, tidak ada yang mau menggunakannya. Tidak bagus untuk pembangunan.
Pengujian otomatis membantu menghindari masalah ini!
Jika proyek tersebut dilengkapi dengan pengujian, maka tidak ada masalah seperti itu. Setelah perubahan apa pun, kami dapat menjalankan pengujian dan melihat banyak pemeriksaan yang dilakukan dalam hitungan detik.
Selain itu, kode yang teruji memiliki arsitektur yang lebih baik.
Tentu saja, hal ini karena kode yang diuji secara otomatis lebih mudah untuk dimodifikasi dan ditingkatkan. Tapi ada juga alasan lain.
Untuk menulis tes, kode harus diatur sedemikian rupa sehingga setiap fungsi memiliki tugas yang dijelaskan dengan jelas, input dan output yang terdefinisi dengan baik. Itu berarti arsitektur yang bagus sejak awal.
Dalam kehidupan nyata, hal itu terkadang tidak semudah itu. Terkadang sulit untuk menulis spesifikasi sebelum kode sebenarnya, karena belum jelas bagaimana seharusnya perilakunya. Namun secara umum tes menulis membuat pengembangan lebih cepat dan stabil.
Nanti di tutorial Anda akan menemui banyak tugas dengan tes yang sudah ada. Jadi, Anda akan melihat contoh yang lebih praktis.
Tes menulis membutuhkan pengetahuan JavaScript yang baik. Tapi kami baru mulai mempelajarinya. Jadi, untuk menyelesaikan semuanya, saat ini Anda tidak diharuskan untuk menulis tes, tetapi Anda seharusnya sudah bisa membacanya meskipun tes tersebut sedikit lebih rumit daripada di bab ini.
pentingnya: 5
Apa yang salah pada tes pow
di bawah ini?
it("Menaikkan x pangkat n", function() { misalkan x = 5; misalkan hasil = x; menegaskan.sama(pow(x, 1), hasil); hasil *= x; menegaskan.sama(pow(x, 2), hasil); hasil *= x; menegaskan.sama(pow(x, 3), hasil); });
PS Secara sintaksis tesnya benar dan lulus.
Tes ini menunjukkan salah satu godaan yang ditemui pengembang saat menulis tes.
Apa yang kita miliki di sini sebenarnya adalah 3 tes, tetapi ditata sebagai satu fungsi dengan 3 pernyataan.
Kadang-kadang lebih mudah untuk menulis dengan cara ini, tetapi jika terjadi kesalahan, apa yang salah menjadi kurang jelas.
Jika kesalahan terjadi di tengah alur eksekusi yang kompleks, maka kita harus mencari tahu datanya pada saat itu. Kami sebenarnya harus men-debug pengujian tersebut .
Akan lebih baik jika pengujian dipecah menjadi it
blok dengan masukan dan keluaran yang ditulis dengan jelas.
Seperti ini:
deskripsikan("Menaikkan x ke pangkat n", function() { it("5 pangkat 1 sama dengan 5", function() { menegaskan.sama(pow(5, 1), 5); }); it("5 pangkat 2 sama dengan 25", function() { menegaskan.sama(pow(5, 2), 25); }); it("5 pangkat 3 sama dengan 125", function() { menegaskan.sama(pow(5, 3), 125); }); });
Kami mengganti single it
dengan describe
dan sekelompok blok it
. Sekarang jika ada yang gagal kita akan melihat dengan jelas apa datanya.
Kita juga dapat mengisolasi satu pengujian dan menjalankannya dalam mode mandiri dengan menulis it.only
sebagai ganti it
:
deskripsikan("Menaikkan x ke pangkat n", function() { it("5 pangkat 1 sama dengan 5", function() { menegaskan.sama(pow(5, 1), 5); }); // Mocha hanya akan menjalankan blok ini it.only("5 pangkat 2 sama dengan 25", function() { menegaskan.sama(pow(5, 2), 25); }); it("5 pangkat 3 sama dengan 125", function() { menegaskan.sama(pow(5, 3), 125); }); });