JavaScript'te Test Edilebilir Kod Yazma: Kısa Bir Genel Bakış
Yayınlanan: 2022-03-11İster Mocha veya Jasmine gibi bir test çerçevesiyle eşleştirilmiş DOM kullanıyor istersek PhantomJS gibi başsız bir tarayıcıda DOM'a bağlı testler yapıyor olalım, JavaScript birim testi seçeneklerimiz şimdi her zamankinden daha iyi.
Ancak bu, test ettiğimiz kodun bizim için araçlarımız kadar kolay olduğu anlamına gelmez! Kolayca test edilebilen kodu düzenlemek ve yazmak biraz çaba ve planlama gerektirir, ancak kodumuzu test etme zamanı geldiğinde zor bir duruma düşmemek için kullanabileceğimiz, işlevsel programlama kavramlarından ilham alan birkaç model vardır. Bu makalede, JavaScript'te test edilebilir kod yazmak için bazı faydalı ipuçlarını ve kalıpları inceleyeceğiz.
İş Mantığını ve Görüntü Mantığını Ayrı Tutun
JavaScript tabanlı bir tarayıcı uygulamasının birincil işlerinden biri, son kullanıcı tarafından tetiklenen DOM olaylarını dinlemek ve ardından bazı iş mantığını çalıştırarak ve sonuçları sayfada görüntüleyerek bunlara yanıt vermektir. DOM olay dinleyicilerinizi kurduğunuz yerde işin büyük kısmını yapan anonim bir işlev yazmak cazip gelebilir. Bunun yarattığı sorun, artık anonim işlevinizi test etmek için DOM olaylarını simüle etmeniz gerektiğidir. Bu, hem kod satırlarında hem de testlerin çalışması için gereken sürede ek yük oluşturabilir.
Bunun yerine, adlandırılmış bir işlev yazın ve bunu olay işleyicisine iletin. Bu şekilde, sahte bir DOM olayını tetiklemek için doğrudan ve çemberler arasında atlamadan adlandırılmış işlevler için testler yazabilirsiniz.
Bu, DOM'dan daha fazlası için geçerlidir. Hem tarayıcıdaki hem de Düğümdeki birçok API, olayları tetiklemek ve dinlemek veya diğer eşzamansız çalışma türlerinin tamamlanmasını beklemek üzere tasarlanmıştır. Temel bir kural, çok sayıda isimsiz geri arama işlevi yazıyorsanız, kodunuzu test etmenin kolay olmayabileceğidir.
// hard to test $('button').on('click', () => { $.getJSON('/path/to/data') .then(data => { $('#my-list').html('results: ' + data.join(', ')); }); }); // testable; we can directly run fetchThings to see if it // makes an AJAX request without having to trigger DOM // events, and we can run showThings directly to see that it // displays data in the DOM without doing an AJAX request $('button').on('click', () => fetchThings(showThings)); function fetchThings(callback) { $.getJSON('/path/to/data').then(callback); } function showThings(data) { $('#my-list').html('results: ' + data.join(', ')); }
Eşzamansız Kodla Geri Aramaları veya Sözleri Kullanın
Yukarıdaki kod örneğinde, yeniden düzenlenmiş fetchThings işlevimiz, çalışmalarının çoğunu eşzamansız olarak yapan bir AJAX isteği çalıştırır. Bu, işlevi çalıştıramayacağımız ve beklediğimiz her şeyi yaptığını test edemeyeceğimiz anlamına gelir, çünkü çalışmasının ne zaman biteceğini bilemeyiz.
Bu sorunu çözmenin en yaygın yolu, eşzamansız olarak çalışan işleve parametre olarak bir geri çağırma işlevini iletmektir. Birim testlerinizde, geçtiğiniz geri aramada iddialarınızı çalıştırabilirsiniz.
Eşzamansız kodu düzenlemenin bir başka yaygın ve giderek daha popüler yolu, Promise API'sidir. Neyse ki $.ajax ve jQuery'nin eşzamansız işlevlerinin çoğu zaten bir Promise nesnesi döndürür, bu nedenle birçok yaygın kullanım durumu zaten kapsanmıştır.
// hard to test; we don't know how long the AJAX request will run function fetchData() { $.ajax({ url: '/path/to/data' }); } // testable; we can pass a callback and run assertions inside it function fetchDataWithCallback(callback) { $.ajax({ url: '/path/to/data', success: callback, }); } // also testable; we can run assertions when the returned Promise resolves function fetchDataWithPromise() { return $.ajax({ url: '/path/to/data' }); }
Yan Etkilerden Kaçının
Argümanları alan ve yalnızca bu argümanlara dayalı bir değer döndüren fonksiyonlar yazın, tıpkı bir sonuç elde etmek için bir matematik denklemine sayıları delmek gibi. İşleviniz bir dış duruma (örneğin, bir sınıf örneğinin özellikleri veya bir dosyanın içeriği) bağlıysa ve işlevinizi test etmeden önce bu durumu ayarlamanız gerekiyorsa, testlerinizde daha fazla kurulum yapmanız gerekir. Çalıştırılmakta olan diğer herhangi bir kodun aynı durumu değiştirmediğine güvenmeniz gerekecek.
Aynı şekilde, çalışırken harici durumu değiştiren (bir dosyaya yazmak veya değerleri bir veritabanına kaydetmek gibi) işlevler yazmaktan kaçının. Bu, diğer kodları güvenle test etme yeteneğinizi etkileyebilecek yan etkileri önler. Genel olarak, yan etkileri mümkün olduğunca az "yüzey alanı" ile kodunuzun kenarlarına mümkün olduğunca yakın tutmak en iyisidir. Sınıflar ve nesne örnekleri söz konusu olduğunda, bir sınıf yönteminin yan etkileri, test edilen sınıf örneğinin durumuyla sınırlı olmalıdır.
// hard to test; we have to set up a globalListOfCars object and set up a // DOM with a #list-of-models node to test this code function processCarData() { const models = globalListOfCars.map(car => car.model); $('#list-of-models').html(models.join(', ')); } // easy to test; we can pass an argument and test its return value, without // setting any global values on the window or checking the DOM the result function buildModelsString(cars) { const models = cars.map(car => car.model); return models.join(','); }
Bağımlılık Enjeksiyonu Kullan
Bir işlevin dış durumu kullanımını azaltmak için yaygın bir model, bir işlevin tüm dış ihtiyaçlarını işlev parametreleri olarak ileten bağımlılık enjeksiyonudur.

