YesCoding

search:

[React] React-Testing-Library λ₯Ό μ΄μš©ν•œ TDD

νšŒμ‚¬μ—μ„œ TDD μŠ€ν„°λ””κ°€ μ—΄λ €μ„œ, ν”„λ‘ νŠΈμ—”λ“œ TDD 유투브 μ˜μƒ, λΈ”λ‘œκ·Έ 등을 곡뢀λ₯Ό ν•˜κ³  직접 ν•œ 번 TDD λ₯Ό ν•΄λ΄€λ‹€. μ˜μƒκ³Ό λΈ”λ‘œκ·Έ 등을 톡해 μ•Œκ²Œλœ TDD 의 μž₯단점은 λ‹€μŒκ³Ό κ°™λ‹€.

TDD μž₯점

  1. ν”Όλ“œλ°± 루프 ν…ŒμŠ€νŠΈ 결과만 보면 λΈŒλΌμš°μ € 직접 확인할 일이 적어진닀.

  2. 관심사 기반 개발이 κ°€λŠ₯ν•΄ 진닀. 1). 관심사 λΆ„λ₯˜λ₯Ό 잘 ν•˜λ©΄ 앱이 μž‘μ•„μ§„λ‹€. 2). μ„œλ‘œ μƒνƒœμ— λŒ€ν•œ 관심을 λΆ„μ‚°μ‹œμΌœμ•Ό ν•œλ‹€.

  3. μ˜μ‘΄μ„±μ΄ 적어진닀. μš”κ΅¬μ‚¬ν•­μ„ λͺ…ν™•ν•˜κ²Œ ν•  수 μžˆλ‹€. λ‚˜ λŒ€μ‹ μ— λ‚΄κ°€ μƒˆλ‘œ μΆ”κ°€ν•œ κΈ°λŠ₯이 λ‹€λ₯Έ κΈ°λŠ₯에도 λ‚˜ λͺ¨λ₯΄κ²Œ 영ν–₯을 쀄 수 μžˆλŠ”λ°, TDD λ₯Ό ν•˜λ©΄ 그것을 막아쀄 수 μžˆλ‹€.

  4. μ˜€λ²„ μ—”μ§€λ‹ˆμ–΄λ§μ„ 막아쀄 수 μžˆλ‹€.

  5. λ¬Έμ„œ 역할을 ν•œλ‹€.

TDD 단점

  1. μ½”λ“œ 생산 μ‹œκ°„μ΄ 길어진닀. (λ‚΄κ°€ λͺ¨λ₯΄λŠ” TDD 단점이 μžˆμ„ 수 μžˆλ‹€.)

μž₯단점을 κ³΅λΆ€ν•˜λ©΄μ„œ λŠλ‚€ 것은, λͺ¨λ“  μƒν™©μ—μ„œ TDDκ°€ 정닡은 μ•„λ‹ˆλΌλŠ” 것이닀. λ‚΄κ°€ μƒκ°ν–ˆμ„ λ•Œ λ„μž…ν•  λ•Œ κ³ λ €ν•΄μ•Ό ν•  μ€‘μš”ν•œ 기쀀은 νšŒμ‚¬μ˜ λΉ„μ¦ˆλ‹ˆμŠ€ 상황인 것 κ°™λ‹€.

πŸ’‘πŸ’‘πŸ’‘πŸ‘©πŸ»β€πŸ’»πŸ‘©πŸ»β€πŸ’»

λΉ„μ¦ˆλ‹ˆμŠ€ 단계가 ν”„λ‘œν† νƒ€μž… μž‘μ„±μ΄ μ€‘μš”ν•œ λ‹¨κ³„λΌλ˜μ§€, 결과물을 빨리 λ‚΄μ•Ό ν•˜λŠ” 상황이라면 TDD λ„μž… ν•˜λŠ” 것은 μ˜€λ²„ 일 수 μžˆλ‹€. 그런데 λ§€μΆœμ„ λ‚΄λŠ” μ£Όμš” κΈ°λŠ₯듀이 μ•ˆμ •ν™”λœ μ„±μˆ™λ‹¨κ³„μ— μžˆλŠ” λ‹¨κ³„λ‚˜, κΈ°μ‘΄ μ„œλ²„κ°€ 있고 μƒˆλ‘œμš΄ μ„œλ²„λ‘œ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ν•˜λŠ” λ‹¨κ³„μ—μ„œλŠ” TDD λ„μž…μ„ 톡해 λ‘œμ§μ„ μž¬κ²€μ¦ν•˜κ³  μ»΄ν¬λ„ŒνŠΈλ“€μ„ 잘게 μͺΌκ°œλ©΄μ„œ κΈ°λŠ₯의 λͺ©μ μ„ λͺ…ν™•νžˆ ν•  수 μžˆμ„ 것 κ°™λ‹€.

