React Test Odaklı Geliştirme: Kullanıcı Hikayelerinden Üretime

Yayınlanan: 2022-03-11

Bu yazıda, kullanıcı hikayelerinden geliştirmeye kadar test odaklı geliştirme (TDD) kullanarak bir React uygulaması geliştireceğiz. Ayrıca TDD için Jest ve Enzyme kullanacağız. Bu kılavuzu tamamladıktan sonra şunları yapabileceksiniz:

  • Gereksinimlere göre destanlar ve kullanıcı hikayeleri oluşturun.
  • Kullanıcı hikayelerine dayalı testler oluşturun.
  • TDD kullanarak bir React uygulaması geliştirin.
  • Bir React uygulamasını test etmek için Enzim ve Jest'i kullanın.
  • Duyarlı tasarım için CSS değişkenlerini kullanın/yeniden kullanın.
  • Sağlanan aksesuarlara göre farklı şekilde işleyen ve işlev gören yeniden kullanılabilir bir React bileşeni oluşturun.
  • React PropTypes kullanarak bileşen desteklerini yazın.

Bu makale, temel React bilgisine sahip olduğunuzu varsaymaktadır. React'te tamamen yeniyseniz, resmi öğreticiyi tamamlamanızı ve Toptal'ın 2019 React Eğitimi: Bölüm 1 ve Bölüm 2'ye bir göz atmanızı tavsiye ederim.

Test Odaklı React Uygulamamıza Genel Bakış

Bazı UI bileşenlerinden oluşan temel bir pomodoro zamanlayıcı uygulaması oluşturacağız. Her bileşen, karşılık gelen bir test dosyasında ayrı bir test grubuna sahip olacaktır. Öncelikle proje gereksinimlerimize göre aşağıdaki gibi destanlar ve kullanıcı hikayeleri oluşturabiliriz.

EPİK KULLANICI HİKAYESİ KABUL KRİTERLERİ
Bir kullanıcı olarak zamanımı yönetebilmek için zamanlayıcıyı kullanmam gerekiyor. Bir kullanıcı olarak, zamanımı geri sayabilmem için zamanlayıcıyı başlatmam gerekiyor. Kullanıcının şunları yapabildiğinden emin olun:

*zamanlayıcıyı başlat
*zamanlayıcının geri saymaya başladığını görün

Kullanıcı başlat düğmesine bir kereden fazla bassa bile sürenin geri sayımı kesintiye uğramamalıdır.
Bir kullanıcı olarak, zamanımı yalnızca gerektiğinde geri sayabilmem için zamanlayıcıyı durdurmam gerekiyor. Kullanıcının şunları yapabildiğinden emin olun:

*zamanlayıcıyı durdur
*zamanlayıcının durduğunu görün

Kullanıcı, durdurma düğmesine bir kereden fazla tıklasa bile hiçbir şey olmamalıdır.
Bir kullanıcı olarak, zamanımı en baştan geri sayabilmem için zamanlayıcıyı sıfırlamam gerekiyor. Kullanıcının şunları yapabildiğinden emin olun:

*zamanlayıcıyı sıfırla
*zamanlayıcının varsayılana sıfırlandığını görün

tel kafes

tel kafes

Proje Kurulumu

İlk olarak Create React App kullanarak aşağıdaki gibi bir React projesi oluşturacağız:

 $ npx create-react-app react-timer $ cd react-timer $ npm start

http://localhost:3000 URL'sinde yeni bir tarayıcı sekmesinin açıldığını göreceksiniz. Çalışan React uygulamasını Ctrl+C kullanarak durdurabilirsiniz.

Şimdi, Jest ve Enzyme ve bazı bağımlılıkları aşağıdaki gibi ekleyeceğiz:

 $ npm i -D enzyme $ npm i -D react-test-renderer enzyme-adapter-react-16

Ayrıca, src dizinine setupTests.js adlı bir dosya ekleyeceğiz veya güncelleyeceğiz:

 import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() });

Create React App her testten önce setupTests.js dosyasını çalıştırdığından, Enzyme'i çalıştıracak ve düzgün şekilde yapılandıracaktır.

CSS'yi Yapılandırma

