react로 tdd 쵸큼 맛보기
TRANSCRIPT
React로�TDD�쵸큼�맛보기김훈민@네이버
나는자바스크립트개발자다
김코딩
스마트에디터�3.0,�프론트엔드�개발�-�@네이버�
기술�블로그(huns.me)에�아주�가끔�글�쓰기�
페이스북�커뮤니티�프론트엔드개발그룹�운영하는�척�
네이버�사내�리액트�미트업�운영하는�척�
Facebook은�React를�왜�만들었을까�-�@FBDG�2015�
Angular2�vs�React,�React�vs�Angular2�-�@Deview�2016
오늘의�주제는�…
단위�테스트�어떻게�작성하나요?�
TDD,�왜�해요?�
TDD�하면�뭐가�좋아요?�
TDD�하면�설계를�잘�할�수�있나요?�
TDD�하면�클린�코드를�작성할�수�있나요?�
TDD�하는�모습이�궁금해요
오늘의�주제는�…
단위�테스트�어떻게�작성하나요?�
TDD,�왜�해요?�
TDD�하면�뭐가�좋아요?�
TDD�하면�설계를�잘�할�수�있나요?�
TDD�하면�클린�코드를�작성할�수�있나요?�
TDD�하는�모습이�궁금해요
오늘�만들�녀석은,�
“막막”�간단한�Spinbox
기본값은�200�
값�입력�
증가�버튼을�클릭하여�값이�1�증가�
감소�버튼을�클릭하면�값이�1�감소
환경은…?
Webpack�
ES2015�with�babel�
Jest�
Enzyme
들어가기�전에
테스트�설정은�잘�되어있나?
구현�코드 테스트�코드
it('스핀박스�생성할�수�있다.', () => { // given - 어떤�조건�또는�상황일�때 // when - 어떤�행위를�하면 // then - 결과가�무엇이어야�한다});
it('스핀박스를�생성할�수�있다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then expect(spinbox).toBeDefined();});
it('스핀박스�생성할�수�있다.', () => { // setup // exercise // verify});
“어디에서�시작하지?”
기본값은�200�
값�입력�
증가�버튼을�클릭하여�값이�1�증가�
감소�버튼을�클릭하면�값이�1�감소
“어디에서�시작하지?”
기본값은�200�
값�입력�
증가�버튼을�클릭하여�값이�1�증가�
감소�버튼을�클릭하면�값이�1�감소
it('스핀박스를�생성할�수�있다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then expect(spinbox).toBeDefined();});
it('스핀박스를�생성할�수�있다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then expect(spinbox).toBeDefined();});
복사해서�붙여넣기
it('스핀박스를�생성할�수�있다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then expect(spinbox).toBeDefined();});
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then expect(spinbox).toBeDefined();});
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then const input = spinbox .find('input') .getDOMNode();
expect(input.value).toEqual(200);});
import React from 'react';
const Spinbox = () => ( <div> <input type=“text" /> <button>▲</button> <button>▼</button> </div>);
Spinbox.defaultProps = {};Spinbox.propTypes = {};
export default Spinbox;
import React from 'react';
const Spinbox = () => ( <div> <input type="text" defaultValue="200" /> <button>▲</button> <button>▼</button> </div>);
Spinbox.defaultProps = {};Spinbox.propTypes = {};
export default Spinbox;
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then const input = spinbox .find('input') .getDOMNode();
const actualValue = Number(input.value); expect(actualValue).toEqual(200);});
import React from 'react';
const Spinbox = () => ( <div> <input type="text" defaultValue="200" /> <button>▲</button> <button>▼</button> </div>);
Spinbox.defaultProps = {};Spinbox.propTypes = {};
export default Spinbox;
넘나�신경�쓰이는�것!
TDD는��테스트와�구현을�반복하면서��계단을�하나씩�쌓아가는�여정�
“테스트�코드”는��어떻게�검증해야�하지?
두�번째�요구사항
기본값은�200�
값�입력�
증가�버튼을�클릭하여�값이�1�증가�
감소�버튼을�클릭하면�값이�1�감소
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then const input = spinbox .find('input') .getDOMNode();
const actualValue = Number(input.value); const defaultValue = 200;
expect(actualValue).toEqual(defaultValue);});
it('입력�폼에�999를�입력할�수�있다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then const input = spinbox .find('input') .getDOMNode();
const actualValue = Number(input.value); const defaultValue = 200;
expect(actualValue).toEqual(defaultValue);});
it('입력�폼에�999를�입력할�수�있다.', () => { // given const spinbox = mount(<Spinbox />);
// when const input = spinbox.find('input'); input.simulate('change', { target: { value: '999' } });
// then const inputNode = input.getDOMNode(); const actualValue = Number(inputNode.value);
expect(actualValue).toEqual(999);});
import React from 'react';
const Spinbox = () => ( <div> <input type="text" defaultValue="200" /> <button>▲</button> <button>▼</button> </div>);
Spinbox.defaultProps = {};Spinbox.propTypes = {};
export default Spinbox;
import React from 'react';
const Spinbox = () => ( <div> <input type="text" defaultValue="200" onChange={ () => { this.setState({ value: 999 }) } } /> <button>▲</button> <button>▼</button> </div>);
Spinbox.defaultProps = {};Spinbox.propTypes = {};
export default Spinbox;
아놬!�무상태�컴포넌트!
import React from 'react';
const Spinbox = () => ( <div> <input type="text" defaultValue="200" /> <button>▲</button> <button>▼</button> </div>);
Spinbox.defaultProps = {};Spinbox.propTypes = {};
export default Spinbox;
안전할�때�까지�되돌리기!
it('입력�폼에�숫자�값을�입력할�수�있다.', () => { // given const spinbox = mount(<Spinbox />);
// when const input = spinbox.find('input'); input.simulate('change', { target: { value: '999' } });
// then const inputNode = input.getDOMNode(); const actualValue = Number(inputNode.value);
expect(actualValue).toEqual(999);});
it.skip('입력�폼에�숫자�값을�입력할�수�있다.', () => { // given const spinbox = mount(<Spinbox />);
// when const input = spinbox.find('input'); input.simulate('change', { target: { value: '999' } });
// then const inputNode = input.getDOMNode(); const actualValue = Number(inputNode.value);
expect(actualValue).toEqual(999);});
import React from 'react';
const Spinbox = () => ( <div> <input type="text" defaultValue="200" /> <button>▲</button> <button>▼</button> </div>);
Spinbox.defaultProps = {};Spinbox.propTypes = {};
export default Spinbox;
import React from ‘react';
class Spinbox extends React.Component { render() { return ( <div> <input type="text" defaultValue="200" /> <button>▲</button> <button>▼</button> </div> ); }}
Spinbox.defaultProps = {};Spinbox.propTypes = {};
export default Spinbox;
it.skip('입력�폼에�숫자�값을�입력할�수�있다.', () => { // given const spinbox = mount(<Spinbox />);
// when const input = spinbox.find('input'); input.simulate('change', { target: { value: '999' } });
// then const inputNode = input.getDOMNode(); const actualValue = Number(inputNode.value);
expect(actualValue).toEqual(999);});
it('입력�폼에�999를�입력할�수�있다.', () => { // given const spinbox = mount(<Spinbox />);
// when const input = spinbox.find('input'); input.simulate('change', { target: { value: '999' } });
// then const inputNode = input.getDOMNode(); const actualValue = Number(inputNode.value);
expect(actualValue).toEqual(999);});
class Spinbox extends React.Component { componentWillMount() { this.state = { value: 200 }; } render() { return ( <div> <input type="text" value={ this.state.value } onChange={ () => { this.setState({ value: 999 }) } } /> <button>▲</button> <button>▼</button> </div> ); }}// 생략 …
class Spinbox extends React.Component { componentWillMount() { this.state = { value: 200 }; } render() { return ( <div> <input type="text" value={ this.state.value } onChange={ () => { this.setState({ value: 999 }) } } /> <button>▲</button> <button>▼</button> </div> ); }}// 생략 …
class Spinbox extends React.Component { componentWillMount() { this.state = { value: 200 }; this.handleChangeInput = this.handleChangeInput.bind(this); }
render() { return ( <div> <input type="text" value={ this.state.value } onChange={ this.handleChangeInput } /> <button>▲</button> <button>▼</button> </div> ); }
handleChangeInput() { this.setState({ value: 999 }); }}
it('스핀박스를�생성할�수�있다.', () => { // given // when const spinbox = mount(<Spinbox />);
// then expect(spinbox).toBeDefined();});
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { … });
it('입력�폼에�999를�입력할�수�있다.', () => { … });
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { … });
it('입력�폼에�999를�입력할�수�있다.', () => { … });
it('입력�폼에�0을�입력할�수�있다.', () => { // given const spinbox = mount(<Spinbox />);
// when const input = spinbox.find('input'); input.simulate('change', { target: { value: '0' } });
// then const inputNode = input.getDOMNode(); const actualValue = Number(inputNode.value);
expect(actualValue).toEqual(0);});
handleChangeInput() { this.setState({ value: 999 });}
handleChangeInput(e) { this.setState({ value: e.target.value });}
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { // given // when const spinbox = mount(<Spinbox />); // 생략…});
it('입력�폼에�999를�입력할�수�있다.', () => { // given const spinbox = mount(<Spinbox />); // 생략…
});
it('입력�폼에�0을�입력할�수�있다.', () => { // given const spinbox = mount(<Spinbox />); // 생략…});
let spinbox;
beforeEach(() => { spinbox = mount(<Spinbox />);});
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { … });
it('입력�폼에�999를�입력할�수�있다.', () => { … });
it('입력�폼에�0을�입력할�수�있다.', () => { … });
it('입력�폼에�999을�입력할�수�있다.', () => { // then const inputNode = input.getDOMNode(); const actualValue = Number(inputNode.value);
expect(actualValue).toEqual(999);});
it('입력�폼에�0을�입력할�수�있다.', () => { // then const inputNode = input.getDOMNode(); const actualValue = Number(inputNode.value);
expect(actualValue).toEqual(0);});
let spinbox;
beforeEach(() => { spinbox = mount(<Spinbox />);});
function getValueFromInputNode(input) { const inputNode = input.getDOMNode(); return Number(inputNode.value);}
it('입력�폼에�999를�입력할�수�있다.', () => { // …생략 // then const actualValue = getValueFromInputNode(input); expect(actualValue).toEqual(999);});
it('입력�폼에�0을�입력할�수�있다.', () => { // …생략 // then const actualValue = getValueFromInputNode(input); expect(actualValue).toEqual(0);});
세�번째�요구사항
기본값은 200
값 입력
증가 버튼을 클릭하여 값이 1 증가
감소 버튼을 클릭하면 값이 1 감소
it('증가�버튼을�클릭하여�값을�1�증가시킬�수�있다.', () => { // given // when const input = spinbox.find('input'); input.simulate('change', { target: { value: '0' } });
// then const actualValue = getValueFromInputNode(input); expect(actualValue).toEqual(0);});
it('증가�버튼을�클릭하여�값을�1�증가시킬�수�있다.', () => { // given const defaultValue = 200;
// when const incrementBtn = spinbox.find('[data-name="increment"]'); incrementBtn.simulate('click');
// then const actualValue = getValueFromInputNode(spinbox.find('input')); expect(actualValue).toEqual(defaultValue + 1);});
render() { return ( <div> <input type="text" value={ this.state.value } onChange={ this.handleChangeInput } /> <button data-name="increment">▲</button> <button>▼</button> </div> );}
render() { return ( <div> <input type="text" value={ this.state.value } onChange={ this.handleChangeInput } /> <button data-name="increment" onClick={ () => { this.setState({ value: 201 }) } }>▲</button> <button>▼</button> </div> );}
render() { return ( <div> <input type="text" value={ this.state.value } onChange={ this.handleChangeInput } /> <button data-name="increment" onClick={ () => { this.setState({ value: 201 }) } }>▲</button> <button>▼</button> </div> );}
componentWillMount() { this.state = { value: 200 }; this.handleChangeInput = this.handleChangeInput.bind(this); this.handleClickIncrement = this.handleClickIncrement.bind(this);}
render() { return ( <div> <input type="text" value={ this.state.value } onChange={ this.handleChangeInput } /> <button data-name="increment" onClick={ this.handleClickIncrement }>▲</button> <button>▼</button> </div> );}
handleClickIncrement() { this.setState({ value: 201 });}
it('증가�버튼을�세�번�클릭하여�값을�3�증가시킬�수�있다.', () => { // given const defaultValue = 200;
// when const incrementBtn = spinbox.find('[data-name="increment"]'); incrementBtn.simulate('click'); incrementBtn.simulate('click'); incrementBtn.simulate('click');
// then const actualValue = getValueFromInputNode(spinbox.find('input')); expect(actualValue).toEqual(defaultValue + 3);});
handleClickIncrement() { this.setState({ value: 201 });}
handleClickIncrement() { this.setState({ value: this.state.value + 1 });}
it('스핀박스를�생성하면�기본값은�200이어야�한다.', () => { … }); it('입력�폼에�999를�입력할�수�있다.', () => { … }); it('입력�폼에�0을�입력할�수�있다.', () => { … }); it('증가�버튼을�클릭하여�값을�1�증가시킬�수�있다.', () => { … }); it('증가�버튼을�세�번�클릭하여�값을�3�증가시킬�수�있다.', () => { … });
describe('기본값�>', () => { it('스핀박스를�생성하면�기본값이�200이어야�한다.', () => { … });});
describe('값�입력�>', () => { it('입력�폼에�999를�입력할�수�있다.', () => { … }); it('입력�폼에�0을�입력할�수�있다.', () => { … });});
describe('값�증가�>', () => { it('증가�버튼을�클릭하여�값을�1�증가시킬�수�있다.', () => { … }); it('증가�버튼을�세�번�클릭하여�값을�3�증가시킬�수�있다.', () => { … });});
describe(‘값�입력�>', () => { it('입력�폼에�999를�입력할�수�있다.', () => { // given // when const input = spinbox.find('input'); // … 생략 });
it('입력�폼에�0을�입력할�수�있다.', () => { // given // when const input = spinbox.find('input'); // … 생략 });});
describe(‘값�입력�>', () => { let input;
beforeEach(() => { input = spinbox.find('input'); });
it('입력�폼에�999를�입력할�수�있다.', () => { … }); it('입력�폼에�0을�입력할�수�있다.', () => { … });});
describe(‘기본값�>', () => { it('스핀박스를�생성하면�기본값이�200이어야�한다.', () => { … });});
describe('값�입력�>', () => { let input;
beforeEach(() => { input = spinbox.find('input'); });
it('입력�폼에�999를�입력할�수�있다.', () => { … }); it('입력�폼에�0을�입력할�수�있다.', () => { … });});
describe('값�증가�>', () => { const defaultValue = 200; let incrementBtn;
beforeEach(() => { incrementBtn = spinbox.find('[data-name="increment"]'); });
it('증가�버튼을�클릭하여�값을�1�증가시킬�수�있다.', () => { … }); it('증가�버튼을�세�번�클릭하여�값을�3�증가시킬�수�있다.', () => { … });});
얻는�것과�잃는�것
변경의�포인트는�집중�
코드는�덜�직관적
it('증가 버튼을 세 번 클릭하여 값을 3 증가시킬 수 있다.', () => { // given // when incrementBtn.simulate('click'); incrementBtn.simulate('click'); incrementBtn.simulate('click');
// then const actualValue = getValueFromInputNode(spinbox.find('input')); expect(actualValue).toEqual(defaultValue + 3);});
뭘까?
네�번째�요구사항
기본값은�200�
값�입력�
증가�버튼을�클릭하여�값이�1�증가�
감소�버튼을�클릭하면�값이�1�감소
describe('값�증가�>', () => { const defaultValue = 200; let incrementBtn;
beforeEach(() => { incrementBtn = spinbox.find('[data-name="increment"]'); });
it('증가�버튼을�클릭하여�값을�1�증가시킬�수�있다.', () => { // given // when incrementBtn.simulate('click');
// then const actualValue = getValueFromInputNode(spinbox.find('input')); expect(actualValue).toEqual(defaultValue + 1); });});
describe('값�감소�>', () => { const defaultValue = 200; let decrementBtn;
beforeEach(() => { decrementBtn = spinbox.find('[data-name="decrement"]'); });
it('감소�버튼을�클릭하여�값을�1�감소시킬�수�있다.', () => { // given // when decrementBtn.simulate('click');
// then const actualValue = getValueFromInputNode(spinbox.find('input')); expect(actualValue).toEqual(defaultValue - 1); });});
class Spinbox extends React.Component { componentWillMount() { this.state = { value: 200 }; this.handleChangeInput = this.handleChangeInput.bind(this); this.handleClickIncrement = this.handleClickIncrement.bind(this); this.handleClickDecrement = this.handleClickDecrement.bind(this); }
render() { … }
handleClickDecrement() { this.setState({ value: this.state.value - 1 }); }
handleClickIncrement() { … } handleChangeInput(e) { … }}
render() { return ( <div> <input type="text" value={ this.state.value } onChange={ this.handleChangeInput } /> <button data-name="increment" onClick={ this.handleClickIncrement }>▲</button> <button>▼</button> </div> );}
render() { return ( <div> <input type="text" value={ this.state.value } onChange={ this.handleChangeInput } /> <button data-name="increment" onClick={ this.handleClickIncrement }>▲</button> <button data-name="decrement" onClick={ this.handleClickDecrement }>▼</button> </div> );}
이걸로�충분할까?
불안하다면�망설이지�말고�
테스트�작성!
it('감소�버튼을�세�번�클릭하여�값을�3�감소시킬�수�있다.', () => { // given // when decrementBtn.simulate('click'); decrementBtn.simulate('click'); decrementBtn.simulate('click');
// then const actualValue = getValueFromInputNode(spinbox.find('input')); expect(actualValue).toEqual(defaultValue - 3);});
끝!
감사합니다!