TDD, 직접 ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

κΆκΈˆν•΄μ„œ TDD 둜 μ•„μ£Ό μž‘μ€ κ°œλ°œμ„ ν•΄λ³΄μ•˜λ‹€. μš”μƒˆ Canvas API 에 관심이 μƒκ²¨μ„œ, νŠœν† λ¦¬μ–Όμ„ μ‹œμž‘ν–ˆλŠ”λ°, 이걸 λ¦¬μ•‘νŠΈλ‘œ κ΅¬ν˜„ν•˜λŠ” 것을 TDD 둜 ν•΄λ³΄μ•˜λ‹€.

React-Testing-Library, CRA λ„Œ λͺ»ν•˜λŠ”κ²Œ μ—†κ΅¬λ‚˜

κ°œλ°œμ„ μ‹œμž‘ν•˜κΈ° 전에 μš°μ„  RTL (React-Testing-Library) 에 λŒ€ν•΄ μ’€ 더 μ•Œκ³  μ‹Άμ—ˆλ‹€. RTL 은 Kent C. Dodds κ°€ κ°œλ°œν•œ React testing tool 둜, airbnb 의 enzyme 의 λŒ€μ²΄μž¬λ‘œ μ•Œλ €μ§€κ³  μžˆλ‹€κ³  ν•œλ‹€.

RTL vs Enzyme

So rather than dealing with instances of rendered React components, your tests will work with actual DOM nodes.

β‡’ 곡식 λ¬Έμ„œλŠ” DOM Testing library μž„μ„ κ°•μ‘°ν•˜κ³  μžˆλ‹€. React component μΈμŠ€ν„΄μŠ€κ°€ μ•„λ‹ˆλΌ, ν…ŒμŠ€νŠΈλŠ” μ‹€μ œ λΈŒλΌμš°μ €μ— 그렀진 DOM nodes λ₯Ό μ΄μš©ν•΄ 이루어진닀. RTL λΌμ΄λΈŒλŸ¬λ¦¬λŠ” μ‹€μ œ μœ μ €μ˜ 행동과 λ™μΌν•˜κ²Œ DOM 을 μ°Ύμ•„λ‹€λ‹ˆλ©° μ˜λ„λŒ€λ‘œ κ·Έλ €μ‘ŒλŠ” 지λ₯Ό ν™•μΈν•œλ‹€.

Enzyme gives React developers utilities to test internals of React components, React Testing Library takes a step back and questions us "how to test React components to get full confidence in our React components": Rather than testing a component's implementation details, React Testing Library puts >the developer in the shoes of an end user of an React application.

β‡’ Enzyme 이 React component κ΄€μ μ—μ„œ 잘 싀행이 λ˜μ—ˆλŠ” 지λ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” 툴이라면, RTL 은 μ—”λ“œμœ μ € μž…μž₯μ—μ„œ 개발자의 μ˜λ„λŒ€λ‘œ 화면이 λ³΄μ΄λŠ” 지λ₯Ό ν…ŒμŠ€νŠΈ ν•˜λŠ” νˆ΄μ΄λ‹€.

RTL vs Jest

λ°˜λ©΄μ—, Jest λŠ” RTL κ³Ό 보완관계이닀. React λ₯Ό μ•ˆ 써봀닀고 해도 javascript application μ—μ„œ ν…ŒμŠ€νŠΈλ₯Ό ν•΄λ³Έ μ‚¬λžŒλ“€ 쀑 λŒ€λΆ€λΆ„μ€ μ•„λž˜μ™€ 같은 μ½”λ“œλ₯Ό λ³Έ 적이 μžˆμ„ 것이닀.

// test suite describe('true is truthy and false is falsy', () => { // test case 1 test('true is truthy', () => { expect(true).toBe(true); }); // test case 2 test('false is falsy', () => { expect(false).toBe(false); }); });