CSS değişkenlerinin uygulamada global olarak kullanılabilir olmasını istediğimiz için değişkenler ve temel bir CSS sıfırlama yazacağız. Değişkenleri :root kapsamından tanımlayacağız. Değişkenleri tanımlamanın sözdizimi, her biri - ile başlayan ve ardından değişken adının geldiği özel özellik gösterimini kullanmaktır.

index.css dosyasına gidin ve aşağıdakileri ekleyin:

 :root { --main-font: “Roboto”, sans-serif; } body, div, p { margin: 0; padding: 0; }

Şimdi, CSS'yi uygulamamıza aktarmamız gerekiyor. index.js dosyasını aşağıdaki gibi güncelleyin:

 import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode> document.getElementById(“root”) )

Sığ Render Testi

Bildiğiniz gibi, TDD süreci şöyle görünecektir:

  1. Bir test ekleyin.
  2. Tüm testleri çalıştırın ve testin başarısız olduğunu göreceksiniz.
  3. Testi geçmek için kodu yazın.
  4. Tüm testleri çalıştırın.
  5. Yeniden düzenleme.
  6. Tekrar et.

Bu nedenle, sığ bir oluşturma testi için ilk testi ekleyeceğiz ve ardından testi geçmek için kodu yazacağız. App.spec.js adlı yeni bir özellik dosyasını src/components/App dizinine aşağıdaki gibi ekleyin:

 import React from 'react'; import { shallow } from 'enzyme'; import App from './App'; describe('App', () => { it('should render a <div />', () => { const container = shallow(<App />); expect(container.find('div').length).toEqual(1); }); });

Ardından, testi çalıştırabilirsiniz:

 $ npm test

Testin başarısız olduğunu göreceksiniz.

Uygulama Bileşeni

Şimdi, testi geçmek için Uygulama bileşenini oluşturmaya devam edeceğiz. src/components/App dizininde App.jsx'e gidin ve kodu aşağıdaki gibi ekleyin:

 import React from 'react'; const App = () => <div className=”app-container” />; export default App;

Şimdi testi tekrar çalıştırın.

 $ npm test

İlk test şimdi geçmelidir.

Uygulama CSS'si Ekleme

App bileşenine aşağıdaki gibi bir stil eklemek için src/components/App dizininde bir App.css dosyası oluşturacağız:

 .app-container { height: 100vh; width: 100vw; align-items: center; display: flex; justify-content: center; }

Artık CSS'yi App.jsx dosyasına aktarmaya hazırız:

 import React from 'react'; import './App.css'; const App = () => <div className=”app-container” />; export default App;

Ardından, App bileşenini içe aktarmak için index.js dosyasını aşağıdaki gibi güncellememiz gerekiyor:

 import React from "react" import ReactDOM from "react-dom" import "./index.css" import App from "./components/App/App" import * as serviceWorker from "./serviceWorker" ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") ) // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister()

Zamanlayıcı Bileşenini Ekleme

Son olarak, uygulama Zamanlayıcı bileşenini içerecektir, bu nedenle uygulamamızda Zamanlayıcı bileşeninin varlığını kontrol etmek için App.spec.js dosyasını güncelleyeceğiz. Ayrıca, sığ oluşturma testinin her test senaryosundan önce yapılması gerektiğinden, ilk test senaryosunun dışında kap değişkenini bildireceğiz.

 import React from "react" import { shallow } from "enzyme" import App from "./App" import Timer from "../Timer/Timer" describe("App", () => { let container beforeEach(() => (container = shallow(<App />))) it("should render a <div />", () => { expect(container.find("div").length).toEqual(1) }) it("should render the Timer Component", () => { expect(container.containsMatchingElement(<Timer />)).toEqual(true) }) })

Bu aşamada npm test çalıştırırsanız, Timer bileşeni henüz mevcut olmadığından test başarısız olacaktır.

Zamanlayıcı Sığ İşleme Testini Yazma

Şimdi, src/components dizini altında Timer adlı yeni bir dizinde Timer.spec.js adlı bir dosya oluşturacağız.

Ayrıca, Timer.spec.js dosyasına sığ oluşturma testini ekleyeceğiz:

 import React from "react" import { shallow } from "enzyme" import Timer from "./Timer" describe("Timer", () => { let container beforeEach(() => (container = shallow(<Timer />))) it("should render a <div />", () => { expect(container.find("div").length).toBeGreaterThanOrEqual(1) }) })

