SlideShare a Scribd company logo
1 of 72
Download to read offline
function Counter()	{
const [count,	setCount] =	useState(0);
const increase	=	()	=> setCount(count + 1);
const decrease	=	()	=> setCount(count - 1);
return (
<div className="simple-counter">
<button className="dec" onClick={decrease}>-</button>
<span className="value">{count}</span>
<button className="inc" onClick={increase}>+</button>
</div>
);
}
it('생성시 버튼과 초기값을 렌더링한다.',	()	=> {
const div =	document.createElement('div');
ReactDOM.render(<Counter	/>,	div);
expect(diffableHTML(div.innerHTML)).toBe(
diffableHTML(`
<div	class="simple-counter">
<button	class="dec">-</button>
<span	class="value">0</span>
<button	class="inc">+</button>
</div>
`)
);
});
it('생성시 버튼과 초기값을 렌더링한다.',	()	=> {
const div =	document.createElement('div');
ReactDOM.render(<Counter	/>,	div);
expect(diffableHTML(div.innerHTML))
.toMatchSnapshot();
});
exports[`생성시 버튼과 초기값을 렌더링한다.	1`]	=	`
"
<div	class="simple-counter">
<button	class="dec">
-
</button>
<span	class="value">
0
</span>
<button	class="inc">
+
</button>
</div>
"
`;
const puppeteer =	require('puppeteer');
const {	percySnapshot }	=	require('@percy/puppeteer')
it('Visual	Testing',	async ()	=> {
const browser =	await puppeteer.launch();
const page =	await browser.newPage();
await page.goto('http://localhost:3000/');		
await percySnapshot(page,	'초기 상태');
const btnInc =	await page.$('button.inc');
await btnInc.click();
await percySnapshot(page,	'+	버튼 클릭 후');
await browser.close();
});
it('초기값은 0이다.',	()	=>	{
const value =	container.querySelector('.value');
expect(value.innerHTML).toBe('0');
});
it('+	버튼 클릭 시 1	증가한다.',	()	=> {
const btnInc =	container.querySelector('button.inc');
const value =	container.querySelector('.value');
btnInc.click();
expect(value.innerHTML).toBe('1');
});
container.querySelector('.box	>	.value');
container.querySelector('span.value');
container.querySelector('.text.white');
container.children[0];
container.querySelector('.count-value');
container.querySelector('[data-testid="count-value"]');
function Counter()	{
//	..
return (
<div className="simple-counter">
<button className="inc"
data-testid="btn-inc"
onClick={ increase }>+</button>
<span className="value"	
data-testid="value">{ count }</span>
<button className="dec"	
data-testid="btn-dec"
onClick={ decrease }>-</button>
</div>
);
}
const getByTestId =	(node,	id)	=>
node.querySelector(`[data-testid="${id}"]`);
it('초기값은 0이다.',	()	=> {
const value = getByTestId(container,	'value');
expect(value.textContent).toBe('0');
});
it('+	버튼 클릭 시 1	증가한다.',	()	=> {
const btnInc =	getByTestId(container,	'btn-inc');
const value =	getByTestId(container,	'value');
btnInc.click();
expect(value.textContent).toBe('1');
});
const puppeteer =	require('puppeteer');
const {	percySnapshot }	=	require('@percy/puppeteer')
it('Visual	Testing',	async ()	=> {
const browser =	await puppeteer.launch();
const page =	await browser.newPage();
await page.goto('http://localhost:3000/');		
await percySnapshot(page,	'초기 상태');
await browser.close();
});
function Counter({	initValue = 0 })	{
const [count,	setCount]	=	useState(initValue);
//	..
}
ReactDOM.render(
<Counter initValue={8}	/>,
document.getElementById('root')
);
ReactDOM.render(
<Counter initValue={76}	/>,
document.getElementById('root')
);
ReactDOM.render(
<Counter initValue={386}	/>,
document.getElementById('root')
);
it('Visual	Testing',	async ()	=> {
const browser =	await puppeteer.launch();
const page =	await browser.newPage();
await page.goto('http://localhost:3000/single-digit');		
await percySnapshot(page,	'1자리 수');
await page.goto('http://localhost:3000/double-digit');		
await percySnapshot(page,	'2자리 수');
await page.goto('http://localhost:3000/triple-digit');		
await percySnapshot(page,	'3자리 수');
await browser.close();
});
import	React from	'react';
import	Counter from	'../src/Counter';
export default {	title:	'Simple	Counter' };
export const singleDigit =	()	=> <Counter initValue={8}	/>;
export const doubleDigit =	()	=> <Counter initValue={76}	/>;
export const tripleDigit =	()	=> <Counter initValue={386}	/>;
export function Counter({	value,	update })	{
const increase	=	()	=> update('inc');
const decrease	=	()	=> update('dec');
return (
<div className="simple-counter">
..
</div>
);
}
it("+	클릭 시 update('inc')를 호춣한다.",	()	=> {
const updateSpy =	jest.fn();
render(<Counter value={10}	update={updateSpy}	/>);
const btnInc =	getByTestId(container,	'btn-inc');
btnInc.click();
expect(updateSpy).toHaveBeenCalledWith('inc');
});
export const UPDATE_REQUEST =	'UPDATE_REQUEST';
export const UPDATE_SUCCESS =	'UPDATE_SUCCESS';
export function updateRequest(value)	{
return	{	type:	UPDATE_REQUEST,	value }
}
export function updateSuccess(value)	{
return	{	type:	UPDATE_SUCCESS,	value }
}
it('UPDATE_REQUEST	액션 생성',	()	=> {
expect(updateRequest(10)).toEqual({
type:	UPDATE_REQUEST,	
value:	10
})
});
it('UPDATE_SUCCESS	액션 생성',	()	=> {
expect(updateSuccess(11)).toEqual({
type:	UPDATE_SUCCESS,	
value:	11
})
});
export function update(type)	{
return async (dispatch,	getState)	=> {
const currValue =	getState();
dispatch(updateRequest(currValue));
const {	data:	{ value }	}	=	
await axios.put(`/${type}`);
dispatch(updateSuccess(value));
};
}
it("update('inc')	비동기 액션",	async ()	=> {
const middlewares =	[ thunk ];
const mockStore =	configureMockStore(middlewares);
const mockAxios =	new MockAdapter(axios);
mockAxios.onPut('/inc').reply(200,	{	value: 11 });
const store =	mockStore(10);
await store.dispatch(update('inc'));
expect(store.getActions()).toEqual([
{	type: UPDATE_REQUEST,	value: 10 },
{	type: UPDATE_SUCCESS,	value: 11 }
]);
});
export function counter(state, action)	{
switch (action.type)	{
case UPDATE_SUCCESS:
return action.value
default:
return state;
}
}
it('UPDATE_SUCCESS	액션 처리',	()	=> {
const action =	{
type:	UPDATE_SUCCESS,
value:	11
};
expect(counter(10,	action)).toBe(11);
})
리듀서
동기 액션
비동기 액션
연결 컴포넌트
컴포넌트
테스트 단위
it('+	버튼을 클릭하면 1	증가한다',	async ()	=> {
const mockAxios =	new MockAdapter(axios);
mockAxios.onPut('/inc').reply(200,	{	value: 11 });
render(<Provider store={store}><Counter /></Provider>);
const btnInc =	getByTestId(container,	'btn-inc');
const value =	getByTestId(container,	'value');
btnInc.click();
await wait(()	=> {
expect(value.textContent).toBe('11');
});
});
it('+	버튼을 클릭하면 1	증가한다.', ()	=> {
cy.server();
cy.route('PUT', '/inc', {	value: 11 });
cy.visit('/');
cy.findByTestId('btn-inc').click();
cy.findByTestId('value').should('have.text', '11');
});
it('+	버튼을 클릭하면 1	증가한다',	async ()	=> {
const mockAxios =	new MockAdapter(axios);
mockAxios.onPut('/inc').reply(200,	{	value: 11 });
render(
<Provider store={store}>
<Counter />
</Provider>
);
getByTestId(container,	'btn-inc').click();
await wait(()	=> {
expect(getByTestId(container,	'value'))
.toHaveText('11');
});
});
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략

