Bereaksi Pengembangan Berbasis Tes: Dari Cerita Pengguna hingga Produksi
Diterbitkan: 2022-03-11Dalam posting ini, kita akan mengembangkan aplikasi React menggunakan test-driven development (TDD) dari cerita pengguna hingga pengembangan. Juga, kita akan menggunakan Jest and Enzyme untuk TDD. Setelah menyelesaikan panduan ini, Anda akan dapat:
- Buat epos dan cerita pengguna berdasarkan persyaratan.
- Buat pengujian berdasarkan cerita pengguna.
- Kembangkan aplikasi React menggunakan TDD.
- Gunakan Enzyme and Jest untuk menguji aplikasi React.
- Gunakan/gunakan kembali variabel CSS untuk desain responsif.
- Buat komponen React yang dapat digunakan kembali yang merender dan berfungsi secara berbeda berdasarkan props yang disediakan.
- Ketik cek komponen props menggunakan React PropTypes.
Artikel ini mengasumsikan bahwa Anda memiliki pengetahuan dasar tentang React. Jika Anda benar-benar baru di React, saya sarankan Anda menyelesaikan tutorial resmi dan melihat Tutorial React 2019 Toptal: Bagian 1 dan Bagian 2.
Ikhtisar Aplikasi React Berbasis Uji Kami
Kami akan membangun aplikasi pengatur waktu pomodoro dasar yang terdiri dari beberapa komponen UI. Setiap komponen akan memiliki serangkaian tes terpisah dalam file tes yang sesuai. Pertama-tama, kita bisa membuat epos dan cerita pengguna sebagai berikut berdasarkan kebutuhan proyek kita.
EPIK | KISAH PENGGUNA | KRITERIA PENERIMAAN |
Sebagai pengguna, saya perlu menggunakan pengatur waktu agar saya dapat mengatur waktu saya. | Sebagai pengguna, saya perlu memulai penghitung waktu agar saya dapat menghitung mundur waktu saya. | Pastikan pengguna dapat: *mulai pengatur waktu *lihat penghitung waktu mulai menghitung mundur Menghitung mundur waktu tidak boleh terganggu bahkan jika pengguna mengklik tombol mulai lebih dari sekali. |
Sebagai pengguna, saya perlu menghentikan penghitung waktu agar saya dapat menghitung mundur waktu saya hanya saat dibutuhkan. | Pastikan pengguna dapat: *hentikan pengatur waktu *lihat timer berhenti Tidak ada yang akan terjadi bahkan jika pengguna mengklik tombol stop lebih dari sekali. | |
Sebagai pengguna, saya perlu mengatur ulang penghitung waktu agar saya dapat menghitung mundur waktu saya dari awal. | Pastikan pengguna dapat: *setel ulang pengatur waktu *lihat timer reset ke default |
Bingkai gambar
Pengaturan Proyek
Pertama, kita akan membuat proyek React menggunakan Create React App sebagai berikut:
$ npx create-react-app react-timer $ cd react-timer $ npm start
Anda akan melihat tab browser baru terbuka di URL http://localhost:3000. Anda dapat menghentikan aplikasi React yang sedang berjalan menggunakan Ctrl+C .
Sekarang, kita akan menambahkan Jest and Enzyme dan beberapa dependensi sebagai berikut:
$ npm i -D enzyme $ npm i -D react-test-renderer enzyme-adapter-react-16
Selain itu, kami akan menambahkan atau memperbarui file bernama setupTests.js di direktori src :
import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() });
Karena Create React App menjalankan file setupTests.js sebelum setiap pengujian, ia akan mengeksekusi dan mengonfigurasi Enzyme dengan benar.
Mengonfigurasi CSS
Kami akan menulis variabel dan reset CSS dasar karena kami ingin variabel CSS tersedia secara global di aplikasi. Kami akan mendefinisikan variabel dari lingkup :root. Sintaks untuk mendefinisikan variabel adalah dengan menggunakan notasi properti khusus, masing-masing diawali dengan – diikuti dengan nama variabel.
Arahkan ke file index.css dan tambahkan berikut ini:
:root { --main-font: “Roboto”, sans-serif; } body, div, p { margin: 0; padding: 0; }
Sekarang, kita perlu mengimpor CSS ke dalam aplikasi kita. Perbarui file index.js sebagai berikut:
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode> document.getElementById(“root”) )
Tes Render Dangkal
Seperti yang mungkin sudah Anda ketahui, proses TDD akan terlihat seperti ini:
- Tambahkan tes.
- Jalankan semua tes, dan Anda akan melihat tes gagal.
- Tulis kode untuk lulus ujian.
- Jalankan semua tes.
- Faktor ulang.
- Mengulang.
Oleh karena itu, kita akan menambahkan tes pertama untuk tes render yang dangkal dan kemudian menulis kode untuk lulus tes. Tambahkan file spesifikasi baru bernama App.spec.js ke direktori src/components/App sebagai berikut:
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); }); });
Kemudian, Anda dapat menjalankan tes:
$ npm test
Anda akan melihat tes gagal.
Komponen Aplikasi
Sekarang, kita akan melanjutkan untuk membuat komponen Aplikasi untuk lulus tes. Arahkan ke App.jsx di direktori src/components/App dan tambahkan kode sebagai berikut:
import React from 'react'; const App = () => <div className=”app-container” />; export default App;
Sekarang, jalankan tes lagi.
$ npm test
Tes pertama sekarang harus lulus.
Menambahkan CSS Aplikasi
Kita akan membuat file App.css di direktori src/components/App untuk menambahkan beberapa gaya ke komponen App sebagai berikut:
.app-container { height: 100vh; width: 100vw; align-items: center; display: flex; justify-content: center; }
Sekarang, kita siap untuk mengimpor CSS ke file App.jsx :
import React from 'react'; import './App.css'; const App = () => <div className=”app-container” />; export default App;
Selanjutnya, kita harus memperbarui file index.js untuk mengimpor komponen App sebagai berikut:
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()
Menambahkan Komponen Timer
Terakhir, aplikasi akan berisi komponen Timer, oleh karena itu kita akan memperbarui file App.spec.js untuk memeriksa keberadaan komponen Timer di aplikasi kita. Selain itu, kita akan mendeklarasikan variabel container di luar kasus uji pertama karena uji render dangkal perlu dilakukan sebelum setiap kasus uji.
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) }) })
Jika Anda menjalankan npm test
pada tahap ini, pengujian akan gagal karena komponen Timer belum ada.
Menulis Tes Rendering Dangkal Timer
Sekarang, kita akan membuat file bernama Timer.spec.js di direktori baru bernama Timer di bawah direktori src/components .
Juga, kami akan menambahkan tes render dangkal di file Timer.spec.js :
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) }) })
Tes akan gagal, seperti yang diharapkan.
Membuat Komponen Timer
Selanjutnya, mari buat file baru bernama Timer.jsx dan definisikan variabel dan metode yang sama berdasarkan cerita pengguna:
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;
Ini harus lulus pengujian dan harus merender <div />
di file Timer.spec.js , tetapi pengujian tidak boleh merender Komponen Timer karena kami belum menambahkan komponen Timer di komponen aplikasi.
Kami akan menambahkan komponen Timer di file App.jsx seperti ini:
import React from 'react'; import './App.css'; import Timer from '../Timer/Timer'; const App = () => ( <div className="app-container"> <Timer /> </div> ); export default App;
Semua tes harus lulus sekarang.
Menambahkan Timer CSS
Kami akan menambahkan variabel CSS yang terkait dengan Timer dan menambahkan kueri media untuk perangkat yang lebih kecil.
Perbarui file index.css sebagai berikut:
: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%; } }
Juga, kita akan membuat file Timer.css di bawah direktori component/Timer :
.timer-container { background-color: var(--timer-background-color); border: var(--timer-border); height: var(--timer-height); width: var(--timer-width); }
Kita harus mengupdate Timer.jsx untuk mengimpor file Timer.css .