Test beklendiği gibi başarısız olacaktır.

Zamanlayıcı Bileşenini Oluşturma

Ardından, Timer.jsx adında yeni bir dosya oluşturalım ve kullanıcı hikayelerine dayalı olarak aynı değişkenleri ve yöntemleri tanımlayalım:

 import React, { Component } from 'react'; class Timer extends Component { constructor(props) { super(props); this.state = { minutes: 25, seconds: 0, isOn: false }; } startTimer() { console.log('Starting timer.'); } stopTimer() { console.log('Stopping timer.'); } resetTimer() { console.log('Resetting timer.'); } render = () => { return <div className="timer-container" />; }; } export default Timer;

Bu, testi geçmeli ve Timer.spec.js dosyasında bir <div /> oluşturmalıdır, ancak Timer bileşenini henüz uygulama bileşenine eklemediğimiz için test , Timer Bileşenini oluşturmamalıdır.

Timer bileşenini App.jsx dosyasına şu şekilde ekleyeceğiz:

 import React from 'react'; import './App.css'; import Timer from '../Timer/Timer'; const App = () => ( <div className="app-container"> <Timer /> </div> ); export default App;

Tüm testler şimdi geçmeli.

Zamanlayıcı CSS Ekleme

Zamanlayıcı ile ilgili CSS değişkenleri ekleyeceğiz ve daha küçük cihazlar için medya sorguları ekleyeceğiz.

