Suatu hari, saya akan membuat laporan internal di mana saya akan memberi tahu pengembang kami tentang kesalahan tidak menyenangkan yang dapat terjadi ketika menulis unit test. Kesalahan yang paling tidak menyenangkan dari sudut pandang saya adalah ketika tes lulus, tetapi pada saat yang sama mereka melakukannya dengan salah sehingga lebih baik tidak lulus. Dan saya memutuskan untuk membagikan contoh kesalahan tersebut kepada semua orang. Tentunya sesuatu yang lain untuk diceritakan dari daerah ini. Contoh ditulis untuk Node.JS dan Mocha, tetapi secara umum kesalahan ini berlaku untuk ekosistem lainnya.
Untuk membuatnya lebih menarik, beberapa dari mereka dibingkai dalam bentuk kode masalah dan spoiler, yang membuka, Anda akan melihat apa masalahnya. Jadi saya sarankan Anda pertama kali melihat kode, menemukan kesalahan di dalamnya, dan kemudian buka spoiler. Tidak ada solusi untuk masalah yang akan ditunjukkan - Saya mengusulkan untuk memikirkannya sendiri. Hanya karena aku malas. Urutan daftar itu tidak masuk akal - itu hanya urutan di mana saya mengingat semua jenis masalah nyata yang membuat kami menangis. Tentunya banyak hal akan tampak jelas bagi Anda - tetapi bahkan pengembang yang berpengalaman dapat secara tidak sengaja menulis kode seperti itu.
Jadi ayo pergi.
0. Kurang tes
Anehnya, banyak yang masih percaya bahwa tes menulis memperlambat kecepatan pengembangan. Tentu saja, jelas bahwa lebih banyak waktu harus dihabiskan untuk menulis tes dan menulis kode yang dapat diuji. Tetapi setelah debugging dan mundur setelah itu Anda harus menghabiskan waktu lebih banyak kali ...
1. Kurangnya menjalankan tes
Jika Anda memiliki tes yang tidak Anda jalankan, atau jalankan dari waktu ke waktu, maka ini seperti tidak adanya tes. Dan bahkan lebih buruk lagi - Anda memiliki kode tes yang ketinggalan jaman dan rasa aman yang salah. Tes setidaknya harus berjalan dalam proses CI saat mendorong kode ke cabang. Dan lebih baik - secara lokal sebelum menekan. Maka pengembang tidak harus kembali ke bangunan dalam beberapa hari, yang, ternyata, tidak lulus.
2. Kurangnya cakupan
Jika Anda masih tidak tahu apa cakupan dalam tes, maka saatnya untuk pergi dan membaca sekarang. Setidaknya
Wikipedia . Kalau tidak, ada kemungkinan bagus bahwa tes Anda akan memeriksa 10% dari kode yang Anda pikir akan diperiksa. Cepat atau lambat Anda pasti akan menginjaknya. Tentu saja, bahkan cakupan 100% dari kode tidak menjamin kebenaran lengkapnya dengan cara apa pun - tetapi ini jauh lebih baik daripada kurangnya cakupan karena akan menunjukkan Anda lebih banyak kesalahan potensial. Tidak heran versi terbaru Node.JS bahkan memiliki alat bawaan untuk membacanya. Secara umum, topik liputannya dalam dan sangat holistik, tetapi saya tidak akan terlalu mendalam ke dalamnya - saya ingin mengatakan sedikit tentang banyak hal.
3.
const {assert} = require('chai'); const Promise = require('bluebird'); const sinon = require('sinon'); class MightyLibrary { static someLongFunction() { return Promise.resolve(1);
Ada apa di siniWaktu habis dalam tes unit.
Di sini mereka ingin memeriksa bahwa pengaturan waktu tunggu untuk operasi yang lama benar-benar berfungsi. Secara umum, ini sedikit masuk akal - Anda tidak harus memeriksa perpustakaan standar - tetapi juga kode ini mengarah ke masalah lain - untuk meningkatkan waktu pelaksanaan tes selama satu detik. Tampaknya ini tidak terlalu banyak ... Tapi gandakan yang kedua ini dengan jumlah tes yang sama, dengan jumlah pengembang, dengan jumlah peluncuran per hari ... Dan Anda akan mengerti bahwa karena timeout seperti itu Anda dapat kehilangan banyak jam kerja setiap minggu, jika tidak setiap hari.
4.
const fs = require('fs'); const testData = JSON.parse(fs.readFileSync('./testData.json', 'utf8')); describe('some block', ()=>{ it('should do something', ()=>{ someTest(testData); }) })
Ada apa di siniMemuat data uji di luar blok uji.
Sekilas, tampaknya tidak masalah di mana harus membaca data uji - dalam uraian, blokir atau dalam modul itu sendiri. Yang kedua juga. Tetapi bayangkan Anda memiliki ratusan tes, dan banyak dari mereka menggunakan data berat. Jika Anda memuatnya di luar tes, ini akan mengarah pada kenyataan bahwa semua data uji akan tetap dalam memori sampai akhir pelaksanaan tes, dan seiring waktu, peluncuran akan mengkonsumsi lebih banyak dan lebih banyak RAM - sampai ternyata tes tidak lagi berjalan sama sekali mesin kerja standar.
5.
const {assert} = require('chai'); const sinon = require('sinon'); class Dog {
Ada apa di siniKode sebenarnya diganti oleh bertopik.
Tentunya Anda langsung melihat kesalahan konyol ini. Dalam kode asli, ini, tentu saja, tidak begitu jelas - tetapi saya melihat kode yang sangat tergantung dengan bertopik sehingga saya tidak menguji apa pun sama sekali.
6.
const sinon = require('sinon'); const {assert} = require('chai'); class Widget { fetch() {} loadData() { this.fetch(); } } if (!sinon.sandbox || !sinon.sandbox.stub) { sinon.sandbox = sinon.createSandbox(); } describe('My widget', () => { it('is awesome', () => { const widget = new Widget(); widget.fetch = sinon.sandbox.stub().returns({ one: 1, two: 2 }); widget.loadData(); assert.isTrue(widget.fetch.called); }); });
Ada apa di siniKetergantungan antara tes.
Sepintas jelas mereka lupa menulis di sini
afterEach(() => { sinon.sandbox.restore(); });
Tapi masalahnya bukan hanya ini, tetapi kotak pasir yang sama digunakan untuk semua tes. Dan sangat mudah untuk membingungkan lingkungan pelaksanaan tes sedemikian rupa sehingga mereka mulai saling bergantung. Setelah itu, tes akan mulai dilakukan hanya dalam urutan tertentu, dan secara umum tidak jelas apa yang harus diuji.
Untungnya, di beberapa titik sinon.sandbox dinyatakan usang dan terputus, sehingga Anda hanya dapat menemukan masalah seperti itu pada proyek lawas - tetapi ada begitu banyak cara lain untuk membingungkan lingkungan pelaksanaan pengujian sedemikian rupa sehingga menyakitkan untuk diselidiki kemudian. kode mana yang bersalah karena perilaku yang salah. Ngomong-ngomong, baru-baru ini ada posting di hub tentang beberapa jenis templat seperti "Pabrik Es" - ini bukan obat mujarab, tapi kadang-kadang membantu dalam kasus seperti itu.
7. Data uji besar dalam file uji
Sangat sering saya melihat seberapa besar file JSON, dan bahkan XML, langsung berada dalam pengujian. Saya pikir jelas mengapa ini tidak layak dilakukan - menjadi menyakitkan untuk menonton, mengedit, dan setiap IDE tidak akan berterima kasih untuk itu. Jika Anda memiliki data uji yang besar, keluarkan dari file uji.
8.
const {assert} = require('chai'); const crypto = require('crypto'); describe('extraTests', ()=>{ it('should generate unique bytes', ()=>{ const arr = []; for (let i = 0; i < 1000; i++) { const value = crypto.randomBytes(256); arr.push(value); } const unique = arr.filter((el, index)=>arr.indexOf(el) === index); assert.equal(arr.length, unique.length, 'Data is not random enough!'); }); });
Ada apa di siniTes ekstra.
Dalam hal ini, pengembang sangat khawatir bahwa pengidentifikasi uniknya akan unik, jadi ia menulis cek untuk ini. Secara umum, keinginan yang dapat dipahami - tetapi lebih baik membaca dokumentasi atau menjalankan tes seperti itu beberapa kali tanpa menambahkannya ke proyek. Menjalankannya di setiap bangunan tidak masuk akal.
Nah, ikatan untuk nilai acak dalam tes itu sendiri merupakan cara yang bagus untuk menembak diri sendiri di kaki dengan membuat tes tidak stabil dari awal.
9. Kekurangan mok
Jauh lebih mudah untuk menjalankan tes dengan database langsung dan layanan 100 persen, dan untuk menjalankan tes pada mereka.
Tetapi cepat atau lambat itu akan kembali membuahkan hasil - tes penghilangan data akan dilakukan pada basis produk, akan mulai jatuh karena layanan mitra yang rusak, atau CI Anda tidak akan memiliki basis untuk menjalankannya. Secara umum, item tersebut cukup holistik, tetapi sebagai aturan - jika Anda dapat meniru layanan eksternal, maka lebih baik melakukannya.
11.
const {assert} = require('chai'); class CustomError extends Error { } function mytestFunction() { throw new CustomError('important message'); } describe('badCompare', ()=>{ it('should throw only my custom errors', ()=>{ let errorHappened = false; try { mytestFunction(); } catch (err) { errorHappened = true; assert.isTrue(err instanceof CustomError); } assert.isTrue(errorHappened); }); });
Ada apa di siniKesalahan debugging yang rumit.
Semuanya tidak buruk, tetapi ada satu masalah - jika tes tiba-tiba crash, Anda akan melihat kesalahan formulir
1) badCompare
should throw only my custom errors:
AssertionError: expected false to be true
+ expected - actual
-false
+true
at Context.it (test/011_badCompare/test.js:23:14)
Selanjutnya, untuk memahami kesalahan macam apa yang sebenarnya terjadi - Anda harus menulis ulang tes. Jadi dalam kasus kesalahan yang tidak terduga - coba tes berbicara tentang hal itu, dan bukan hanya fakta bahwa itu terjadi.
12.
const {assert} = require('chai'); function someVeryBigFunc1() { return 1;
Ada apa di siniSemua yang ada di blok sebelumnya.
Tampaknya pendekatan yang keren adalah melakukan semua operasi di blok `before`, dan dengan demikian hanya meninggalkan pemeriksaan di dalam` it`.
Tidak juga.
Karena dalam hal ini ada kekacauan di mana Anda tidak dapat memahami waktu pelaksanaan tes yang sebenarnya, atau alasan untuk jatuh, atau apa yang terkait dengan satu tes, dan apa yang lain.
Jadi semua pekerjaan tes (kecuali untuk inisialisasi standar) harus dilakukan di dalam tes itu sendiri.
13.
const {assert} = require('chai'); const moment = require('moment'); function someDateBasedFunction(date) { if (moment().isAfter(date)) { return 0; } return 1; } describe('useFutureDate', ()=>{ it('should return 0 for passed date', ()=>{ const pastDate = moment('2010-01-01'); assert.equal(someDateBasedFunction(pastDate), 0); }); it('should return 1 for future date', ()=>{ const itWillAlwaysBeInFuture = moment('2030-01-01'); assert.equal(someDateBasedFunction(itWillAlwaysBeInFuture), 1); }); });
Ada apa di siniIkat tanggal.
Itu juga akan tampak seperti kesalahan nyata - tetapi juga muncul secara berkala di antara pengembang yang lelah yang sudah percaya bahwa besok tidak akan pernah datang. Dan bangunan yang berjalan baik kemarin tiba-tiba jatuh hari ini.
Ingatlah bahwa tanggal apa pun akan datang cepat atau lambat - jadi gunakan emulasi waktu dengan hal-hal seperti `sinon.fakeTimers`, atau setidaknya atur tanggal jauh seperti 2050 - biarkan keturunan Anda terluka ...
14.
describe('dynamicRequires', ()=>{ it('should return english locale', ()=>{
Ada apa di siniMemuat modul secara dinamis.
Jika Anda memiliki Eslint, maka Anda mungkin telah melarang dependensi dinamis. Atau tidak.
Seringkali saya melihat bahwa pengembang mencoba memuat pustaka atau berbagai modul langsung di dalam tes. Namun, mereka umumnya tahu bagaimana `mengharuskan` bekerja - tetapi mereka lebih suka ilusi bahwa mereka seharusnya diberikan modul bersih yang tidak ada yang bingung sejauh ini.
Asumsi ini berbahaya karena memuat modul tambahan selama pengujian lebih lambat, dan sekali lagi mengarah pada perilaku yang lebih tidak jelas.
15.
function someComplexFunc() {
Ada apa di siniNama uji yang tidak dapat dipahami.
Anda pasti bosan dengan hal-hal yang jelas, bukan? Tetapi Anda masih harus mengatakannya karena banyak yang tidak repot-repot menuliskan nama yang dapat dimengerti untuk tes - dan sebagai hasilnya, adalah mungkin untuk memahami apa yang dilakukan tes tertentu hanya setelah banyak penelitian.
16.
const {assert} = require('chai'); const Promise = require('bluebird'); function someTomeoutingFunction() { throw new Promise.TimeoutError(); } describe('no Error check', ()=>{ it('should throw error', async ()=>{ let timedOut = false; try { await someTomeoutingFunction(); } catch (err) { timedOut = true; } assert.equal(timedOut, true); }); });
Ada apa di siniKurangnya verifikasi kesalahan yang dilemparkan.
Seringkali Anda perlu memeriksa bahwa dalam beberapa kasus fungsi melempar kesalahan. Tetapi Anda selalu perlu memeriksa apakah ini droid yang kami cari - karena mungkin tiba-tiba muncul kesalahan lain yang dibuang, di tempat lain dan karena alasan lain ...
17.
function someBadFunc() { throw new Error('I am just wrong!'); } describe.skip('skipped test', ()=>{ it('should be fine', ()=>{ someBadFunc(); }); });
Ada apa di siniTes yang dinonaktifkan.
Tentu saja, suatu situasi mungkin selalu muncul ketika kode sudah diuji berkali-kali dengan tangan Anda, Anda perlu segera menggulungnya, dan untuk beberapa alasan tes tidak berfungsi. Misalnya, karena komplikasi yang tidak jelas dari tes lain, yang saya tulis sebelumnya. Dan tes dimatikan. Dan ini normal. Tidak normal - jangan langsung mengatur tugas untuk menghidupkan kembali tes. Jika ini tidak dilakukan, maka jumlah tes yang dinonaktifkan akan berlipat ganda, dan kode mereka akan terus menjadi usang. Sampai satu-satunya pilihan tetap - tunjukkan belas kasihan dan lempar semua tes ini nafig, karena lebih cepat menulisnya lagi daripada memahami kesalahannya.
Berikut adalah pilihan keluar. Semua tes ini lulus tes dengan baik, tetapi mereka rusak oleh desain. Tambahkan opsi Anda di komentar, atau di
repositori yang saya buat untuk mengumpulkan kesalahan seperti itu.