// depends on an external state database connector instance; hard to test function updateRow(rowId, data) { myGlobalDatabaseConnector.update(rowId, data); } // takes a database connector instance in as an argument; easy to test! function updateRow(rowId, data, databaseConnector) { databaseConnector.update(rowId, data); }
Bağımlılık enjeksiyonunu kullanmanın ana faydalarından biri, gerçek yan etkilere neden olmayan (bu durumda, veritabanı satırlarını güncellemek) birim testlerinizden sahte nesneleri geçebilmeniz ve sahte nesnenizin üzerinde işlem yapıldığını iddia edebilmenizdir. beklenen şekilde.
Her İşleve Tek Bir Amaç Verin
Birkaç şey yapan uzun işlevleri, kısa, tek amaçlı işlevler koleksiyonuna ayırın. Bu, büyük bir fonksiyonun bir değer döndürmeden önce her şeyi doğru yaptığını ummak yerine, her bir fonksiyonun görevini doğru şekilde yapıp yapmadığını test etmeyi çok daha kolaylaştırır.
İşlevsel programlamada, birkaç tek amaçlı işlevi bir araya getirme eylemine kompozisyon denir. Underscore.js, bir işlevler listesi alan ve bunları birbirine zincirleyen, her adımın dönüş değerini alan ve onu sıradaki bir sonraki işleve geçiren bir _.compose
işlevine bile sahiptir.
// hard to test function createGreeting(name, location, age) { let greeting; if (location === 'Mexico') { greeting = '!Hola'; } else { greeting = 'Hello'; } greeting += ' ' + name.toUpperCase() + '! '; greeting += 'You are ' + age + ' years old.'; return greeting; } // easy to test function getBeginning(location) { if (location === 'Mexico') { return 'Hola'; } else { return 'Hello'; } } function getMiddle(name) { return ' ' + name.toUpperCase() + '! '; } function getEnd(age) { return 'You are ' + age + ' years old.'; } function createGreeting(name, location, age) { return getBeginning(location) + getMiddle(name) + getEnd(age); }
Parametreleri Değiştirmeyin
JavaScript'te diziler ve nesneler değer yerine referansa göre iletilir ve bunlar değiştirilebilir. Bu, bir işleve parametre olarak bir nesneyi veya diziyi ilettiğinizde, hem kodunuz hem de nesneyi veya diziyi ilettiğiniz işlev, o dizinin veya nesnenin bellekteki aynı örneğini değiştirme yeteneğine sahip olmak anlamına gelir. Bu, kendi kodunuzu test ediyorsanız, kodunuzun çağırdığı işlevlerden hiçbirinin nesnelerinizi değiştirmediğine güvenmeniz gerektiği anlamına gelir. Kodunuza aynı nesneyi değiştiren yeni bir yer eklediğinizde, o nesnenin neye benzemesi gerektiğini takip etmek giderek zorlaşır ve test etmeyi zorlaştırır.
Bunun yerine, bir nesneyi veya diziyi alan bir fonksiyonunuz varsa, o nesne veya dizi üzerinde salt okunurmuş gibi hareket etmesini sağlayın. Kodda yeni bir nesne veya dizi oluşturun ve ihtiyaçlarınıza göre buna değerler ekleyin. Veya, üzerinde işlem yapmadan önce geçirilen nesneyi veya diziyi klonlamak için Alt Çizgi veya Lodash kullanın. Daha da iyisi, salt okunur veri yapıları oluşturan Immutable.js gibi bir araç kullanın.
// alters objects passed to it function upperCaseLocation(customerInfo) { customerInfo.location = customerInfo.location.toUpperCase(); return customerInfo; } // sends a new object back instead function upperCaseLocation(customerInfo) { return { name: customerInfo.name, location: customerInfo.location.toUpperCase(), age: customerInfo.age }; }
Testlerinizi Kodunuzdan Önce Yazın
Test ettikleri koddan önce birim testleri yazma işlemine test odaklı geliştirme (TDD) denir. Pek çok geliştirici, TDD'yi çok yararlı buluyor.
İlk önce testlerinizi yazarak, ortaya çıkardığınız API hakkında onu tüketen bir geliştiricinin bakış açısıyla düşünmek zorunda kalırsınız. Ayrıca, gereksiz yere karmaşık bir çözüm üzerinde gereğinden fazla mühendislik yapmak yerine, yalnızca testlerinizin uyguladığı sözleşmeyi karşılamaya yetecek kadar kod yazdığınızdan emin olmanıza yardımcı olur.
Uygulamada TDD, tüm kod değişiklikleriniz için taahhüt edilmesi zor olabilecek bir disiplindir. Ancak denemeye değer göründüğünde, tüm kodları test edilebilir tutmanızı garanti etmenin harika bir yoludur.
Sarmak
Karmaşık JavaScript uygulamalarını yazarken ve test ederken düşmesi çok kolay olan birkaç tuzak olduğunu hepimiz biliyoruz. Ancak umarım bu ipuçlarıyla ve kodumuzu her zaman mümkün olduğunca basit ve işlevsel tutmayı hatırlayarak, test kapsamımızı yüksek ve genel kod karmaşıklığını düşük tutabiliriz!
- JavaScript Geliştiricilerinin Yaptığı En Yaygın 10 Hata
- Hız İhtiyacı: Bir Toptal JavaScript Kodlama Mücadelesi Retrospektifi