import React, { Component } from "react" import "./Timer.css"
Jika Anda menjalankan aplikasi React sekarang, Anda akan melihat layar sederhana dengan perbatasan di browser Anda.
Tulis Tes Rendering Dangkal TimerButton
Kita membutuhkan tiga tombol: Start, Stop , dan Reset , maka kita akan membuat Komponen TimerButton .
Pertama, kita perlu memperbarui file Timer.spec.js untuk memeriksa keberadaan komponen TimerButton dalam komponen Timer :
it("should render instances of the TimerButton component", () => { expect(container.find("TimerButton").length).toEqual(3) })
Sekarang, mari tambahkan file TimerButton.spec.js di direktori baru bernama TimerButton di bawah direktori src/components dan tambahkan tes ke file seperti ini:
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) }) })
Sekarang, jika Anda menjalankan tes, Anda akan melihat tes gagal.
Mari buat file TimerButton.jsx untuk komponen TimerButton :
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;
Jika Anda menjalankan npm test
pada tahap ini, pengujian harus merender instance komponen TimerButton tetapi akan gagal karena kami belum menambahkan komponen TimerButton ke komponen Timer.
Mari impor komponen TimerButton dan tambahkan tiga komponen TimerButton dalam metode render di Timer.jsx :
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> ); };
CSS TimerButton
Sekarang, saatnya menambahkan variabel CSS untuk komponen TimerButton. Mari tambahkan variabel dalam :root scope ke file index.css :
:root { ... --button-border: 3px solid #000000; --button-text-size: 2em; } @media screen and (max-width: 1024px) { :root { … --button-text-size: 4em; } }
Juga, mari buat file bernama TimerButton.css di direktori TimerButton di bawah direktori src/components :
.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; }
Mari perbarui TimerButton.jsx sesuai untuk mengimpor file TimerButton.css dan untuk menampilkan nilai tombol:
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;
Juga, kita perlu memperbarui Timer.css untuk menyelaraskan ketiga tombol secara horizontal, jadi mari kita juga memperbarui file Timer.css :
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;
Jika Anda menjalankan aplikasi React sekarang, Anda akan melihat layar sebagai berikut:

