React Components, UI Testini Nasıl Kolaylaştırıyor?
Yayınlanan: 2022-03-11Arka uçları test etmek kolaydır. Seçtiğiniz dili seçin, en sevdiğiniz çerçeveyle eşleştirin, bazı testler yazın ve "çalıştır" düğmesine basın. Konsolunuz “Hay! İşe yarıyor!" Sürekli entegrasyon hizmetiniz, testlerinizi her basışta çalıştırır, hayat harikadır.
Elbette, test güdümlü geliştirme (TDD) ilk başta garip gelebilir, ancak öngörülebilir bir ortam, birden fazla test çalıştırıcısı, çerçevelere yerleştirilmiş test araçları ve sürekli entegrasyon desteği hayatı kolaylaştırır. Beş yıl önce, testlerin yaşadığım her sorunun çözümü olduğunu düşünürdüm.
Sonra Omurga büyüdü.
Hepimiz ön uç MVC'ye geçtik. Test edilebilir arka uçlarımız, yüceltilmiş veritabanı sunucuları haline geldi. En karmaşık kodumuz tarayıcıya taşındı. Ve uygulamalarımız artık pratikte test edilemezdi.
Bunun nedeni, ön uç kodunu ve UI bileşenlerini test etmenin biraz zor olmasıdır.
Tek istediğimiz modellerimizin iyi çalışıp çalışmadığını kontrol etmekse o kadar da kötü değil. Veya bir işlevi çağırmak doğru değeri değiştirecektir. React birim testi için yapmamız gereken tek şey:
- İyi biçimlendirilmiş, yalıtılmış modüller yazın.
- İşlevleri çalıştırmak için Jasmine veya Mocha testlerini (veya her neyse) kullanın.
- Karma veya Chutzpah gibi bir test koşucusu kullanın.
Bu kadar. Kodumuz birim test edilmiştir.
Eskiden ön uç testleri yapmak zor kısımdı . Her çerçevenin kendi fikirleri vardı ve çoğu durumda, testleri her çalıştırmak istediğinizde manuel olarak yenileyeceğiniz bir tarayıcı penceresi elde ettiniz. Elbette, her zaman unutacaksın. En azından, yaptığımı biliyorum.
2012'de Vojta Jina, Karma koşucusunu piyasaya sürdü (o sırada Testacular olarak adlandırıldı). Karma ile ön uç testi, takım zincirinin tam bir vatandaşı olur. React testlerimiz bir terminalde veya sürekli entegrasyon sunucusunda çalışır, bir dosyayı değiştirdiğimizde kendilerini yeniden çalıştırırlar ve hatta kodumuzu aynı anda birden fazla tarayıcıda test edebiliriz.
Daha ne isteyebiliriz ki? Aslında ön uç kodumuzu test etmek için.
Ön Uç Testi, Birim Testlerinden Daha Fazlasını Gerektirir
Birim testi harikadır: Bir algoritmanın her seferinde doğru şeyi yapıp yapmadığını görmenin veya giriş doğrulama mantığımızı, veri dönüşümlerini veya başka herhangi bir izole işlemi kontrol etmenin en iyi yoludur. Birim testi, temel bilgiler için mükemmeldir.
Ancak ön uç kodu, verileri manipüle etmekle ilgili değildir. Bu, kullanıcı olayları ve doğru zamanda doğru görünümleri oluşturmakla ilgilidir. Ön uçlar kullanıcılarla ilgilidir.
İşte yapabilmek istediğimiz şey:
- React kullanıcı olaylarını test edin
- Bu olaylara verilen yanıtı test edin
- Doğru şeylerin doğru zamanda oluşturulduğundan emin olun
- Testleri birçok tarayıcıda çalıştırın
- Dosya değişikliklerinde testleri yeniden çalıştırın
- Travis gibi sürekli entegrasyon sistemleriyle çalışın
Bunu yaptığım on yılda, React'i kurcalamaya başlayana kadar kullanıcı etkileşimini test etmenin ve görüntülemeyi görüntülemenin uygun bir yolunu bulamamıştım.
React Unit Testi: UI Bileşenleri
React, bu hedeflere ulaşmanın en kolay yoludur. Kısmen, bizi test edilebilir kalıplar kullanarak uygulamalar tasarlamaya nasıl zorladığı için, kısmen de harika React test araçları olduğu için.
React'i daha önce hiç kullanmadıysanız, React+d3.js kitabıma bakmalısınız . Görselleştirmelere yönelik, ancak bana bunun React'e “harika hafif bir giriş” olduğu söylendi.
React, bizi her şeyi “bileşenler” olarak oluşturmaya zorlar. React bileşenlerini widget'lar veya biraz mantıkla HTML parçaları olarak düşünebilirsiniz. Nesne olmaları dışında, işlevsel programlamanın en iyi ilkelerinin çoğunu takip ederler.
Örneğin, aynı parametre seti verildiğinde, bir React bileşeni her zaman aynı çıktıyı verecektir. Kaç kez işlenirse işlensin, kimin oluşturduğu önemli değil, çıktıyı nereye yerleştirdiğimiz önemli değil. Her zaman aynı. Sonuç olarak, React bileşenlerini test etmek için karmaşık yapı iskelesi yapmak zorunda değiliz. Yalnızca özellikleriyle ilgilenirler, global değişkenlerin ve yapılandırma nesnelerinin izlenmesi gerekmez.
Bunu büyük ölçüde durumdan kaçınarak başarıyoruz. İşlevsel programlamada bu referans şeffaflığı olarak adlandırırsınız. React'te bunun için özel bir isim olduğunu düşünmüyorum, ancak resmi belgeler mümkün olduğunca devlet kullanımından kaçınmanızı tavsiye ediyor.
Konu kullanıcı etkileşimlerini test etmeye gelince, React bizi geri çağırma işlevine bağlı olaylarla ilgilendiriyor. Test casusları kurmak ve bir tıklama olayının doğru işlevi çağırdığından emin olmak kolaydır. Ve React bileşenleri kendilerini oluşturduğu için, bir tıklama olayını tetikleyebilir ve HTML'de değişiklik olup olmadığını kontrol edebiliriz. Bu işe yarar çünkü bir React bileşeni yalnızca kendisiyle ilgilenir. Buraya tıklamak oradaki şeyleri değiştirmez. Hiçbir zaman bir dizi olay işleyiciyle uğraşmak zorunda kalmayacağız, sadece iyi tanımlanmış işlev çağrıları.
Oh, ve React sihir olduğu için DOM hakkında endişelenmemize gerek yok. React, bileşenleri bir JavaScript değişkenine dönüştürmek için sanal DOM denilen şeyi kullanır. Ve sanal DOM'ye bir referans, React bileşenlerini gerçekten test etmek için ihtiyacımız olan tek şey.
Çok tatlı.
TestUtils
React, yerleşik bir TestUtils
paketi ile birlikte gelir. Jest adında önerilen bir test koşucusu bile var ama ben bundan hoşlanmıyorum. Nedenini birazdan açıklayacağım. İlk olarak, TestUtils
.
Bunları require('react/addons').addons.TestUtils
gibi bir şey yaparak elde ederiz. Bu, kullanıcı etkileşimlerini test etmek ve çıktıyı kontrol etmek için giriş noktamızdır.
React TestUtils
, DOM'sini bir sayfaya eklemek yerine bir değişkene koyarak bir React bileşeni oluşturmamızı sağlar. Örneğin, bir React bileşeni oluşturmak için şöyle bir şey yapardık:
var component = TestUtils.renderIntoDocument( <MyComponent /> );
Ardından, tüm çocukların oluşturulup oluşturulmadığını kontrol etmek için TestUtils
kullanabiliriz. Bunun gibi bir şey:
var h1 = TestUtils.findRenderedDOMComponentWithTag( component, 'h1' );
findRenderedDOMComponentWithTag
kulağa nasıl geliyorsa onu yapacak: alt öğeleri gözden geçirin, aradığımız bileşeni bulun ve geri getirin. Döndürülen değer, bir React bileşeni gibi davranacaktır.
Daha sonra ham DOM öğesine erişmek ve değerlerini test etmek için getDOMNode()
'u kullanabiliriz. Bileşendeki bir h1
etiketinin “A title” yazdığını kontrol etmek için şunu yazardık:
expect(h1.getDOMNode().textContent) .toEqual("A title");
Bir araya getirildiğinde, tam test şöyle görünür:
it("renders an h1", function () { var component = TestUtils.renderIntoDocument( <MyComponent /> ); var h1 = TestUtils.findRenderedDOMComponentWithTag( component, 'h1' ); expect(h1.getDOMNode().textContent) .toEqual("A title"); });
İşin güzel yanı, TestUtils'in kullanıcı olaylarını da tetiklememize izin vermesi. Bir tıklama olayı için şöyle bir şey yazardık:
var node = component .findRenderedDOMComponentWithTag('button') .getDOMNode(); TestUtils.Simulate.click(node);
Bu, bir tıklamayı simüle eder ve çıktıyı, durumu veya her ikisini de değiştiren bileşen yöntemleri olması gereken olası dinleyicileri tetikler. Bu dinleyiciler, gerekirse bir üst bileşen üzerindeki bir işlevi çağırabilir.
Tüm durumların test edilmesi kolaydır: Değişen durum component.state
içindedir, çıktıya normal DOM işlevleriyle ve casuslarla işlev çağrılarına erişebiliriz.
Neden Jest değil?
React'in resmi belgeleri, test çalıştırıcısı ve React test çerçevesi olarak https://facebook.github.io/jest/ kullanılmasını önerir. Jest, Jasmine üzerine kuruludur ve aynı sözdizimini kullanır. Jasmine'den aldığınız her şeyin yanı sıra Jest, test ettiğimiz bileşen dışında her şeyle alay eder. Bu teoride harika, ama can sıkıcı buluyorum. Henüz uygulamadığımız veya kod tabanının farklı bir bölümünden gelen herhangi bir şey undefined
. Bu, çoğu durumda iyi olsa da, sessizce başarısız olan hatalara yol açabilir.
Örneğin, bir tıklama olayını test etmekte sorun yaşadım. Ne denediysem, dinleyicisini aramadı. Sonra işlevin Jest tarafından alay edildiğini fark ettim ve bana bunu hiç söylemedi.
Ancak Jest'in şimdiye kadarki en kötü kusuru, yeni değişiklikleri otomatik olarak test etmek için bir izleme modunun olmamasıydı. Bir kez çalıştırabilir, test sonuçlarını alabiliriz, o kadar. (Çalışırken testlerimi arka planda yapmayı seviyorum. Aksi halde çalıştırmayı unutuyorum.) Günümüzde artık bu bir sorun olmaktan çıkıyor.
Oh, ve Jest, birden çok tarayıcıda React testlerinin çalıştırılmasını desteklemiyor. Bu eskisinden daha az sorun, ancak heisenbug'un yalnızca Chrome'un belirli bir sürümünde meydana geldiği nadir bir durum için önemli bir özellik gibi hissediyorum…
Editörün notu: Bu makale orijinal olarak yazıldığından beri Jest önemli ölçüde gelişti. Daha yeni eğitimimiz olan Enzyme ve Jest Kullanarak React Unit Testing'i okuyabilir ve Jest testinin günümüzde işe yarayıp yaramayacağına kendiniz karar verebilirsiniz.
Tepki Testi: Entegre Bir Örnek
Her neyse, iyi bir ön uç React testinin teoride nasıl çalışması gerektiğini gördük. Kısa bir örnekle eyleme geçirelim.
React ve d3.js ile yapılmış bir dağılım grafiği bileşeni kullanarak rastgele sayılar üretmenin farklı yollarını görselleştireceğiz. Kod ve demosu da Github'da.
Test çalıştırıcı olarak Karma'yı, test çerçevesi olarak Mocha'yı ve modül yükleyici olarak Webpack'i kullanacağız.
Kurulum
Kaynak dosyalarımız bir <root>/src
dizinine gidecek ve testleri bir <root>/src/__tests__
dizinine koyacağız. Buradaki fikir, src
içine her ana bileşen için bir tane ve her biri kendi test dosyalarına sahip birkaç dizin koyabileceğimizdir. Kaynak kodu ve bunun gibi test dosyalarını bir araya getirmek, React bileşenlerini farklı projelerde yeniden kullanmayı kolaylaştırır.
Dizin yapısı yerindeyken, aşağıdaki gibi bağımlılıkları kurabiliriz:
$ npm install --save-dev react d3 webpack babel-loader karma karma-cli karma-mocha karma-webpack expect
Herhangi bir şey yüklenemezse, kurulumun o bölümünü yeniden çalıştırmayı deneyin. NPM bazen yeniden çalıştırmada kaybolan şekillerde başarısız olur.