index.css dosyasını aşağıdaki gibi güncelleyin:

 :root { --timer-background-color: #FFFFFF; --timer-border: 1px solid #000000; --timer-height: 70%; --timer-width: 70%; } body, div, p { margin: 0; padding: 0; } @media screen and (max-width: 1024px) { :root { --timer-height: 100%; --timer-width: 100%; } }

Ayrıca, component/Timer dizini altında Timer.css dosyasını oluşturacağız:

 .timer-container { background-color: var(--timer-background-color); border: var(--timer-border); height: var(--timer-height); width: var(--timer-width); }

Timer.css dosyasını içe aktarmak için Timer.jsx'i güncellememiz gerekiyor.

 import React, { Component } from "react" import "./Timer.css"

React uygulamasını şimdi çalıştırırsanız, tarayıcınızda kenarlıklı basit bir ekran göreceksiniz.

TimerButton Sığ İşleme Testini Yazın

Üç düğmeye ihtiyacımız var: Başlat, Durdur ve Sıfırla , bu nedenle TimerButton Bileşenini oluşturacağız.

İlk olarak, Timer bileşenindeki TimerButton bileşeninin varlığını kontrol etmek için Timer.spec.js dosyasını güncellememiz gerekiyor:

 it("should render instances of the TimerButton component", () => { expect(container.find("TimerButton").length).toEqual(3) })

Şimdi TimerButton.spec.js dosyasını src/components dizini altında TimerButton adlı yeni bir dizine ekleyelim ve dosyaya testi şu şekilde ekleyelim:

 import React from "react" import { shallow } from "enzyme" import TimerButton from "./TimerButton" describe("TimerButton", () => { let container beforeEach(() => { container = shallow( <TimerButton buttonAction={jest.fn()} buttonValue={""} /> ) }) it("should render a <div />", () => { expect(container.find("div").length).toBeGreaterThanOrEqual(1) }) })

Şimdi, testi çalıştırırsanız, testin başarısız olduğunu göreceksiniz.

TimerButton bileşeni için TimerButton.jsx dosyasını oluşturalım:

 import React from 'react'; import PropTypes from 'prop-types'; const TimerButton = ({ buttonAction, buttonValue }) => ( <div className="button-container" /> ); TimerButton.propTypes = { buttonAction: PropTypes.func.isRequired, buttonValue: PropTypes.string.isRequired, }; export default TimerButton;

Bu aşamada npm test çalıştırırsanız, test TimerButton bileşeninin örneklerini oluşturmalıdır, ancak TimerButton bileşenlerini henüz Timer bileşenine eklemediğimiz için başarısız olacaktır.

TimerButton bileşenini içe aktaralım ve Timer.jsx'teki render yöntemine üç TimerButton bileşeni ekleyelim :

 render = () => { return ( <div className="timer-container"> <div className="time-display"></div> <div className="timer-button-container"> <TimerButton buttonAction={this.startTimer} buttonValue={'Start'} /> <TimerButton buttonAction={this.stopTimer} buttonValue={'Stop'} /> <TimerButton buttonAction={this.resetTimer} buttonValue={'Reset'} /> </div> </div> ); };

ZamanlayıcıDüğmesi CSS

Şimdi TimerButton bileşeni için CSS değişkenleri ekleme zamanı. İndex.css dosyasına : root kapsamındaki değişkenleri ekleyelim:

 :root { ... --button-border: 3px solid #000000; --button-text-size: 2em; } @media screen and (max-width: 1024px) { :root { … --button-text-size: 4em; } }

Ayrıca src/components dizini altındaki TimerButton dizininde TimerButton.css adında bir dosya oluşturalım:

 .button-container { flex: 1 1 auto; text-align: center; margin: 0px 20px; border: var(--button-border); font-size: var(--button-text-size); } .button-container:hover { cursor: pointer; }

TimerButton.css dosyasını içe aktarmak ve düğme değerini görüntülemek için TimerButton.jsx'i uygun şekilde güncelleyelim:

 import React from 'react'; import PropTypes from 'prop-types'; import './TimerButton.css'; const TimerButton = ({ buttonAction, buttonValue }) => ( <div className="button-container"> <p className="button-value">{buttonValue}</p> </div> ); TimerButton.propTypes = { buttonAction: PropTypes.func.isRequired, buttonValue: PropTypes.string.isRequired, }; export default TimerButton;

Ayrıca, üç düğmeyi yatay olarak hizalamak için Timer.css dosyasını güncellememiz gerekiyor, bu yüzden Timer.css dosyasını da güncelleyelim:

 import React from 'react'; import PropTypes from 'prop-types'; import './TimerButton.css'; const TimerButton = ({ buttonAction, buttonValue }) => ( <div className="button-container"> <p className="button-value">{buttonValue}</p> </div> ); TimerButton.propTypes = { buttonAction: PropTypes.func.isRequired, buttonValue: PropTypes.string.isRequired, }; export default TimerButton;

React uygulamasını şimdi çalıştırırsanız aşağıdaki gibi bir ekran göreceksiniz:

zamanlayıcı

Zamanlayıcıyı Yeniden Düzenleme

startTimer, stopTimer, restartTimer ve resetTimer gibi işlevleri uygulamak istediğimiz için Zamanlayıcıyı yeniden değerlendireceğiz. Önce Timer.spec.js dosyasını güncelleyelim:

 describe('mounted Timer', () => { let container; beforeEach(() => (container = mount(<Timer />))); it('invokes startTimer when the start button is clicked', () => { const spy = jest.spyOn(container.instance(), 'startTimer'); container.instance().forceUpdate(); expect(spy).toHaveBeenCalledTimes(0); container.find('.start-timer').first().simulate('click'); expect(spy).toHaveBeenCalledTimes(1); }); it('invokes stopTimer when the stop button is clicked', () => { const spy = jest.spyOn(container.instance(), 'stopTimer'); container.instance().forceUpdate(); expect(spy).toHaveBeenCalledTimes(0); container.find('.stop-timer').first().simulate('click'); expect(spy).toHaveBeenCalledTimes(1); }); it('invokes resetTimer when the reset button is clicked', () => { const spy = jest.spyOn(container.instance(), 'resetTimer'); container.instance().forceUpdate(); expect(spy).toHaveBeenCalledTimes(0); container.find('.reset-timer').first().simulate('click'); expect(spy).toHaveBeenCalledTimes(1); }); });

Testi çalıştırırsanız, henüz TimerButton bileşenini güncellemediğimiz için eklenen testlerin başarısız olduğunu göreceksiniz. Click olayını eklemek için TimerButton bileşenini güncelleyelim:

 const TimerButton = ({ buttonAction, buttonValue }) => ( <div className="button-container" onClick={() => buttonAction()}> <p className="button-value">{buttonValue}</p> </div> );

Şimdi, testler geçmeli.

Ardından, monte edilen Zamanlayıcı test senaryosunda her bir işlev çağrıldığında durumu kontrol etmek için daha fazla test ekleyeceğiz:

 it('should change isOn state true when the start button is clicked', () => { container.instance().forceUpdate(); container.find('.start-timer').first().simulate('click'); expect(container.instance().state.isOn).toEqual(true); }); it('should change isOn state false when the stop button is clicked', () => { container.instance().forceUpdate(); container.find('.stop-timer').first().simulate('click'); expect(container.instance().state.isOn).toEqual(false); }); it('should change isOn state false when the reset button is clicked', () => { container.instance().forceUpdate(); container.find('.stop-timer').first().simulate('click'); expect(container.instance().state.isOn).toEqual(false); expect(container.instance().state.minutes).toEqual(25); expect(container.instance().state.seconds).toEqual(0); });

Testleri çalıştırırsanız, her bir yöntemi henüz uygulamadığımız için bunların başarısız olduğunu göreceksiniz. Öyleyse testleri geçmek için her işlevi uygulayalım:

 startTimer() { this.setState({ isOn: true }); } stopTimer() { this.setState({ isOn: false }); } resetTimer() { this.stopTimer(); this.setState({ minutes: 25, seconds: 0, }); }

Çalıştırırsanız testlerin geçtiğini göreceksiniz. Şimdi kalan işlevleri Timer.jsx'te uygulayalım :

 import React, { Component } from 'react'; import './Timer.css'; import TimerButton from '../TimerButton/TimerButton'; class Timer extends Component { constructor(props) { super(props); this.state = { minutes: 25, seconds: 0, isOn: false, }; this.startTimer = this.startTimer.bind(this); this.stopTimer = this.stopTimer.bind(this); this.resetTimer = this.resetTimer.bind(this); } startTimer() { if (this.state.isOn === true) { return; } this.myInterval = setInterval(() => { const { seconds, minutes } = this.state; if (seconds > 0) { this.setState(({ seconds }) => ({ seconds: seconds - 1, })); } if (seconds === 0) { if (minutes === 0) { clearInterval(this.myInterval); } else { this.setState(({ minutes }) => ({ minutes: minutes - 1, seconds: 59, })); } } }, 1000); this.setState({ isOn: true }); } stopTimer() { clearInterval(this.myInterval); this.setState({ isOn: false }); } resetTimer() { this.stopTimer(); this.setState({ minutes: 25, seconds: 0, }); } render = () => { const { minutes, seconds } = this.state; return ( <div className="timer-container"> <div className="time-display"> {minutes}:{seconds < 10 ? `0${seconds}` : seconds} </div> <div className="timer-button-container"> <TimerButton className="start-timer" buttonAction={this.startTimer} buttonValue={'Start'} /> <TimerButton className="stop-timer" buttonAction={this.stopTimer} buttonValue={'Stop'} /> <TimerButton className="reset-timer" buttonAction={this.resetTimer} buttonValue={'Reset'} /> </div> </div> ); }; } export default Timer;

Tüm fonksiyonların daha önce hazırladığımız kullanıcı hikayelerine göre çalıştığını göreceksiniz.

zamanlayıcı

İşte bu şekilde TDD kullanarak temel bir React uygulaması geliştirdik. Kullanıcı hikayeleri ve kabul kriterleri daha detaylı ise, test senaryoları daha kesin yazılabilir, böylece daha da fazla katkı sağlanabilir.

Toplama

TDD kullanarak bir uygulama geliştirirken, projeyi sadece destanlara veya kullanıcı hikayelerine bölmek değil, aynı zamanda kabul kriterlerine iyi hazırlanmak da çok önemlidir. Bu yazımda sizlere React TDD geliştirmesi için projeyi nasıl parçalayacağınızı ve hazırlanan kabul kriterlerini nasıl kullanacağınızı göstermek istedim.

Dışarıda React TDD ile ilgili birçok kaynak olmasına rağmen, umarım bu makale, kullanıcı hikayelerini kullanarak React ile TDD geliştirme hakkında biraz bilgi edinmenize yardımcı olmuştur. Bu yaklaşımı taklit etmeyi seçerseniz, lütfen burada tam kaynak koduna bakın.