More Related Content

What's hot

Адаптация TInyMCE редактора под нужды клиента by Vitaly Nikolaev
Адаптация TInyMCE редактора под нужды клиента by Vitaly NikolaevАдаптация TInyMCE редактора под нужды клиента by Vitaly Nikolaev
Адаптация TInyMCE редактора под нужды клиента by Vitaly NikolaevWordCamp Kyiv
 
Drupal Cms Prezentace
Drupal Cms PrezentaceDrupal Cms Prezentace
Drupal Cms PrezentaceTomáš Kafka
 
How to make a simple calculator
How to make a simple calculatorHow to make a simple calculator
How to make a simple calculatorNida Shafiyanti
 
Як досвід компанії перетворився на фреймворк
Як досвід компанії перетворився на фреймворкЯк досвід компанії перетворився на фреймворк
Як досвід компанії перетворився на фреймворкShtrih Sruleg
 
Mantto con vb2010
Mantto con vb2010Mantto con vb2010
Mantto con vb2010tihuilo
 
JAVA Program in NetBeans
JAVA Program in NetBeansJAVA Program in NetBeans
JAVA Program in NetBeansHimanshiSingh71
 
Semana 12 interfaces gráficas de usuario
Semana 12   interfaces gráficas de usuarioSemana 12   interfaces gráficas de usuario
Semana 12 interfaces gráficas de usuarioTerryJoss
 