İşimiz bittiğinde package.json
dosyamız şöyle görünmelidir:
// package.json { "name": "react-testing-example", "description": "A sample project to investigate testing options with ReactJS", "scripts": { "test": "karma start" }, // ... "homepage": "https://github.com/Swizec/react-testing-example", "devDependencies": { "babel-core": "^5.2.17", "babel-loader": "^5.0.0", "d3": "^3.5.5", "expect": "^1.6.0", "jsx-loader": "^0.13.2", "karma": "^0.12.31", "karma-chrome-launcher": "^0.1.10", "karma-cli": "0.0.4", "karma-mocha": "^0.1.10", "karma-sourcemap-loader": "^0.3.4", "karma-webpack": "^1.5.1", "mocha": "^2.2.4", "react": "^0.13.3", "react-hot-loader": "^1.2.7", "react-tools": "^0.13.3", "webpack": "^1.9.4", "webpack-dev-server": "^1.8.2" } }
Bazı yapılandırmalardan sonra, testleri npm test
veya karma start
ile çalıştırabileceğiz.
Yapılandırma
Yapılandırma için fazla bir şey yok. Webpack'in kodumuzu nasıl bulacağını bildiğinden ve Karma'nın testleri nasıl çalıştıracağını bildiğinden emin olmalıyız.
Karma ve Webpack'in birlikte çalışmasına yardımcı olmak için bir ./tests.webpack.js
dosyasına iki satır JavaScript koyduk:
// tests.webpack.js var context = require.context('./src', true, /-test\.jsx?$/); context.keys().forEach(context);
Bu, Webpack'e -test
soneki olan her şeyi test takımının parçası olarak düşünmesini söyler.
Karma'yı yapılandırmak biraz daha fazla iş gerektirir:
// karma.conf.js var webpack = require('webpack'); module.exports = function (config) { config.set({ browsers: ['Chrome'], singleRun: true, frameworks: ['mocha'], files: [ 'tests.webpack.js' ], preprocessors: { 'tests.webpack.js': ['webpack'] }, reporters: ['dots'], webpack: { module: { loaders: [ {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'} ] }, watch: true }, webpackServer: { noInfo: true } }); };
Bu satırların çoğu varsayılan bir Karma yapılandırmasındandır. Testlerin Chrome'da çalışması gerektiğini söylemek için browsers
, hangi test frameworks
kullandığımızı belirtmek için çerçeveleri ve testlerin varsayılan olarak yalnızca bir kez çalıştırılmasını sağlamak için singleRun
kullandık. karma start --no-single-run
arka planda çalışmasını sağlayabilirsiniz.
Bu üçü belli. Webpack olayı daha ilginç.
Webpack, kodumuzun bağımlılık ağacını işlediğinden, files
dizisindeki tüm dosyalarımızı belirtmemiz gerekmez. Yalnızca, tüm gerekli dosyaları gerektiren tests.webpack.js
ihtiyacımız var.
Webpack'e ne yapacağını söylemek için webpack
ayarını kullanırız. Normal bir ortamda bu kısım bir webpack.config.js
dosyasına gider.
Ayrıca Webpack'e JavaScript'lerimiz için babel-loader
loader'ı kullanmasını söyleriz. Bu bize ECMAScript2015 ve React's JSX'in tüm yeni ve gösterişli özelliklerini verir.
webpackServer
konfigürasyonu ile Webpack'e herhangi bir hata ayıklama bilgisi yazdırmamasını söyleriz. Bu sadece test çıktımızı bozar.
Bir React Bileşeni ve Bir Test
Çalışan bir test paketi ile gerisi basittir. Bir dizi rastgele koordinat kabul eden ve bir grup nokta içeren bir <svg>
öğesi oluşturan bir bileşen yapmalıyız.
En iyi React testi uygulamalarını (yani standart TDD uygulamasını) izleyerek önce testi, ardından gerçek React bileşenini yazacağız. src/__tests__/
içindeki bir vanilya testleri dosyasıyla başlayalım:
// ScatterPlot-test.jsx var React = require('react/addons'), TestUtils = React.addons.TestUtils, expect = require('expect'), ScatterPlot = require('../ScatterPlot.jsx'); var d3 = require('d3'); describe('ScatterPlot', function () { var normal = d3.random.normal(1, 1), mockData = d3.range(5).map(function () { return {x: normal(), y: normal()}; }); });
Öncelikle React'e, TestUtils'ine, d3.js'ye, expect
kitaplığına ve test ettiğimiz koda ihtiyacımız var. Ardından describe
ile yeni bir test takımı oluşturuyoruz ve bazı rastgele veriler oluşturuyoruz.
İlk testimiz için ScatterPlot
bir başlık oluşturduğundan emin olalım. Testimiz describe
bloğunun içine giriyor:
// ScatterPlot-test.jsx it("renders an h1", function () { var scatterplot = TestUtils.renderIntoDocument( <ScatterPlot /> ); var h1 = TestUtils.findRenderedDOMComponentWithTag( scatterplot, 'h1' ); expect(h1.getDOMNode().textContent).toEqual("This is a random scatterplot"); });
Çoğu test aynı modeli izleyecektir:
- Oluşturun.
- Belirli bir düğümü bulun.
- İçeriği kontrol edin.
Daha önce gördüğümüz gibi, renderIntoDocument
bileşenimizi işler, findRenderedDOMComponentWithTag
test ettiğimiz belirli kısmı bulur ve getDOMNode
bize ham DOM erişimi verir.
İlk başta testimiz başarısız olacak. Geçmesi için başlık etiketi oluşturan bileşeni yazmalıyız:
var React = require('react/addons'); var d3 = require('d3'); var ScatterPlot = React.createClass({ render: function () { return ( <div> <h1>This is a random scatterplot</h1> </div> ); } }); module.exports = ScatterPlot;
Bu kadar. ScatterPlot
bileşeni, beklenen metni içeren bir <h1>
etiketiyle bir <div>
oluşturur ve testimiz başarılı olur. Evet, HTML'den daha uzun, ama benimle birlikte ol.
Baykuşun Kalanını Çiz
Örneğimizin geri kalanını yukarıda belirtildiği gibi GitHub'da görebilirsiniz. Bu makalede adım adım açıklamayı atlayacağız, ancak genel süreç yukarıdakiyle aynıdır. Yine de size daha ilginç bir test göstermek istiyorum. Tüm veri noktalarının grafikte görünmesini sağlayan bir test:
// ScatterPlot-test.jsx it("renders a circle for each datapoint", function () { var scatterplot = TestUtils.renderIntoDocument( <ScatterPlot data={mockData} /> ); var circles = TestUtils.scryRenderedDOMComponentsWithTag( scatterplot, 'circle' ); expect(circles.length).toEqual(5); });
Öncekiyle aynı. Oluşturun, düğümleri bulun, sonucu kontrol edin. Buradaki ilginç kısım, bu DOM düğümlerini çizmektir. ScatterPlot
bileşenine şöyle bir d3.js büyüsü ekliyoruz:
// ScatterPlot.jsx componentWillMount: function () { this.yScale = d3.scale.linear(); this.xScale = d3.scale.linear(); this.update_d3(this.props); }, componentWillReceiveProps: function (newProps) { this.update_d3(newProps); }, update_d3: function (props) { this.yScale .domain([d3.min(props.data, function (d) { return dy; }), d3.max(props.data, function (d) { return dy; })]) .range([props.point_r, Number(props.height-props.point_r)]); this.xScale .domain([d3.min(props.data, function (d) { return dx; }), d3.max(props.data, function (d) { return dx; })]) .range([props.point_r, Number(props.width-props.point_r)]); }, ...
X ve Y etki alanları için boş d3 ölçekleri ayarlamak için componentWillMount
ve bir şeyler değiştiğinde güncellendiklerinden emin olmak için componentWillReceiveProps
kullanıyoruz. Ardından update_d3
, her iki ölçek için domain
ve range
ayarladığınızdan emin olur.
Veri kümemizdeki rastgele değerler ile resimdeki konumlar arasında çeviri yapmak için iki ölçeği kullanacağız. Rastgele oluşturucuların çoğu, piksel olarak görülemeyecek kadar küçük olan [0,1] aralığındaki sayıları döndürür.
Ardından, bileşenimizin oluşturma yöntemine noktaları ekliyoruz:
// ScatterPlot.jsx render: function () { return ( <div> <h1>This is a random scatterplot</h1> <svg width={this.props.width} height={this.props.height}> {this.props.data.map(function (pos, i) { var key = "circle-"+i; return ( <circle key={key} cx={this.xScale(pos.x)} cy={this.yScale(pos.y)} r={this.props.point_r} /> ); }.bind(this))}; </svg> </div> ); }
Bu kod this.props.data
dizisinden geçer ve her veri noktası için bir <circle>
öğesi ekler. Basit.
Veri görselleştirme bileşenleri yapmak için React ve d3.js'yi birleştirmek hakkında daha fazla bilgi edinmek istiyorsanız, bu da kitabım React+d3.js'ye göz atmak için harika bir neden.
Otomatik React Bileşen Testi: Göründüğünden Daha Kolay
React ile test edilebilir ön uç bileşenleri yazmak hakkında bilmemiz gereken tek şey bu. Daha fazla kod testi React bileşeni görmek için, yukarıda belirtildiği gibi Github'daki React test örneği kod tabanına bakın.
Şunu öğrendik:
- React, bizi modülerleştirmeye ve kapsüllemeye zorluyor.
- Bu, React UI testinin otomatikleştirilmesini kolaylaştırır.
- Birim testleri ön uçlar için yeterli değildir.
- Karma harika bir test koşucusudur.
- Jest'in potansiyeli var, ancak henüz tam olarak orada değil. (Ya da belki şimdi öyledir.)
Bu makaleyi beğendiyseniz, beni Twitter'da takip edin ve aşağıya bir yorum bırakın. Okuduğunuz için teşekkürler ve mutlu React testleri!