μœ„ μ½”λ“œλ₯Ό test-suit μ΄λΌν•œλ‹€. ν…ŒμŠ€νŠΈν•˜λŠ” κ°œμš”λ₯Ό describe ν•΄μ£Όκ³ , ν…ŒμŠ€νŠΈν•΄μ„œ λ‚˜μ˜€λŠ” κΈ°λŒ€κ°’μ„ λΉ„κ΅ν•΄μ£ΌλŠ” μ½”λ“œλ₯Ό λ‹΄λŠ”λ‹€.

μ˜€μΌ€μ΄.. 이제 μ–΄λŠμ •λ„ RTL μ•„μ΄λ””μ–΄λŠ” 이해가 λ˜μ—ˆλ‹€. κ°„λ‹¨νžˆ 빨간색 μ‚¬κ°ν˜•μ„ κ·Έλ¦¬λŠ” 걸둜 첫 TDD λ₯Ό ν•΄λ³Έλ‹€.

직접 해보기

create-react-app ν•˜λ©΄ μžλ™μœΌλ‘œ

@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10",

μ„Έ κ°œκ°€ μ„€μΉ˜ λ˜μ–΄ μžˆλ‹€. μš”μ¦˜ 세상 νŽΈν•˜λ‹€ πŸ‘ 이미 create-react-app 을 톡해 λ§Œλ“€μ–΄ μ“°κ³  있던 React λ ˆν¬κ°€ μžˆμ–΄μ„œ, 거기에 RTL 을 μ„€μΉ˜ν•˜λ €κ³  λ³΄λ‹ˆ 이미 default 둜 κΉ”λ €μ Έ μžˆμ—ˆλ‹€.

canvas.test.js νŒŒμΌμ„ λ¨Όμ € λ§Œλ“ λ‹€.

import React from 'react'; import { render } from '@testing-library/react'; import RedRectangle from './RedRectangle'; describe('RedRectangle', () => { test('renders RedRectangle', () => { render(<RedRectangle />); }) })

canvas_test1

ν…ŒμŠ€νŠΈλ₯Ό 돌리면 μ•„λž˜κ°™μ€ μ—λŸ¬κ°€ λœ¬λ‹€. μ™œλƒ? RedRectangle μ»΄ν¬λ„ŒνŠΈλ₯Ό 아직 μ•ˆ λ§Œλ“€μ—ˆκΈ° λ•Œλ¬Έμ΄λ‹€.

canvas_test2

import React from 'react'; const RedRectangle = () => { return ( <div></div> ) }; export default RedRectangle;

λ§Œλ“€κ³ , λ‹€μ‹œ ν…ŒμŠ€νŠΈ.

canvas_test3

ν…ŒμŠ€νŠΈ ν†΅κ³Όν•˜μ—¬ λ‹€μŒμ—” canvas μ—˜λ¦¬λ¨ΌνŠΈλ₯Ό λ„£κΈ° 전에, canvas element κ°€ DOM 에 κ·Έλ €μ‘ŒλŠ”μ§€ ν™•μΈν•˜λŠ” ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ§ λ‹€.