JQuery应用开发
JQuery应用开发JQuery应用开发
JQuery应用开发chevionlu
 
Palestra PythonBrasil[8]
Palestra PythonBrasil[8]Palestra PythonBrasil[8]
Palestra PythonBrasil[8]Thiago Da Silva
 
Android Fast Track CRUD Android PHP MySql
Android Fast Track CRUD Android PHP MySqlAndroid Fast Track CRUD Android PHP MySql
Android Fast Track CRUD Android PHP MySqlAgus Haryanto
 
Ejercicios resueltos Practica 4 informatica II
Ejercicios resueltos Practica 4 informatica IIEjercicios resueltos Practica 4 informatica II
Ejercicios resueltos Practica 4 informatica IIAlvin Jacobs
 
Javascript and jQuery for Mobile
Javascript and jQuery for MobileJavascript and jQuery for Mobile
Javascript and jQuery for MobileIvano Malavolta
 
Device Orientation & WebSocket API
Device Orientation & WebSocket APIDevice Orientation & WebSocket API
Device Orientation & WebSocket APIComparto Web
 
modern javascript, unobtrusive javascript, jquery
modern javascript, unobtrusive javascript, jquerymodern javascript, unobtrusive javascript, jquery
modern javascript, unobtrusive javascript, jqueryAdam Zygadlewicz
 
アプリ設定の保存をシンプルに
アプリ設定の保存をシンプルにアプリ設定の保存をシンプルに
アプリ設定の保存をシンプルにsusan335
 

What's hot (20)

Адаптация TInyMCE редактора под нужды клиента by Vitaly Nikolaev
Адаптация TInyMCE редактора под нужды клиента by Vitaly NikolaevАдаптация TInyMCE редактора под нужды клиента by Vitaly Nikolaev
Адаптация TInyMCE редактора под нужды клиента by Vitaly Nikolaev
 
Drupal Cms Prezentace
Drupal Cms PrezentaceDrupal Cms Prezentace
Drupal Cms Prezentace
 
How to make a simple calculator
How to make a simple calculatorHow to make a simple calculator
How to make a simple calculator
 
Simulacion - Algoritmo congruencial cuadratico
Simulacion - Algoritmo congruencial cuadraticoSimulacion - Algoritmo congruencial cuadratico
Simulacion - Algoritmo congruencial cuadratico
 
Як досвід компанії перетворився на фреймворк
Як досвід компанії перетворився на фреймворкЯк досвід компанії перетворився на фреймворк
Як досвід компанії перетворився на фреймворк
 
Test
TestTest
Test
 
Mantto con vb2010
Mantto con vb2010Mantto con vb2010
Mantto con vb2010
 
JAVA Program in NetBeans
JAVA Program in NetBeansJAVA Program in NetBeans
JAVA Program in NetBeans
 
Semana 12 interfaces gráficas de usuario
Semana 12   interfaces gráficas de usuarioSemana 12   interfaces gráficas de usuario
Semana 12 interfaces gráficas de usuario
 
Blog 3
Blog 3Blog 3
Blog 3
 
JQuery应用开发
JQuery应用开发JQuery应用开发
JQuery应用开发
 
Macro
MacroMacro
Macro
 
Palestra PythonBrasil[8]
Palestra PythonBrasil[8]Palestra PythonBrasil[8]
Palestra PythonBrasil[8]
 