Memfaktorkan Ulang Timer
Kami akan memfaktorkan ulang Timer karena kami ingin mengimplementasikan fungsi seperti startTimer, stopTimer, restartTimer , dan resetTimer . Mari kita perbarui file Timer.spec.js terlebih dahulu:
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); }); });
Jika Anda menjalankan pengujian, Anda akan melihat pengujian yang ditambahkan gagal karena kami belum memperbarui komponen TimerButton . Mari perbarui komponen TimerButton untuk menambahkan acara klik:
const TimerButton = ({ buttonAction, buttonValue }) => ( <div className="button-container" onClick={() => buttonAction()}> <p className="button-value">{buttonValue}</p> </div> );
Sekarang, tes harus lulus.
Selanjutnya, kita akan menambahkan lebih banyak tes untuk memeriksa status saat setiap fungsi dipanggil dalam kasus uji Timer yang dipasang :
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); });
Jika Anda menjalankan tes, Anda akan melihatnya gagal karena kami belum menerapkan setiap metode. Jadi mari kita implementasikan setiap fungsi untuk lulus tes:
startTimer() { this.setState({ isOn: true }); } stopTimer() { this.setState({ isOn: false }); } resetTimer() { this.stopTimer(); this.setState({ minutes: 25, seconds: 0, }); }
Anda akan melihat tes lulus jika Anda menjalankannya. Sekarang, mari kita implementasikan fungsi yang tersisa di Timer.jsx :
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;
Anda akan melihat semua fungsi bekerja berdasarkan cerita pengguna yang kami siapkan sebelumnya.
Jadi, begitulah cara kami mengembangkan aplikasi React dasar menggunakan TDD. Jika cerita pengguna dan kriteria penerimaan lebih rinci, kasus uji dapat ditulis lebih tepat, sehingga berkontribusi lebih banyak lagi.
Membungkus
Saat mengembangkan aplikasi menggunakan TDD, sangat penting untuk tidak hanya memecah proyek menjadi epik atau cerita pengguna, tetapi juga mempersiapkan dengan baik untuk kriteria penerimaan. Pada artikel ini, saya ingin menunjukkan kepada Anda bagaimana memecah proyek dan menggunakan kriteria penerimaan yang telah disiapkan untuk pengembangan React TDD.
Meskipun ada banyak sumber yang terkait dengan React TDD di luar sana, saya harap artikel ini membantu Anda mempelajari sedikit tentang pengembangan TDD dengan React menggunakan cerita pengguna. Jika Anda memilih untuk meniru pendekatan ini, silakan merujuk ke kode sumber lengkap di sini.