import { render, screen } from '@testing-library/react'; import RedRectangle from './RedRectangle'; describe('RedRectangle', () => { test('renders RedRectangle', () => { render(<RedRectangle />); }) test('render canvas element', () => { screen.getByTestId('canvas'); // 화면에 dataset id = cavas 인 element κ°€ μžˆλŠ”μ§€ 확인해쀀닀. }) })

κ²°κ³ΌλŠ” λ‹Ήμ—°νžˆ FAIL (아직 μ•ˆ λ§Œλ“€μ–΄μ€¬μœΌλ‹ˆκΉŒ)

canvas_test4

κ·Έλž˜μ„œ Canvas μ»΄ν¬λ„ŒνŠΈλ₯Ό μƒμ„±ν–ˆλ‹€.

import React, {useRef, useEffect } from 'react'; const Canvas = ({draw, height, width}) => { const canvas = useRef(null); useEffect(() => { const context = canvas.current.getContext('2d'); draw(context); }); return ( <canvas ref={canvas} height={height} width={width} /> ); }; export default Canvas;

그리고 μœ„ Canvas μ»΄ν¬λ„ŒνŠΈλ₯Ό import ν•΄μ„œ

import React from 'react'; import Canvas from './Canvas'; const RedRectangle = () => { const draw = (context) => { // context.fillRect(x, y, width, height) -> x, y λŠ” μ‹œμž‘ μ’Œν‘œ. context.fillStyle = "red"; context.fillRect(100, 100, 400, 300); } return ( <Canvas draw={draw} width={800} height={600} /> ) }; export default RedRectangle;

λΉ¨κ°„ μ‚¬κ°ν˜•μ„ κ·Έλ €λ΄€λŠ”λ°,

canvas_test5

μ΄λ ‡κ²Œ λΈŒλΌμš°μ €μ—λŠ” 잘 λ Œλ”λ§ λ˜μ§€λ§Œ, ν…ŒμŠ€νŠΈλŠ” 깨진닀.

canvas_test6

canvas.test.js μ—μ„œ context λ₯Ό null 둜 λ°›λŠ” 것이 λ¬Έμ œλ‹€.

npm start λ₯Ό ν•΄μ„œ context λ₯Ό 찍어보면 μ•„λž˜μ²˜λŸΌ 잘 λ‚˜μ˜¨λ‹€. μ™œ ν…ŒμŠ€νŠΈμ—μ„œλŠ” λͺ» λ°›μ„κΉŒ?

canvas_test7

원인은 canvas api λŠ” λΈŒλΌμš°μ € dom 에 κ·Έλ €μ§€λŠ” 것인데, RTL κ³Ό jest λŠ” jsdom μ΄λΌλŠ” κ°€μ§œ 돔 (ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ λΆ€λΆ„λ§Œ μΆ”λ €μ˜€λŠ”) 을 가지고 ν…ŒμŠ€νŠΈλ₯Ό ν•˜μ§€, μ§„μ§œ λΈŒλΌμš°μ €μ™€λŠ” 상관이 μ—†κΈ° λ•Œλ¬Έμ— Canvas element λ₯Ό λͺ» μ°ΎλŠ” κ²ƒμ΄μ—ˆλ‹€.

κ²€μƒ‰ν•΄λ³΄λ‹ˆ, canvas μ—˜λ¦¬λ¨ΌνŠΈ ν…ŒμŠ€νŠΈ 라이브러리둜 https://www.npmjs.com/package/jest-canvas-mock κ°€ μžˆμ—ˆλ‹€. 그런데 jest-canvas-mock 은 λ‚΄ λ¦¬μ•‘νŠΈ 앱인 CRA react-script 4+ λ²„μ „μ—μ„œ μΆ©λŒλ¬Έμ œκ°€ μžˆμ–΄μ„œ, node μ—μ„œ canvas 돔에 μ ‘κ·Όν•˜κ²Œ ν•΄μ£ΌλŠ” node-canvas 라이브러리λ₯Ό μ„€μΉ˜ν–ˆλ”λ‹ˆ ν…ŒμŠ€νŠΈκ°€ μ„±κ³΅ν•˜μ˜€λ‹€. πŸ₯³

canvas_test8

πŸ’‘πŸ’‘πŸ’‘πŸ‘©πŸ»β€πŸ’»πŸ‘©πŸ»β€πŸ’» TDD ν•΄λ³Έ μ†Œκ°

μ˜μƒμ—μ„œ 봀던 κ²ƒμ²˜λŸΌ, μ§€κΈˆ ν•΄μ•Ό ν•  일과 λ‹€μŒ ν•΄μ•Ό ν•  일이 λͺ…ν™•ν•΄μ Έμ„œ μ’‹μ•˜λ‹€. λ‹€λ§Œ ν…ŒμŠ€νŠΈμ— μ“΄ canvas μ—˜λ¦¬λ¨ΌνŠΈλŠ” 아직 μ™„λ²½νžˆ ν…ŒμŠ€νŠΈ 툴이 μ§€μ›λ˜μ–΄ μžˆλŠ” 것 같진 μ•Šμ•˜λ‹€. κ·Έλ ‡μ§€λ§Œ SRP 에 λ§žμΆ”μ–΄ κ°œλ°œν•˜λŠ” κ²ƒμ—λŠ” ν™•μ‹€νžˆ 도움이 λ˜λŠ” 것 κ°™λ‹€. 계속 써보고 μ‹Άλ‹€!


πŸ“”Β λ ˆνΌλŸ°μŠ€ RTL

https://www.robinwieruch.de/react-testing-library/

https://testing-library.com/docs/react-testing-library/intro/#tutorials

react - canvas 그리기

https://velog.io/@mokyoungg/React-Reactμ—μ„œ-Canvas-μ‚¬μš©ν•˜κΈ°λ§ˆμš°μŠ€-그리기

Β© Copyright 2022, yesCoding