Android Fast Track CRUD Android PHP MySql
Android Fast Track CRUD Android PHP MySqlAndroid Fast Track CRUD Android PHP MySql
Android Fast Track CRUD Android PHP MySql
 
Ejercicios resueltos Practica 4 informatica II
Ejercicios resueltos Practica 4 informatica IIEjercicios resueltos Practica 4 informatica II
Ejercicios resueltos Practica 4 informatica II
 
Javascript and jQuery for Mobile
Javascript and jQuery for MobileJavascript and jQuery for Mobile
Javascript and jQuery for Mobile
 
Device Orientation & WebSocket API
Device Orientation & WebSocket APIDevice Orientation & WebSocket API
Device Orientation & WebSocket API
 
modern javascript, unobtrusive javascript, jquery
modern javascript, unobtrusive javascript, jquerymodern javascript, unobtrusive javascript, jquery
modern javascript, unobtrusive javascript, jquery
 
Blog 4
Blog 4Blog 4
Blog 4
 
アプリ設定の保存をシンプルに
アプリ設定の保存をシンプルにアプリ設定の保存をシンプルに
アプリ設定の保存をシンプルに
 

More from NHN FORWARD

[2019] 패션 시소러스 기반 상품 특징 분석 시스템
[2019] 패션 시소러스 기반 상품 특징 분석 시스템[2019] 패션 시소러스 기반 상품 특징 분석 시스템
[2019] 패션 시소러스 기반 상품 특징 분석 시스템NHN FORWARD
 
[2019] 스몰 스텝: Android 렛츠기릿!
[2019] 스몰 스텝: Android 렛츠기릿![2019] 스몰 스텝: Android 렛츠기릿!
[2019] 스몰 스텝: Android 렛츠기릿!NHN FORWARD
 
딥러닝, 야 너도 할 수 있어(feat. PyTorch)
딥러닝, 야 너도 할 수 있어(feat. PyTorch)딥러닝, 야 너도 할 수 있어(feat. PyTorch)
딥러닝, 야 너도 할 수 있어(feat. PyTorch)NHN FORWARD
 
NHN 베이스캠프: 신입사원들은 무엇을 배우나요?
NHN 베이스캠프: 신입사원들은 무엇을 배우나요?NHN 베이스캠프: 신입사원들은 무엇을 배우나요?
NHN 베이스캠프: 신입사원들은 무엇을 배우나요?NHN FORWARD
 
[2019] GIF 스티커 만들기: 스파인 2D를 이용한 움직이는 스티커 만들기
[2019] GIF 스티커 만들기: 스파인 2D를 이용한 움직이는 스티커 만들기[2019] GIF 스티커 만들기: 스파인 2D를 이용한 움직이는 스티커 만들기
[2019] GIF 스티커 만들기: 스파인 2D를 이용한 움직이는 스티커 만들기NHN FORWARD
 
[2019] 전기 먹는 하마의 다이어트 성공기 클라우드 데이터 센터의 에너지 절감 노력과 사례
[2019] 전기 먹는 하마의 다이어트 성공기   클라우드 데이터 센터의 에너지 절감 노력과 사례[2019] 전기 먹는 하마의 다이어트 성공기   클라우드 데이터 센터의 에너지 절감 노력과 사례
[2019] 전기 먹는 하마의 다이어트 성공기 클라우드 데이터 센터의 에너지 절감 노력과 사례NHN FORWARD
 
[2019] 스몰 스텝: Dooray!를 이용한 업무 효율화/자동화(고객문의 시스템 구축)
[2019] 스몰 스텝: Dooray!를 이용한 업무 효율화/자동화(고객문의 시스템 구축)[2019] 스몰 스텝: Dooray!를 이용한 업무 효율화/자동화(고객문의 시스템 구축)
[2019] 스몰 스텝: Dooray!를 이용한 업무 효율화/자동화(고객문의 시스템 구축)NHN FORWARD
 
[2019] 아직도 돈 주고 DB 쓰나요? for Developer
[2019] 아직도 돈 주고 DB 쓰나요? for Developer[2019] 아직도 돈 주고 DB 쓰나요? for Developer
[2019] 아직도 돈 주고 DB 쓰나요? for DeveloperNHN FORWARD
 
