この投稿では、ユーザーストーリーから開発まで、テスト駆動開発(TDD)を使用してReactアプリを開発します。また、TDDにはJestとEnzymeを使用します。このガイドを完了すると、次のことができるようになります。
この記事は、Reactの基本的な知識があることを前提としています。 Reactを初めて使用する場合は、 公式チュートリアル ApeeScapeの2019Reactチュートリアルをご覧ください。 パート1 そして パート2 。
いくつかのUIコンポーネントで構成される基本的なポモドーロタイマーアプリを構築します。各コンポーネントには、対応するテストファイルに個別のテストセットがあります。まず、プロジェクトの要件に基づいて、次のようにエピックとユーザーストーリーを作成できます。
大作 | ユーザーストーリー | 合否基準 |
ユーザーとして、自分の時間を管理できるようにタイマーを使用する必要があります。 | ユーザーとして、時間をカウントダウンできるようにタイマーを開始する必要があります。 | ユーザーが次のことができることを確認します。 *タイマーを開始します *タイマーがカウントダウンを開始するのを参照してください ユーザーがスタートボタンを2回以上クリックしても、時間のカウントダウンが中断されないようにする必要があります。 |
ユーザーとして、必要なときにだけ時間をカウントダウンできるように、タイマーを停止する必要があります。 | ユーザーが次のことができることを確認します。 *タイマーを停止します *タイマーが停止したことを確認してください ユーザーが停止ボタンを2回以上クリックしても、何も起こりません。 | |
ユーザーとして、最初から時間をカウントダウンできるように、タイマーをリセットする必要があります。 | ユーザーが次のことができることを確認します。 *タイマーをリセットする *デフォルトにリセットされたタイマーを参照してください |
まず、を使用してReactプロジェクトを作成します Reactアプリを作成する 次のように:
$ npx create-react-app react-timer $ cd react-timer $ npm start
URLで開いている新しいブラウザタブが表示されます http:// localhost:3000 。を使用して実行中のReactアプリを停止できます Ctrl + C 。
次に、追加します です そして 酵素 およびいくつかの依存関係は次のとおりです。
$ npm i -D enzyme $ npm i -D react-test-renderer enzyme-adapter-react-16
また、というファイルを追加または更新します setupTests.js の中に src ディレクトリ:
import { configure } from ‘enzyme’; import Adapter from ‘enzyme-adapter-react-16’; configure({ adapter: new Adapter() });
以来 Reactアプリを作成する を実行します setupTests.js 各テストの前にファイルを実行し、酵素を適切に構成します。
変数と基本的なCSSリセットを記述します。 CSS変数 アプリケーションでグローバルに利用できます。から変数を定義します :rootスコープ 。変数を定義するための構文は、それぞれが–で始まり、その後に変数名が続くカスタムプロパティ表記を使用することです。
に移動します index.css ファイルを作成し、以下を追加します。
:root { --main-font: “Roboto”, sans-serif; } body, div, p { margin: 0; padding: 0; }
次に、CSSをアプリケーションにインポートする必要があります。 index.jsファイルを次のように更新します。
import React from ‘react’; import ReactDOM from ‘react-dom’; import ‘./index.css’; ReactDOM.render( document.getElementById(“root”) )
ご存知かもしれませんが、TDDプロセスは次のようになります。
したがって、浅いレンダリングテストの最初のテストを追加してから、テストに合格するコードを記述します。名前の付いた新しいスペックファイルを追加します App.spec.js に src / components / App 次のようなディレクトリ:
import React from ‘react’; import { shallow } from ‘enzyme’; import App from ‘./App’; describe(‘App’, () => { it(‘should render a ’, () => { const container = shallow(); expect(container.find(‘div’).length).toEqual(1); }); });
次に、テストを実行できます。
$ npm test
テストが失敗することがわかります。
次に、テストに合格するためのAppコンポーネントの作成に進みます。案内する App.jsx ディレクトリ内 src / components / App 次のようにコードを追加します。
import React from ‘react’; const App = () => ; export default App;
ここで、テストを再度実行します。
$ npm test
これで最初のテストに合格するはずです。
ファイルを作成します App.css ディレクトリ内 src / components / App 次のように、アプリコンポーネントにスタイルを追加します。
.app-container { height: 100vh; width: 100vw; align-items: center; display: flex; justify-content: center; }
これで、CSSをにインポートする準備ができました。 App.jsx ファイル:
import React from ‘react’; import ‘./App.css’; const App = () => ; export default App;
次に、更新する必要があります index.js 次のようにAppコンポーネントをインポートするファイル:
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( , 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()
最後に、アプリにはタイマーコンポーネントが含まれるため、 App.spec.js アプリ内のタイマーコンポーネントの存在を確認するファイル。また、浅いレンダリングテストは各テストケースの前に実行する必要があるため、最初のテストケースの外側でコンテナ変数を宣言します。
import React from 'react' import { shallow } from 'enzyme' import App from './App' import Timer from '../Timer/Timer' describe('App', () => { let container beforeEach(() => (container = shallow())) it('should render a ', () => { expect(container.find('div').length).toEqual(1) }) it('should render the Timer Component', () => { expect(container.containsMatchingElement()).toEqual(true) }) })
npm test
を実行した場合この段階では、Timerコンポーネントがまだ存在しないため、テストは失敗します。
次に、という名前のファイルを作成します Timer.spec.js 名前の付いた新しいディレクトリに タイマー 下 src / components ディレクトリ。
また、浅いレンダリングテストを追加します Timer.spec.js ファイル:
import React from 'react' import { shallow } from 'enzyme' import Timer from './Timer' describe('Timer', () => { let container beforeEach(() => (container = shallow())) it('should render a ', () => { expect(container.find('div').length).toBeGreaterThanOrEqual(1) }) })
予想どおり、テストは失敗します。
次に、という新しいファイルを作成しましょう Timer.jsx ユーザーストーリーに基づいて同じ変数とメソッドを定義します。
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 ; }; } export default Timer;
これはテストに合格し、 すべき レンダリングします Timer.spec.js ファイル、しかしテスト いけない アプリコンポーネントにタイマーコンポーネントをまだ追加していないため、タイマーコンポーネントをレンダリングします。
タイマーコンポーネントをに追加します App.jsx このようなファイル:
import React from 'react'; import './App.css'; import Timer from '../Timer/Timer'; const App = () => ( ); export default App;
これですべてのテストに合格するはずです。
タイマーに関連するCSS変数を追加し、小型デバイス用のメディアクエリを追加します。
ファイルを更新する index.css 次のように:
: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%; } }
また、作成します Timer.css ディレクトリの下のファイル コンポーネント/タイマー :
.timer-container { background-color: var(--timer-background-color); border: var(--timer-border); height: var(--timer-height); width: var(--timer-width); }
更新する必要があります Timer.jsx インポートするには Timer.css ファイル。
import React, { Component } from 'react' import './Timer.css'
ここでReactアプリを実行すると、ブラウザーに境界線のある単純な画面が表示されます。
3つのボタンが必要です。 起動停止 、および リセット 、したがって、作成します TimerButtonコンポーネント 。
まず、更新する必要があります Timer.spec.js の存在を確認するファイル TimerButton のコンポーネント タイマー 成分:
it('should render instances of the TimerButton component', () => { expect(container.find('TimerButton').length).toEqual(3) })
それでは、を追加しましょう TimerButton.spec.js と呼ばれる新しいディレクトリのファイル TimerButton 下 src / components ディレクトリを作成し、次のようにテストをファイルに追加しましょう。
import React from 'react' import { shallow } from 'enzyme' import TimerButton from './TimerButton' describe('TimerButton', () => { let container beforeEach(() => { container = shallow( ) }) it('should render a ', () => { expect(container.find('div').length).toBeGreaterThanOrEqual(1) }) })
ここで、テストを実行すると、テストが失敗することがわかります。
を作成しましょう TimerButton.jsx のファイル TimerButton 成分:
Excel2016でpowerpivotを使用する方法
import React from 'react'; import PropTypes from 'prop-types'; const TimerButton = ({ buttonAction, buttonValue }) => ( ); TimerButton.propTypes = { buttonAction: PropTypes.func.isRequired, buttonValue: PropTypes.string.isRequired, }; export default TimerButton;
npm test
を実行した場合この段階で、テスト TimerButtonコンポーネントのインスタンスをレンダリングする必要があります ただし、TimerButtonコンポーネントをTimerコンポーネントにまだ追加していないため、失敗します。
をインポートしましょう TimerButton コンポーネントと3つ追加 TimerButton のrenderメソッドのコンポーネント Timer.jsx :
render = () => { return ( ); };
次に、TimerButtonコンポーネントのCSS変数を追加します。に変数を追加しましょう :ルート スコープ index.css ファイル:
:root { ... --button-border: 3px solid #000000; --button-text-size: 2em; } @media screen and (max-width: 1024px) { :root { … --button-text-size: 4em; } }
また、というファイルを作成しましょう TimerButton.css の中に TimerButton 下のディレクトリ 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; }
更新しましょう TimerButton.jsx それに応じてインポートする TimerButton.css ファイルとボタンの値を表示するには:
import React from 'react'; import PropTypes from 'prop-types'; import './TimerButton.css'; const TimerButton = ({ buttonAction, buttonValue }) => ( {buttonValue}
); TimerButton.propTypes = { buttonAction: PropTypes.func.isRequired, buttonValue: PropTypes.string.isRequired, }; export default TimerButton;
また、更新する必要があります Timer.css 3つのボタンを水平に揃えるために、 Timer.css ファイルも:
import React from 'react'; import PropTypes from 'prop-types'; import './TimerButton.css'; const TimerButton = ({ buttonAction, buttonValue }) => ( {buttonValue}
); TimerButton.propTypes = { buttonAction: PropTypes.func.isRequired, buttonValue: PropTypes.string.isRequired, }; export default TimerButton;
ここでReactアプリを実行すると、次のような画面が表示されます。
次のような関数を実装したいので、タイマーをリファクタリングします。 startTimer、stopTimer、restartTimer 、および resetTimer 。更新しましょう Timer.spec.js 最初のファイル:
describe('mounted Timer', () => { let container; beforeEach(() => (container = mount())); 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); }); });
テストを実行すると、更新されていないため、追加されたテストが失敗することがわかります。 TimerButton コンポーネントはまだです。更新しましょう TimerButton クリックイベントを追加するコンポーネント:
const TimerButton = ({ buttonAction, buttonValue }) => ( buttonAction()}> {buttonValue}
);
これで、テストに合格するはずです。
次に、各関数がで呼び出されたときの状態を確認するためのテストをさらに追加します。 マウントされたタイマー テストケース:
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); });
テストを実行すると、各メソッドをまだ実装していないため、テストが失敗することがわかります。それでは、テストに合格するために各関数を実装しましょう。
startTimer() { this.setState({ isOn: true }); } stopTimer() { this.setState({ isOn: false }); } resetTimer() { this.stopTimer(); this.setState({ minutes: 25, seconds: 0, }); }
テストを実行すると、テストに合格することがわかります。それでは、残りの関数をで実装しましょう 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 ( {minutes}:{seconds <10 ? `0${seconds}` : seconds} ); }; } export default Timer;
以前に準備したユーザーストーリーに基づいて、すべての機能が機能することがわかります。
これが、TDDを使用して基本的なReactアプリを開発した方法です。ユーザーストーリーと受け入れ基準がより詳細である場合、テストケースをより正確に記述できるため、さらに貢献できます。
TDDを使用してアプリケーションを開発する場合、プロジェクトを叙事詩やユーザーストーリーに分類するだけでなく、受け入れ基準に十分に備えることが非常に重要です。この記事では、プロジェクトを分解し、準備された受け入れ基準をReactTDD開発に使用する方法を紹介したいと思います。
React TDDに関連するリソースはたくさんありますが、この記事が、ユーザーストーリーを使用したReactでのTDD開発について少し学ぶのに役立つことを願っています。このアプローチをエミュレートすることを選択した場合は、完全なソースコードを参照してください ここに 。
テスト駆動開発の場合、最初にテストを作成し、テストが失敗するかどうかを確認する必要があります。テストが失敗したことを確認したら、テストに合格するコードを記述し、必要に応じてリファクタリングします。次に、次のテストのためにこれらの手順をすべて繰り返します。
テストを作成する主な理由は、アプリが正常に機能することを確認することです。 Reactでは、アプリがエラーなしで正しくレンダリングされるかどうかをテストする必要があります。また、出力と状態、およびイベントをテストする必要があります。最後に、他のテクノロジースタックと同様にエッジケースをテストする必要があります。
テスト駆動開発は、本番環境のバグを減らし、コードの品質を向上させ、コードの保守を容易にするため、有益です。また、回帰テストの自動テストも提供します。ただし、TDDはGUIテストにコストがかかる可能性があり、TDDが多すぎるとコードがより複雑になります。