[2019] 아직도 돈 주고 DB 쓰나요 for DBA
[2019] 아직도 돈 주고 DB 쓰나요 for DBA[2019] 아직도 돈 주고 DB 쓰나요 for DBA
[2019] 아직도 돈 주고 DB 쓰나요 for DBANHN FORWARD
 
[2019] 비주얼 브랜딩: Basic system
[2019] 비주얼 브랜딩: Basic system[2019] 비주얼 브랜딩: Basic system
[2019] 비주얼 브랜딩: Basic systemNHN FORWARD
 
[2019] PAYCO 매거진 서버 Kotlin 적용기
[2019] PAYCO 매거진 서버 Kotlin 적용기[2019] PAYCO 매거진 서버 Kotlin 적용기
[2019] PAYCO 매거진 서버 Kotlin 적용기NHN FORWARD
 
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)NHN FORWARD
 
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기NHN FORWARD
 
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기NHN FORWARD
 
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례NHN FORWARD
 
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자NHN FORWARD
 
[2019] 200만 동접 게임을 위한 MySQL 샤딩
[2019] 200만 동접 게임을 위한 MySQL 샤딩[2019] 200만 동접 게임을 위한 MySQL 샤딩
[2019] 200만 동접 게임을 위한 MySQL 샤딩NHN FORWARD
 
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션NHN FORWARD
 
[2019] 글로벌 게임 서비스 노하우
[2019] 글로벌 게임 서비스 노하우[2019] 글로벌 게임 서비스 노하우
[2019] 글로벌 게임 서비스 노하우NHN FORWARD
 
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인NHN FORWARD
 

More from NHN FORWARD (20)

[2019] 패션 시소러스 기반 상품 특징 분석 시스템
[2019] 패션 시소러스 기반 상품 특징 분석 시스템[2019] 패션 시소러스 기반 상품 특징 분석 시스템
[2019] 패션 시소러스 기반 상품 특징 분석 시스템
 
[2019] 스몰 스텝: Android 렛츠기릿!
[2019] 스몰 스텝: Android 렛츠기릿![2019] 스몰 스텝: Android 렛츠기릿!
[2019] 스몰 스텝: Android 렛츠기릿!
 
딥러닝, 야 너도 할 수 있어(feat. PyTorch)
딥러닝, 야 너도 할 수 있어(feat. PyTorch)딥러닝, 야 너도 할 수 있어(feat. PyTorch)
딥러닝, 야 너도 할 수 있어(feat. PyTorch)
 
NHN 베이스캠프: 신입사원들은 무엇을 배우나요?
NHN 베이스캠프: 신입사원들은 무엇을 배우나요?NHN 베이스캠프: 신입사원들은 무엇을 배우나요?
NHN 베이스캠프: 신입사원들은 무엇을 배우나요?
 
[2019] GIF 스티커 만들기: 스파인 2D를 이용한 움직이는 스티커 만들기
[2019] GIF 스티커 만들기: 스파인 2D를 이용한 움직이는 스티커 만들기[2019] GIF 스티커 만들기: 스파인 2D를 이용한 움직이는 스티커 만들기
[2019] GIF 스티커 만들기: 스파인 2D를 이용한 움직이는 스티커 만들기
 
[2019] 전기 먹는 하마의 다이어트 성공기 클라우드 데이터 센터의 에너지 절감 노력과 사례
[2019] 전기 먹는 하마의 다이어트 성공기   클라우드 데이터 센터의 에너지 절감 노력과 사례[2019] 전기 먹는 하마의 다이어트 성공기   클라우드 데이터 센터의 에너지 절감 노력과 사례
[2019] 전기 먹는 하마의 다이어트 성공기 클라우드 데이터 센터의 에너지 절감 노력과 사례
 
[2019] 스몰 스텝: Dooray!를 이용한 업무 효율화/자동화(고객문의 시스템 구축)
[2019] 스몰 스텝: Dooray!를 이용한 업무 효율화/자동화(고객문의 시스템 구축)[2019] 스몰 스텝: Dooray!를 이용한 업무 효율화/자동화(고객문의 시스템 구축)
[2019] 스몰 스텝: Dooray!를 이용한 업무 효율화/자동화(고객문의 시스템 구축)
 
[2019] 아직도 돈 주고 DB 쓰나요? for Developer
[2019] 아직도 돈 주고 DB 쓰나요? for Developer[2019] 아직도 돈 주고 DB 쓰나요? for Developer
[2019] 아직도 돈 주고 DB 쓰나요? for Developer
 
[2019] 아직도 돈 주고 DB 쓰나요 for DBA
[2019] 아직도 돈 주고 DB 쓰나요 for DBA[2019] 아직도 돈 주고 DB 쓰나요 for DBA
[2019] 아직도 돈 주고 DB 쓰나요 for DBA
 
[2019] 비주얼 브랜딩: Basic system
[2019] 비주얼 브랜딩: Basic system[2019] 비주얼 브랜딩: Basic system
[2019] 비주얼 브랜딩: Basic system
 
[2019] PAYCO 매거진 서버 Kotlin 적용기
[2019] PAYCO 매거진 서버 Kotlin 적용기[2019] PAYCO 매거진 서버 Kotlin 적용기
[2019] PAYCO 매거진 서버 Kotlin 적용기
 
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
 
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
 
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
 
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
 
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
 
[2019] 200만 동접 게임을 위한 MySQL 샤딩
[2019] 200만 동접 게임을 위한 MySQL 샤딩[2019] 200만 동접 게임을 위한 MySQL 샤딩
[2019] 200만 동접 게임을 위한 MySQL 샤딩
 
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
 
[2019] 글로벌 게임 서비스 노하우
[2019] 글로벌 게임 서비스 노하우[2019] 글로벌 게임 서비스 노하우
[2019] 글로벌 게임 서비스 노하우
 
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
 

[2019] 실용적인 프런트엔드 테스트 전략

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9. function Counter() { const [count, setCount] = useState(0); const increase = () => setCount(count + 1); const decrease = () => setCount(count - 1); return ( <div className="simple-counter"> <button className="dec" onClick={decrease}>-</button> <span className="value">{count}</span> <button className="inc" onClick={increase}>+</button> </div> ); }
  • 10. it('생성시 버튼과 초기값을 렌더링한다.', () => { const div = document.createElement('div'); ReactDOM.render(<Counter />, div); expect(diffableHTML(div.innerHTML)).toBe( diffableHTML(` <div class="simple-counter"> <button class="dec">-</button> <span class="value">0</span> <button class="inc">+</button> </div> `) ); });
  • 11. it('생성시 버튼과 초기값을 렌더링한다.', () => { const div = document.createElement('div'); ReactDOM.render(<Counter />, div); expect(diffableHTML(div.innerHTML)) .toMatchSnapshot(); }); exports[`생성시 버튼과 초기값을 렌더링한다. 1`] = ` " <div class="simple-counter"> <button class="dec"> - </button> <span class="value"> 0 </span> <button class="inc"> + </button> </div> " `;
  • 12.
  • 13.
  • 14.
  • 15.
  • 16. const puppeteer = require('puppeteer'); const { percySnapshot } = require('@percy/puppeteer') it('Visual Testing', async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://localhost:3000/'); await percySnapshot(page, '초기 상태'); const btnInc = await page.$('button.inc'); await btnInc.click(); await percySnapshot(page, '+ 버튼 클릭 후'); await browser.close(); });
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22. it('초기값은 0이다.', () => { const value = container.querySelector('.value'); expect(value.innerHTML).toBe('0'); }); it('+ 버튼 클릭 시 1 증가한다.', () => { const btnInc = container.querySelector('button.inc'); const value = container.querySelector('.value'); btnInc.click(); expect(value.innerHTML).toBe('1'); });
  • 24. function Counter() { // .. return ( <div className="simple-counter"> <button className="inc" data-testid="btn-inc" onClick={ increase }>+</button> <span className="value" data-testid="value">{ count }</span> <button className="dec" data-testid="btn-dec" onClick={ decrease }>-</button> </div> ); } const getByTestId = (node, id) => node.querySelector(`[data-testid="${id}"]`); it('초기값은 0이다.', () => { const value = getByTestId(container, 'value'); expect(value.textContent).toBe('0'); }); it('+ 버튼 클릭 시 1 증가한다.', () => { const btnInc = getByTestId(container, 'btn-inc'); const value = getByTestId(container, 'value'); btnInc.click(); expect(value.textContent).toBe('1'); });
  • 25. const puppeteer = require('puppeteer'); const { percySnapshot } = require('@percy/puppeteer') it('Visual Testing', async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://localhost:3000/'); await percySnapshot(page, '초기 상태'); await browser.close(); });
  • 26.
  • 27. function Counter({ initValue = 0 }) { const [count, setCount] = useState(initValue); // .. } ReactDOM.render( <Counter initValue={8} />, document.getElementById('root') ); ReactDOM.render( <Counter initValue={76} />, document.getElementById('root') ); ReactDOM.render( <Counter initValue={386} />, document.getElementById('root') );
  • 28. it('Visual Testing', async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://localhost:3000/single-digit'); await percySnapshot(page, '1자리 수'); await page.goto('http://localhost:3000/double-digit'); await percySnapshot(page, '2자리 수'); await page.goto('http://localhost:3000/triple-digit'); await percySnapshot(page, '3자리 수'); await browser.close(); });
  • 29.
  • 30.
  • 31. import React from 'react'; import Counter from '../src/Counter'; export default { title: 'Simple Counter' }; export const singleDigit = () => <Counter initValue={8} />; export const doubleDigit = () => <Counter initValue={76} />; export const tripleDigit = () => <Counter initValue={386} />;
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43. export function Counter({ value, update }) { const increase = () => update('inc'); const decrease = () => update('dec'); return ( <div className="simple-counter"> .. </div> ); } it("+ 클릭 시 update('inc')를 호춣한다.", () => { const updateSpy = jest.fn(); render(<Counter value={10} update={updateSpy} />); const btnInc = getByTestId(container, 'btn-inc'); btnInc.click(); expect(updateSpy).toHaveBeenCalledWith('inc'); });
  • 44. export const UPDATE_REQUEST = 'UPDATE_REQUEST'; export const UPDATE_SUCCESS = 'UPDATE_SUCCESS'; export function updateRequest(value) { return { type: UPDATE_REQUEST, value } } export function updateSuccess(value) { return { type: UPDATE_SUCCESS, value } } it('UPDATE_REQUEST 액션 생성', () => { expect(updateRequest(10)).toEqual({ type: UPDATE_REQUEST, value: 10 }) }); it('UPDATE_SUCCESS 액션 생성', () => { expect(updateSuccess(11)).toEqual({ type: UPDATE_SUCCESS, value: 11 }) });
  • 45. export function update(type) { return async (dispatch, getState) => { const currValue = getState(); dispatch(updateRequest(currValue)); const { data: { value } } = await axios.put(`/${type}`); dispatch(updateSuccess(value)); }; } it("update('inc') 비동기 액션", async () => { const middlewares = [ thunk ]; const mockStore = configureMockStore(middlewares); const mockAxios = new MockAdapter(axios); mockAxios.onPut('/inc').reply(200, { value: 11 }); const store = mockStore(10); await store.dispatch(update('inc')); expect(store.getActions()).toEqual([ { type: UPDATE_REQUEST, value: 10 }, { type: UPDATE_SUCCESS, value: 11 } ]); });
  • 46. export function counter(state, action) { switch (action.type) { case UPDATE_SUCCESS: return action.value default: return state; } } it('UPDATE_SUCCESS 액션 처리', () => { const action = { type: UPDATE_SUCCESS, value: 11 }; expect(counter(10, action)).toBe(11); })
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52. 리듀서 동기 액션 비동기 액션 연결 컴포넌트 컴포넌트 테스트 단위
  • 53. it('+ 버튼을 클릭하면 1 증가한다', async () => { const mockAxios = new MockAdapter(axios); mockAxios.onPut('/inc').reply(200, { value: 11 }); render(<Provider store={store}><Counter /></Provider>); const btnInc = getByTestId(container, 'btn-inc'); const value = getByTestId(container, 'value'); btnInc.click(); await wait(() => { expect(value.textContent).toBe('11'); }); });
  • 54.
  • 55.
  • 56.
  • 57.
  • 58. it('+ 버튼을 클릭하면 1 증가한다.', () => { cy.server(); cy.route('PUT', '/inc', { value: 11 }); cy.visit('/'); cy.findByTestId('btn-inc').click(); cy.findByTestId('value').should('have.text', '11'); }); it('+ 버튼을 클릭하면 1 증가한다', async () => { const mockAxios = new MockAdapter(axios); mockAxios.onPut('/inc').reply(200, { value: 11 }); render( <Provider store={store}> <Counter /> </Provider> ); getByTestId(container, 'btn-inc').click(); await wait(() => { expect(getByTestId(container, 'value')) .toHaveText('11'); }); });