Skip to content

Latest commit

ย 

History

History
986 lines (723 loc) ยท 59.1 KB

the-right-way-to-test-react-components.md

File metadata and controls

986 lines (723 loc) ยท 59.1 KB

Stephen Scott๋‹˜์ด ์“ฐ์‹  The Right Way to Test React Components๋ผ๋Š” ๊ธ€์˜ ๋ฒˆ์—ญ์ž…๋‹ˆ๋‹ค. ๋ณธ ๋ฒˆ์—ญ ๊ธ€์€ ์› ์ž‘์ž์˜ ํ—ˆ๊ฐ€ํ•˜์— ์ž‘์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฒˆ์—ญ๋ฌธ์ด์ง€๋งŒ ํ•œ๊ตญ์–ด๋กœ ์ฝํžˆ๊ธฐ ์‰ฝ๊ฒŒ ํ•˜๊ฒŒ ์œ„ํ•ด ์›์ž‘์ž์˜ ์˜๋„๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š๋Š” ์„ ์—์„œ ์ ๊ทน์ ์œผ๋กœ ์˜์—ญ์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ ๋ณธ๋ฌธ์—์„œ์˜ ์ค‘์š”ํ•˜๊ฒŒ ๋‹ค๋ค„์ง€๋Š” ๊ฐœ๋…๋“ค(React์˜ Component, Prop, State์™€ ๊ฐ™์ด)์€ ๋ฒˆ์—ญ์‹œ ์˜คํ•ด๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์›๋ฌธ์„ ๊ทธ๋Œ€๋กœ ์”๋‹ˆ๋‹ค.

๋ฒˆ์—ญ์ž : Junyoung Choi (Rokt33r)

React Component๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•

์ง€๊ธˆ React Component๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ "์˜ฌ๋ฐ”๋ฅธ" ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋งŽ์€ ํ˜ผ๋ž€์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜์ž‘์—…์œผ๋กœ ์จ์•ผํ• ๊นŒ์š”? ์•„๋‹ˆ๋ฉด ์Šค๋ƒ…์ƒท๋งŒ ํ™œ์šฉํ•ด์•ผ ํ• ๊นŒ์š”? ์–ด์ฉŒ๋ฉด ๋‘˜ ๋‹ค? Prop๋“ค๋„ ํ…Œ์ŠคํŠธ ํ•  ๊ฑด๊ฐ€์š”? State๋Š”? ์Šคํƒ€์ผ์ด๋‚˜ ๋ ˆ์ด์•„์›ƒ์€ ์–ด๋–ป๊ฒŒ ํ•  ๊ฑด๊ฐ€์š”?

์ œ ์ƒ๊ฐ์—๋Š” ํ•˜๋‚˜์˜ "์˜ฌ๋ฐ”๋ฅธ" ๋ฐฉ๋ฒ•์€ ์—†๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค๋งŒ, ๊ทธ๋ž˜๋„ ํšจ๊ณผ๊ฐ€ ์žˆ๋Š” ๋ช‡๊ฐ€์ง€ ํŒ๊ณผ ํŒจํ„ด๋“ค์„ ์†Œ๊ฐœ๋“œ๋ฆฌ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

๋ฐฐ๊ฒฝ: ํ…Œ์ŠคํŠธํ•ด ๋ณผ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜

์Šค๋งˆํŠธํฐ์˜ ์ž ๊น€ํ™”๋ฉด๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•˜๋Š” LockScreen Component๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค. ์ด ์•ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ํ•ฉ๋‹ˆ๋‹ค:

  • ํ˜„์žฌ ์‹œ๊ฐ„์„ ํ‘œ์‹œํ•œ๋‹ค.
  • ์œ ์ €๊ฐ€ ์„ค์ •ํ•œ ๋ฉ”์„ธ์ง€๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • ์œ ์ €๊ฐ€ ์„ค์ •ํ•œ ๋ฐฐ๊ฒฝ ํ™”๋ฉด์„ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • ๋ฐ€์–ด์„œ ์ž ๊ธˆ ํ•ด์ œ ์œ„์ ฏ์„ ๋ฐ‘์— ๋‘ฌ์•ผํ•œ๋‹ค.

์•„๋งˆ ์ด๋Ÿฐ ๋ชจ์–‘์ด ๋ ๊ฒƒ์ž…๋‹ˆ๋‹ค:

Demo

์—ฌ๊ธฐ์—์„œ ์ง์ ‘ ์จ๋ณด์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ๋Š” GitHub์—์„œ ์ฐพ์œผ์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์˜ App Component๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

App.jsx

import React from "react";
import LockScreen from "./LockScreen";

export default class App extends React.Component {
  render() {
    return (
      <LockScreen
        wallpaperPath="react_wallpaper.png"
        userInfoMessage="์ด๊ฑด Tim์˜ ์ „ํ™”์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ฐพ์œผ์‹œ๋ฉด ๊ทธ์—๊ฒŒ ๋Œ๋ ค์ฃผ์„ธ์š”. ์ด๊ฒŒ ์—†์œผ๋ฉด ์Šฌํผํ•˜๊ณ  ์žˆ์„๊ฑฐ์—์š”."
        onUnlocked={() => alert("์—ด๋ฆผ!")}
      />
    );
  }
}

๋ณด๋‹ค์‹ถ์ด, LockScreen์€ ์„ธ๊ฐ€์ง€ Prop๋“ค์„ ๋ฐ›์Šต๋‹ˆ๋‹ค: wallpaperPath, userInfoMessage, ๊ทธ๋ฆฌ๊ณ  onUnlocked.

LockScreen์˜ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

LockScreen.jsx

import React, { PropTypes } from "react";
import ClockDisplay from "./ClockDisplay";
import TopOverlay from "./TopOverlay";
import SlideToUnlock from "./SlideToUnlock";

export default class LockScreen extends React.Component {
  static propTypes = {
    wallpaperPath: PropTypes.string,
    userInfoMessage: PropTypes.string,
    onUnlocked: PropTypes.func,
  };

  render() {
    const {
      wallpaperPath,
      userInfoMessage,
      onUnlocked,
    } = this.props;

    return (
      <div
        style={{
          height: "100%",
          display: "flex",
          justifyContent: "space-between",
          flexDirection: "column",
          backgroundImage: wallpaperPath ? `url(${wallpaperPath})` : "",
          backgroundColor: "black",
          backgroundPosition: "center",
          backgroundSize: "cover",
        }}
      >
        <ClockDisplay />
        {userInfoMessage ? (
          <TopOverlay
            style={{
              padding: "2em",
              marginBottom: "auto",
            }}
          >
            {userInfoMessage}
          </TopOverlay>
        ) : null}
        <SlideToUnlock onSlide={onUnlocked} />
      </div>
    );
  }
}

LockScreen๋Š” ๋‹ค๋ฅธ Component๋“ค์„ ๋ถˆ๋ฅด๊ณ  ์žˆ์ง€๋งŒ, ์šฐ๋ฆฌ๋Š” LockScreen๋งŒ ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๊ฒƒ์ด๋ฏ€๋กœ, ์ง€๊ธˆ์€ ์—ฌ๊ธฐ์—๋งŒ ์ง‘์ค‘ํ•ฉ์‹œ๋‹ค.

Component Contracts

LockScreen๋ฅผ ํ…Œ์ŠคํŠธ ํ•˜๋ ค๋ฉด, ์šฐ์„  ๊ทธ๊ฒƒ์˜ Contract๊ฐ€ ๋ญ์ธ์ง€ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•œ Component์˜ Contract๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์€ React Component์˜ ํ…Œ์ŠคํŠธ์—์„œ ๋งค์šฐ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. Contract๋Š” Component๋กœ๋ถ€ํ„ฐ ๊ธฐ๋Œ€๋˜๋Š” ๋™์ž‘๊ณผ ์‚ฌ์šฉ์‹œ ์–ด๋–คํ•œ ๊ฐ€์ •์ด ์ด์น˜์— ๋งž๋Š” ์ง€๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ๋ถ„๋ช…ํ•œ Contract๊ฐ€ ์—†๋‹ค๋ฉด, ๋‹น์‹ ์˜ Component๋Š” ์•„๋งˆ ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šธ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ Component์˜ Contract๋ฅผ ์ œ๋Œ€๋กœ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•œ ์•„์ฃผ ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

์—ญ์ž ์ฃผ: Contract๋Š” ๋น„์ฆˆ๋‹ˆ์Šค์—์„œ์˜ ๊ณ„์•ฝ์— ๋Œ€ํ•œ ์€์œ ๋กœ์จ ๋ณธ๋ฌธ์—์„  Component๊ฐ€ ์–ด๋–ป๊ฒŒ ํ‘œ์‹œ๋˜๊ณ  ๋ฌด์—‡์„ ํ•ด์•ผํ•˜๋Š”์ง€ ๋“ฑ์„ ๊ฒฐ์ •ํ•˜๋Š” ์‚ฌ์–‘(Specification) ๊ณผ ๊ฐ™์€ ์˜๋ฏธ๋กœ ์ƒ๊ฐํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

https://en.wikipedia.org/wiki/Design_by_contract

๊ฐ๊ฐ์˜ React Component๋Š” Contract๋ฅผ ์ •์˜ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ ์–ด๋„ ํ•˜๋‚˜๋Š” ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ฌด์—‡์„ ๋ Œ๋”์‹œํ‚ค๋Š”๊ฐ€ (์–ด์ฉŒ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•  ์ˆ˜๋„ ์žˆ๊ฒŸ์ฃ )

๊ทธ๋ฆฌ๊ณ , ๋Œ€๋ถ€๋ถ„์˜ Component Contract๋Š” ์ฃผ๋กœ ๋‹ค์Œ ์š”์†Œ๋“ค์— ์˜ํ–ฅ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค:

  • Component๊ฐ€ ๋ฐ›์„ props
  • Component๊ฐ€ ๊ฐ€์ง€๋Š” state
  • ์œ ์ €์™€ ์ƒํ˜ธ์ž‘์šฉ์ด ์ผ์–ด๋‚  ๋•Œ Component๊ฐ€ ํ•˜๋Š” ์ผ (ํด๋ฆญ, ๋“œ๋ž˜๊ทธ, ํ‚ค๋ณด๋“œ์ž…๋ ฅ ๋“ฑ)

Component Contract์— ๋œ ์˜ํ–ฅ์„ ๋ผ์น˜๋Š” ๊ฒƒ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  • Component๊ฐ€ ํ‘œ์‹œ๋˜๊ณ ์žˆ๋Š” context
  • Component์˜ ์ธ์Šคํ„ด์Šค ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ถˆ๋Ÿฌ์™€์กŒ์„ ๋•Œ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•˜๋Š” ๊ฒƒ (public ref interface)
  • Component ์ผ๋ถ€ lifecycle์—์„œ ์ผ์–ด๋‚˜๋Š” Side effects (componentDidMount, componentWillUnmount, ๋“ฑ)

์—ฌ๋Ÿฌ๋ถ„์˜ Component์˜ Contract๋ฅผ ์•Œ๊ธฐ ์œ„ํ•ด์„  ๋‹ค์Œ์˜ ์งˆ๋ฌธ์— ๋Œ€ํ•ด ์Šค์Šค๋กœ ๋‹ตํ•ด๋ณด์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • Component๋Š” ๋ฌด์—‡์„ ๋ Œ๋”๋งํ•˜๋Š”๊ฐ€?
  • Component๋Š” ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๊ฒƒ๋“ค์„ ๋ Œ๋”๋ง ํ•˜๋Š”๊ฐ€?
  • Prop์œผ๋กœ ๋„˜๊ฒจ์ค€ ํ•จ์ˆ˜๋ฅผ Component๋Š” ๋ฌด์–ผ ์œ„ํ•ด ์“ฐ๋Š”๊ฐ€? ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€? ์•„๋‹ˆ๋ฉด ๋˜ ๋‹ค๋ฅธ Component๋กœ ๋„˜๊ฒจ์ค„ ๊ฑด๊ฐ€? ๋งŒ์•ฝ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋™์‹œ์— ๋ฌด์—‡์„ ํ•˜๋Š”๊ฐ€?
  • ์œ ์ €๊ฐ€ Component์™€ ์ƒํ˜ธ์ž‘์šฉ์„ ํ•˜๋ฉด ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๋Š”๊ฐ€?

LockScreen์˜ Contract๋ฅผ ์•Œ์•„๋ณด์ž

LockScreen์˜ render ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ดํŽด๋ณด๊ณ , ์–ด๋””์—์„œ ๋ Œ๋”๋ง์ด ๋ฐ”๋€” ์ˆ˜ ์žˆ๋Š”์ง€ ์ฝ”๋ฉ˜ํŠธ๋กœ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 3ํ•ญ ์—ฐ์‚ฐ์ž, If๋ฌธ, Switch๋ฌธ์ด ๋‹จ์„œ๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ ์ข€ ๋” ์‰ฝ๊ฒŒ Contract๋กœ ์ธํ•œ ๋ณ€ํ™”๋ฅผ ํŒŒ์•… ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

LockScreen-render.jsx

render() {
  const {
    wallpaperPath,
    userInfoMessage,
    onUnlocked,
  } = this.props;

  return (
    <div
      style={{
        height: "100%",
        display: "flex",
        justifyContent: "space-between",
        flexDirection: "column",
        // ๋งŒ์•ฝ wallpaperPath props์ด ๋„˜๊ฒจ์ง€๋ฉด, div์˜ CSS background-image๋กœ ๋“ค์–ด๊ฐˆ ๊ฒƒ์ด๋‹ค.
        // ๋„˜๊ฒจ์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋นˆ ๋ฌธ์ž์—ด์ด ๋˜์–ด์•ผํ•œ๋‹ค.(= ์•„๋ฌด๋Ÿฐ ์Šคํƒ€์ผ๋„ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค.)
        backgroundImage: wallpaperPath ? `url(${wallpaperPath})` : "",
        backgroundColor: "black",
        backgroundPosition: "center",
        backgroundSize: "cover",
      }}
    >
      <ClockDisplay />
      {/*
        ๋งŒ์•ฝ userInfoMessage prop์ด ๋„˜๊ฒจ์ง€๋ฉด, TopOverlay๋ฅผ ํ†ตํ•ด userInfoMessage๋ฅผ ํ‘œ์‹œ์‹œํ‚จ๋‹ค.
        ๋„˜๊ฒจ์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์—ฌ๊ธฐ์— ์•„๋ฌด๊ฒƒ๋„ ๋ Œ๋”๋ง ์‹œํ‚ค์ง€ ์•Š๋Š”๋‹ค.(null)
      */}
      {userInfoMessage ? (
        <TopOverlay
          style={{
            padding: "2em",
            marginBottom: "auto",
          }}
        >
          {userInfoMessage}
        </TopOverlay>
      ) : null}
      <SlideToUnlock onSlide={onUnlocked} />
    </div>
  );
}

์ด์ œ LockScreen์˜ contract๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” 3๊ฐ€์ง€์˜ Contraint์— ๋Œ€ํ•ด ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค:

  • ๋งŒ์•ฝ wallpaperPath Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๊ฐ€์žฅ ๋ฐ”๊นฅ์˜ div๋Š” ๊ทธ ๊ฐ’์ด ๋ฌด์—‡์ด๋“  url(...)๋กœ ๊ฐ์‹ธ์—ฌ์ ธ์„œ background-image CSS property๋ฅผ inline ์Šคํƒ€์ผ๋กœ ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๋ช‡๊ฐ€์ง€ inline ์Šคํƒ€์ผ๊ณผ ํ•จ๊ป˜ TopOverlay์˜ ์ž์‹์œผ๋กœ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, TopOverlay๋Š” ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์—ญ์ž ์ฃผ: Constraint๋Š” Contract(์‚ฌ์–‘)์— ๋Œ€ํ•œ ๊ฐ๊ฐ์˜ ํ•ญ๋ชฉ, ๋ฐ”๊ฟ”๋งํ•˜๋ฉด ์„ธ์„ธํ•œ ๊ทœ์น™์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜๋„์ ์œผ๋กœ Contract์™€ Constraint๋ฅผ ํ˜ผ๋™ํ•˜์ง€ ์•Š๋„๋ก ๋‘ ๋‹จ์–ด๋Š” ์˜์–ด ๊ทธ๋Œ€๋กœ ์ป์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  Contract์—์„œ ๋ช‡๊ฐ€์ง€ Contraint์€ ํ•ญ์ƒ ๊ทธ๋Œ€๋กœ๋ผ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • div๋Š” ๋ฌด์—‡์ด ๋“ค์–ด์˜ค๋“  ํ•ญ์ƒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. inline ์Šคํƒ€์ผ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
  • ClockDisplay๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ์•„๋ฌด๋Ÿฐ Prop๋„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • SlideToUnlock๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. onUnlocked์ด ์ •์˜๋˜๋“  ๋ง๋“  SlideToUnlock์˜ onSlide Prop์œผ๋กœ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.

Component์˜ propTypes ์—ญ์‹œ Contract๋ฅผ ์•Œ๊ธฐ ์œ„ํ•œ ๋‹จ์„œ๋ฅผ ์ฐพ๊ธฐ ์ข‹์€ ๊ณณ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ข€ ๋” ๋งŽ์€ Contraint๋“ค์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค:

  • wallpaperPath๋Š” ๋ฌธ์ž์—ด์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • userInfoMessage๋Š” ๋ฌธ์ž์—ด์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • onUnlocked๋Š” ํ•จ์ˆ˜์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฐ์‹์œผ๋กœ Component์˜ Contract๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋งˆ ๋” ๋งŽ์€ Contraint์ด ์žˆ์–ด์•ผ ํ•  ๊ฒƒ์ด๊ณ , Production ์ˆ˜์ค€์˜ ์ฝ”๋“œ์ด๋ฉด (ํ’ˆ์งˆ์„ ๋ณด์ฆํ•˜๊ธฐ ์œ„ํ•ด) ๊ฐ€๋Šฅํ•œ ํ•œ ๋” ๋งŽ์ด ์ฐพ๊ณ  ์‹ถ์œผ์‹ค ๊ฒ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์˜ˆ์ œ์ด๋ฏ€๋กœ ์ผ๋‹จ ์ด๊ฒƒ๋“ค๋งŒ์„ ๊ฐ€์ง€๊ณ  ํ•ด๋ด…์‹œ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ ์–ธ์ œ๋“ ์ง€ Contraint๋“ค์„ ๋” ๋ฐœ๊ฒฌํ•˜๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ๋Š˜๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฌด์—‡์ด ํ…Œ์ŠคํŠธ ํ• ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ๋‚˜?

  • wallpaperPath๋Š” ๋ฌธ์ž์—ด์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • userInfoMessage๋Š” ๋ฌธ์ž์—ด์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • onUnlocked๋Š” ํ•จ์ˆ˜์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • div๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง๋˜๋ฉฐ, ๋ชจ๋“  ๊ฒƒ์„ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. inline ์Šคํƒ€์ผ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
  • ClockDisplay๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ์•„๋ฌด๋Ÿฐ Prop๋„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • SlideToUnlock๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. onUnlocked์ด ์ •์˜๋˜๋“  ๋ง๋“  SlideToUnlock์˜ onSlide Prop์œผ๋กœ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ wallpaperPath Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๊ฐ€์žฅ ๋ฐ”๊นฅ์˜ div๋Š” ๊ทธ ๊ฐ’์ด ๋ฌด์—‡์ด๋“  url(...)๋กœ ๊ฐ์‹ธ์—ฌ์ ธ์„œ background-image CSS property๋ฅผ inline ์Šคํƒ€์ผ๋กœ ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๋ช‡๊ฐ€์ง€ inline ์Šคํƒ€์ผ๊ณผ ํ•จ๊ป˜ TopOverlay์˜ ์ž์‹์œผ๋กœ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, TopOverlay๋Š” ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ผ๋ถ€ Contraint์€ ํ…Œ์ŠคํŠธํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ์ง€๋งŒ, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒƒ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ €๋Š” ํ…Œ์ŠคํŠธํ•  ๊ฐ€์น˜๊ฐ€ ์—†๋Š” ๊ฒƒ๋“ค์„ 3๊ฐ€์ง€ Rules of thumb์„ ํ†ตํ•ด ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค:

์—ญ์ž ์ฃผ: Rules of Thumb๋Š” ๊ฒฝํ—˜์ƒ์œผ๋กœ ์–ป์–ด์ง„ ๋งŒ๋“  ๊ทœ์น™๋“ค์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„  ์›์ž‘์ž์˜ ๊ฒฝํ—˜์ƒ ์“ธ๋งŒํ•œ ๊ทœ์น™์œผ๋กœ, ์ดํ•˜๋Š” ์ด Rules of Thumb๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธํ•  Constraint๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜๋ฏธ์ƒ Contract, Constraint ๊ทธ๋ฆฌ๊ณ  Rules of Thumb ๋ชจ๋‘ ๊ทœ์น™์œผ๋กœ ๋ฒˆ์—ญ๋  ์šฐ๋ ค๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ์ด ์—ญ์‹œ ์›๋ฌธ ๊ทธ๋Œ€๋กœ ์”๋‹ˆ๋‹ค.

  1. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ์‹คํ–‰ ์ฝ”๋“œ๊ฐ€ ๊ทธ๋Œ€๋กœ ์ค‘๋ณต๋˜์–ด ์“ฐ์—ฌ์ ธ ์žˆ๋Š”๊ฐ€? ์ด๊ฒƒ์€ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ง๊ฐ€์ง€๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  2. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ Assertion์ด ์ด๋ฏธ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ๊ฐ€ ๋ณด์ฆํ•˜๋Š” (๊ทธ๋ฆฌ๊ณ  ์ฑ…์ž„์ง€๋Š”) ๋ถ€๋ถ„๊ณผ ์ค‘๋ณต๋˜๋Š”๊ฐ€? (๊ฐ€์žฅ ์ค‘์š”)
  3. Component ๋ฐ”๊นฅ์—์„œ์˜ ์‹œ์ ์—์„œ, ์ด๋Ÿฌํ•œ ์„ธ๋ถ€ํ•ญ๋ชฉ๋“ค์ด ์ค‘์š”ํ•œ๊ฐ€? ํ˜น์€ ๊ทธ์ € ๋‚ด๋ถ€์ ์ธ ์‚ฌ์ •์ธ๊ฐ€? ์ด๋Ÿฌํ•œ ๋‚ด๋ถ€์ ์ธ ์‚ฌ์ •์œผ๋กœ ์ธํ•œ ์˜ํ–ฅ๋Š” Component์˜ public API๋ฅผ ์“ฐ๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์„ค๋ช… ๋  ์ˆ˜ ์žˆ๋Š”๊ฐ€?

์ด๊ฒƒ๋“ค์€ ๊ทธ์ € Rules of thumb์ด๋ฏ€๋กœ, ์–ด๋ ต๋‹ค๋‹ค๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‹ซ๋‹ค๋Š” ๋ณ€๋ช…์œผ๋กœ ์“ฐ์—ฌ์ง€์ง€ ์•Š๋„๋ก ์กฐ์‹ฌํ•˜์„ธ์š”. ์ข…์ข…, ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์›Œ ๋ณด์ด๋Š” ๊ฒƒ๋“ค์ด ํ…Œ์ŠคํŠธ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ํ–‰ํ•ด์ง€๋Š” ์ฝ”๋“œ๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‹ค๋ฅธ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๊นŒ์ง€๋„ ์—ฌ๋Ÿฌ ๋งŽ์€ ๊ฐ€์ •๋“ค์„ ์žก์•„ ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์•ˆ์ •์ ์ธ ๊ฒฐ๊ณผ๋ฌผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ Constraint๋ฅผ ์‚ดํŽด๋ณด๊ณ  ๋ฌด์—‡์„ ํ…Œ์ŠคํŠธํ• ์ง€ ๋ง์ง€๋ฅผ Rules of Thumb๋ฅผ ํ†ตํ•ด ๊ฒฐ์ •ํ•ด๋ด…์‹œ๋‹ค. ๋จผ์ € ์ฒ˜์Œ 3๊ฐœ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ฉ์‹œ๋‹ค:

  • wallpaperPath๋Š” ๋ฌธ์ž์—ด์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • userInfoMessage๋Š” ๋ฌธ์ž์—ด์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • onUnlocked๋Š” ํ•จ์ˆ˜์ด๋ฉฐ, ์„ ํƒ์ ์œผ๋กœ ์ฃผ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด Constraint๋“ค์€ React์˜ PropTypes ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์‹ ๊ฒฝ์“ฐ๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ, ์ด๊ฑธ ํ…Œ์ŠคํŠธ๋กœ ๋งŒ๋“œ๋Š”๊ฑด Rule #2(์ด๋ฏธ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ณด์ฆํ•˜๊ณ  ์žˆ์Œ)๋ฅผ ์œ„๋ฐฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ณ ๋กœ, ์ €๋Š” Prop์˜ ํƒ€์ž…๋“ค์€ ํ…Œ์ŠคํŠธ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ €๋Š”, ์ข…์ข… ํ…Œ์ŠคํŠธ๋“ค์€ ๋ฌธ์„œ๋ฅผ ๊ฒธํ•  ์ˆ˜ ์žˆ๊ธฐ์—, ๊ฐ€๋” Rule #2๋ฅผ ์œ„๋ฐฐํ•˜๋”๋ผ๋„ ์‹คํ–‰์ฝ”๋“œ๊ฐ€ ์–ด๋–ค ํƒ€์ž…์„ ๋ฐ›์„์ง€ ์•Œ๊ธฐ ์–ด๋ ต๋‹ค๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ์“ฐ๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, propTypes๋Š” ์ถฉ๋ถ„ํžˆ ์ข‹๊ณ  ์ฝ๊ธฐ๋„ ์‰ฌ์šฐ๋ฏ€๋กœ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ Constraint๋ฅผ ๋ด…์‹œ๋‹ค:

  • div๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง๋˜๋ฉฐ, ๋ชจ๋“  ๊ฒƒ์„ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. inline ์Šคํƒ€์ผ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

์ด๊ฑด 3๊ฐ€์ง€์˜ Constraint๋“ค๋กœ ๋‚˜๋ˆ„์–ด ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • div๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.
  • div๋Š” ๋ Œ๋”๋ง๋˜๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ๊ฒƒ๋“ค์„ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • div๋Š” inline ์Šคํƒ€์ผ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

์ฒ˜์Œ 2๊ฐœ์˜ Constraint๋“ค์€ Rules of Thumb๋ฅผ ์œ„๋ฐ˜ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์šฐ๋ฆฌ๋Š” ์ด๊ฒƒ๋“ค์„ ํ…Œ์ŠคํŠธํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, 3๋ฒˆ์งธ๋ฅผ ๋ด…์‹œ๋‹ค.

๋‹ค๋ฅธ constraint๊ฐ€ ๋‹ค๋ฃจ๊ณ  ์žˆ๋Š” background-image๋ฅผ ์ œ์™ธํ•˜๊ณ , div๋Š” ๋‹ค์Œ์˜ ์Šคํƒ€์ผ๋“ค์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค:

height: "100%",
display: "flex",
justifyContent: "space-between",
flexDirection: "column",
backgroundColor: "black",
backgroundPosition: "center",
backgroundSize: "cover",

๋งŒ์•ฝ ์ด ์Šคํƒ€์ผ๋“ค์ด ์žˆ๋Š”์ง€ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋ ค ํ•œ๋‹ค๋ฉด, ์œ ํšจํ•œ Assertion๋ฅผ ์œ„ํ•ด ๊ฐ ์Šคํƒ€์ผ์˜ ๊ฐ’์„ ์žˆ๋Š” ๊ทธ๋Œ€๋กœ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ณ ๋กœ Assertion๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค:

  • div๋Š” height ์Šคํƒ€์ผ์€ 100%์˜ ๊ฐ’์„ ๊ฐ€์ง„๋‹ค.
  • div๋Š” display ์Šคํƒ€์ผ์„ flex๋กœ ๊ฐ€์ง„๋‹ค.
  • โ€ฆ๋‹ค๋ฅธ ์Šคํƒ€์ผ๋“ค๋„ ๋˜‘๊ฐ™์ด ํ•œ๋‹ค.

๊ฐ„๊ฒฐํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด toMatchObject๊ฐ™์€ ๊ฑธ ์“ฐ๊ณ  ์žˆ๋”๋ผ๋„, ์ด๊ฑด ์‹คํ–‰ ์ฝ”๋“œ์˜ ์Šคํƒ€์ผ๊ณผ ์ค‘๋ณต๋˜๋Š”๋ฐ๋‹ค ๋ง๊ฐ€์ง€๊ธฐ ์‰ฝ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋‹ค๋ฅธ ์Šคํƒ€์ผ์„ ์ถ”๊ฐ€ํ•œ๋‹คํ•ด๋„, ๋˜‘๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธ์—๋„ ๋„ฃ์–ด์•ผ ํ• ๊ฒ๋‹ˆ๋‹ค. ๋˜ํ•œ, Component์˜ ํ–‰๋™์ด ๋ฐ”๋€Œ์ง€ ์•Š๋”๋ผ๋„, ์Šคํƒ€์ผ์„ ์•ฝ๊ฐ„ ์ˆ˜์ •ํ•˜๊ฒŒ๋˜๋ฉด ํ…Œ์ŠคํŠธ์—์„œ์˜ ์Šคํƒ€์ผ ์—ญ์‹œ ๊ทธ๋Œ€๋กœ ์ˆ˜์ •๋˜์–ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ด Constraint๋Š” Rule #1๋ฅผ ์œ„๋ฐ˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.(์‹คํ–‰ ์ฝ”๋“œ์™€ ์ค‘๋ณต๋จ; ๋ง๊ฐ€์ง€๊ธฐ ์‰ฌ์›€) ์ด๋Ÿฐ ์ด์œ ๋กœ, ์ €๋Š” ๋Ÿฐํƒ€์ž„์—์„œ ๋ณ€ํ™”๊ฐ€ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š” ํ•œ inline ์Šคํƒ€์ผ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ข…์ข… "์ด๊ฒƒ์€ ์ด๊ฒƒ์ด ํ•˜๋Š” ๊ฑธ ํ•œ๋‹ค"๋‚˜ "์ด๊ฒƒ์€ ์‹คํ–‰ ์ฝ”๋“œ์—์„œ ํ•˜๋Š” ๊ฑธ ๊ทธ๋Œ€๋กœ ํ•œ๋‹ค" ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์“ฐ์‹ ๋‹ค๋ฉด, ์ด๋Ÿฌํ•œ ํ…Œ์ŠคํŠธ๋“ค์€ ๋ถˆํ•„์š” ํ•˜๊ฑฐ๋‚˜ ๋„ˆ๋ฌด ๋ช…๋ฐฑํ•œ ๊ฒ๋‹ˆ๋‹ค.

์ด์ œ ๊ทธ ๋‹ค์Œ 2๊ฐœ์˜ Constraint๋ฅผ ๋ด…์‹œ๋‹ค:

  • ClockDisplay๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ์•„๋ฌด๋Ÿฐ Prop๋„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • SlideToUnlock๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. onUnlocked์ด ์ •์˜๋˜๋“  ๋ง๋“  SlideToUnlock์˜ onSlide Prop์œผ๋กœ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.

์ด๊ฒƒ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜๋‰  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ClockDisplay๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.
  • ๋ Œ๋”๋ง๋œ ClockDisplay๋Š” ์•„๋ฌด๋Ÿฐ Prop๋„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • SlideToUnlock๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.
  • onUnlocked Prop์ด ์ •์˜๋˜์—ˆ์œผ๋ฉด, ๋ Œ๋”๋ง๋œ SlideToUnlock์€ onSlide Prop์œผ๋กœ onUnlocked๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • onUnlocked Prop์ด ์ •์˜๋˜์ง€ ์•Š์•˜์œผ๋ฉด, ๋ Œ๋”๋ง๋œ SlideToUnlock์€ onSlide Prop ์—ญ์‹œ undefined๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ Constraint๋“ค์€ 2๊ฐ€์ง€ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค: "์–ด๋–ค Component๊ฐ€ ๋ Œ๋”๋ง๋œ๋‹ค", ๊ทธ๋ฆฌ๊ณ  "๋ Œ๋”๋ง๋œ Component๋Š” ์ด๋Ÿฌํ•œ Prop์„ ๋ฐ›๋Š”๋‹ค". ์ด๊ฒƒ๋“ค์€ ์–ด๋–ป๊ฒŒ ์—ฌ๋Ÿฌ๋ถ„์˜ Component๊ฐ€ ๋‹ค๋ฅธ Component๋“ค๊ณผ ์ƒํ˜ธ์ž‘์šฉ์„ ํ•˜๋Š” ์ง€๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋‘๊ฐ€์ง€ ๋ชจ๋‘ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•  ๋งŒํผ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ณ ๋กœ, ์šฐ๋ฆฌ๋Š” ์ด๋Ÿฌํ•œ Constraint๋“ค์„ ๋ชจ๋‘ ํ…Œ์ŠคํŠธ ํ•  ๊ฒ๋‹ˆ๋‹ค.

๋‹ค์Œ Constraint๋ฅผ ๋ด…์‹œ๋‹ค:

  • ๋งŒ์•ฝ wallpaperPath Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๊ฐ€์žฅ ๋ฐ”๊นฅ์˜ div๋Š” ๊ทธ ๊ฐ’์ด ๋ฌด์—‡์ด๋“  url(...)๋กœ ๊ฐ์‹ธ์—ฌ์ ธ์„œ background-image CSS property๋ฅผ inline ์Šคํƒ€์ผ๋กœ ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์–ด์ฉŒ๋ฉด ์—ฌ๋Ÿฌ๋ถ„๋“ค์€, inline ์Šคํƒ€์ผ์ด๊ธฐ ๋•Œ๋ฌธ์—, ํ…Œ์ŠคํŠธ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ• ์ง€๋„ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, background-image๋Š” wallpaperPath Prop์— ๋”ฐ๋ผ ๋ฐ”๋€Œ๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ๋  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ…Œ์ŠคํŠธํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, Component์˜ Public interface์˜ ์ผ๋ถ€์ธ wallpaperPath Prop์˜ ์˜ํ–ฅ์—๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋Š” ์•„๋ฌด๊ฒƒ๋„ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. Public interface๋Š” ํ•ญ์ƒ ํ…Œ์ŠคํŠธ ๋˜์–ด์ ธ์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ๋‚จ์€ 2๊ฐœ์˜ Constraint๋“ค์„ ๋ด…์‹œ๋‹ค:

  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๋ช‡๊ฐ€์ง€ inline ์Šคํƒ€์ผ๊ณผ ํ•จ๊ป˜ TopOverlay์˜ ์ž์‹์œผ๋กœ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, TopOverlay๋Š” ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ๋“ค๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜๋‰  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, TopOverlay๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๊ทธ๊ฒƒ์€ TopOverlay์˜ children์œผ๋กœ ๋„˜๊ฒจ์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๋ Œ๋”๋ง๋œ TopOverlay๋Š” inline ์Šคํƒ€์ผ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, TopOverlay๋Š” ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ฒซ๋ฒˆ์งธ๊ณผ 4๋ฒˆ์งธ Constraint๋Š”(TopOverlay์€ ๋ Œ๋”๋ง ๋œ๋‹ค/๋˜์ง€ ์•Š๋Š”๋‹ค) ๋ฌด์—‡์„ ๋ Œ๋”๋งํ• ์ง€๋ฅผ ๋งํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

๋‘๋ฒˆ์งธ Constraint๋Š” TopOverlay๊ฐ€ userInfoMessage์˜ ๊ฐ’์„ Prop์œผ๋กœ ๋ฐ›๊ณ  ์žˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ด ์—ญ์‹œ ๋ Œ๋”๋ง๋œ Component๊ฐ€ ๋ฐ›์„ Prop์— ๋Œ€ํ•œ ๊ฒƒ์ด๋ฏ€๋กœ ํ…Œ์ŠคํŠธํ•  ๋งŒํผ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ณ ๋กœ, ์ด๊ฒƒ๋„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

3๋ฒˆ์งธ Constraint๋„ TopOverlay๊ฐ€ ๋ฐ›๋Š” ํŠน์ • Prop์„ ํ™•์ธํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์–ด์ฉŒ๋ฉด ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์…จ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ด Prop์€ ๊ทธ์ € inline ์Šคํƒ€์ผ์ž…๋‹ˆ๋‹ค. Prop์ด ๋„˜๊ฒจ์กŒ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์€ ์ค‘์š”ํ•˜์ง€๋งŒ, inline ์Šคํƒ€์ผ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“œ๋Š”๊ฑด ๋ง๊ฐ€์ง€๊ธฐ ์‰ฝ๊ณ  ์‹คํ–‰์ฝ”๋“œ์™€ ์ค‘๋ณต๋ฉ๋‹ˆ๋‹ค. (Rule #1์— ์œ„๋ฐฐ๋ฉ๋‹ˆ๋‹ค) ๋„˜๊ฒจ์ง„ Prop๋“ค์„ ํ™•์ธํ•˜๋Š” ๊ฑด ์ค‘์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, Rule #1๋งŒ ์ƒ๊ฐํ•˜๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผํ• ์ง€ ๋ง์ง€๊ฐ€ ์• ๋งคํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค; ๋‹คํ–‰์Šค๋Ÿฝ๊ฒŒ๋„ ์ œ๊ฐ€ Rule #3๋ฅผ ๋งํ•œ ์ด์œ ๊ฐ€ ์—ฌ๊ธฐ์— ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์ƒ๊ธฐํ•˜๋ฉด:

Component ๋ฐ”๊นฅ์—์„œ์˜ ์‹œ์ ์—์„œ, ์ด๋Ÿฌํ•œ ์„ธ๋ถ€ํ•ญ๋ชฉ๋“ค์ด ์ค‘์š”ํ•œ๊ฐ€? ํ˜น์€ ๊ทธ์ € ๋‚ด๋ถ€์ ์ธ ์‚ฌ์ •์ธ๊ฐ€? ์ด๋Ÿฌํ•œ ๋‚ด๋ถ€์ ์ธ ์‚ฌ์ •์œผ๋กœ ์ธํ•œ ์˜ํ–ฅ๋Š” Component์˜ public API๋ฅผ ์“ฐ๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์„ค๋ช… ๋  ์ˆ˜ ์žˆ๋Š”๊ฐ€?

ํ…Œ์ŠคํŠธ๋ฅผ ์“ธ ๋•Œ, ์ €๋Š” ๊ฐ€๋Šฅํ•œ Component์˜ public API๋งŒ์„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค. (์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ API๊ฐ€ ๊ฐ€์ง€๋Š” Side effects๋ฅผ ํฌํ•จํ•ด์„œ) ์ด Component์˜ ๋ ˆ์ด์•„์›ƒ์€ public API๋กœ๋ถ€ํ„ฐ ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค; ์ด๊ฒƒ์€ CSS์—”์ง„์ด ์‹ ๊ฒฝ ์“ธ ์ผ์ž…๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด Rule #3๊นŒ์ง€ ์œ„๋ฐ˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ Rule #1์™€ Rule #3์— ์œ„๋ฐฐ๋˜๋ฏ€๋กœ, TopOverlay๊ฐ€ Prop์„ ๋ฐ›๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์œผ๋กœ ์ค‘์š”ํ•˜์ง€๋งŒ, ํ…Œ์ŠคํŠธ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰ Constraint๋Š” ํ…Œ์ŠคํŠธํ• ์ง€ ๋ง์ง€ ๊ฒฐ์ •ํ•˜๊ธฐ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ์—ฌ๋Ÿฌ๋ถ„์ด ์–ด๋–ค ๋ถ€๋ถ„์ด ํ…Œ์ŠคํŠธํ• ๋งŒํผ ์ค‘์š”ํ•œ์ง€์— ๋‹ฌ๋ ค์žˆ์Šต๋‹ˆ๋‹ค; ์ด๋Ÿฌํ•œ Rules of Thumb๋Š” ๊ทธ์ € ๊ฐ€์ด๋“œ๋ผ์ธ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

์ด์ œ ์šฐ๋ฆฐ ๋ชจ๋“  Constraint๋“ค์„ ํ™•์ธํ•ด๋ณด์•˜๊ณ , ๋ฌด์—‡์„ ํ…Œ์ŠคํŠธํ• ๊ฑด์ง€ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค:

  • div๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.
  • div๋Š” ๋ Œ๋”๋ง๋˜๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ๊ฒƒ๋“ค์„ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • ClockDisplay๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.
  • ๋ Œ๋”๋ง๋œ ClockDisplay๋Š” ์•„๋ฌด๋Ÿฐ Prop๋„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • SlideToUnlock๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.
  • onUnlocked Prop์ด ์ •์˜๋˜์—ˆ์œผ๋ฉด, ๋ Œ๋”๋ง๋œ SlideToUnlock์€ onSlide Prop์œผ๋กœ onUnlocked๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • onUnlocked Prop์ด ์ •์˜๋˜์ง€ ์•Š์•˜์œผ๋ฉด, ๋ Œ๋”๋ง๋œ SlideToUnlock์€ onSlide Prop ์—ญ์‹œ undefined๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ wallpaperPath Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๊ฐ€์žฅ ๋ฐ”๊นฅ์˜ div๋Š” ๊ทธ ๊ฐ’์ด ๋ฌด์—‡์ด๋“  url(...)๋กœ ๊ฐ์‹ธ์—ฌ์ ธ์„œ background-image CSS property๋ฅผ inline ์Šคํƒ€์ผ๋กœ ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, TopOverlay๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๊ทธ๊ฒƒ์€ TopOverlay์˜ children์œผ๋กœ ๋„˜๊ฒจ์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, TopOverlay๋Š” ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Constraint๋“ค์„ ์„ธ์„ธํ•˜๊ฒŒ ์‚ดํŽด๋ด„์œผ๋กœ์จ, ๋งŽ์€ ์ˆ˜์˜ Constraint๋ฅผ ์—ฌ๋Ÿฌ ์ž‘์€ Constraint๋“ค๋กœ ๋‚˜๋ˆ„์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฑด ์ •๋ง ์ข‹์•„์š”! ์ด์ œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์“ฐ๊ธฐ๊ฐ€ ๋”์šฑ ์‰ฌ์›Œ์งˆ ๊ฒ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ค€๋น„ํ•˜๊ธฐ

Componentํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์ค€๋น„๋ฅผ ํ•ด๋ด…์‹œ๋‹ค. ์ €๋Š” enzyme์™€ ํ•จ๊ป˜ Jest๋ฅผ ์“ธ๊ฒ๋‹ˆ๋‹ค. Jest๋Š” React์™€ ๋งค์šฐ ์ž˜ ๋งž๊ณ  create-react-app์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋„ ํฌํ•จ๋œ ํ…Œ์ŠคํŠธ๋Ÿฌ๋„ˆ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์–ด์ฉŒ๋ฉด ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ ์ด๋ฏธ ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ๋˜์–ด์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. Enzyme๋Š” ๋ธŒ๋ผ์šฐ์ €์™€ Nodeํ™˜๊ฒฝ์—์„œ ๋™์ž‘ํ•˜๋Š” ๋ฏฟ์„ ๋งŒํ•œ React ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

๋น„๋ก ์ €๋Š” Jest์™€ enzyme๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์—ฌ๊ธฐ์„œ ์“ฐ์ธ ๊ฐœ๋…๋“ค์€ ์–ด๋–ค ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋„ ์ ์šฉ์‹œํ‚ค์‹ค ์ˆ˜ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค.

LockScreen.test.jsx

import React from "react";
import { mount } from "enzyme";
import LockScreen from "./LockScreen";

describe("LockScreen", () => {
  let props;
  let mountedLockScreen;
  const lockScreen = () => {
    if (!mountedLockScreen) {
      mountedLockScreen = mount(
        <LockScreen {...props} />
      );
    }
    return mountedLockScreen;
  }

  beforeEach(() => {
    props = {
      wallpaperPath: undefined,
      userInfoMessage: undefined,
      onUnlocked: undefined,
    };
    mountedLockScreen = undefined;
  });

  // ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋“ค์€ ์—ฌ๊ธฐ์— ์“ฐ์—ฌ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
});

์กฐ๊ธˆ ํฐ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ๋˜์—ˆ๋„ค์š”. ๋ญ˜ ํ•˜๊ณ  ์žˆ๋Š”์ง€ ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”:

  • let์œผ๋กœ props์™€ mountedLockScreen๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ์จ describeํ•จ์ˆ˜ ์•ˆ์ด๋ฉด ์–ด๋””์—์„œ๋“  ๋ถ€๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • lockScreen ํ•จ์ˆ˜ ์—ญ์‹œ describe ์•ˆ ์–ด๋””์—์„œ๋“  ๋ถ€๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ mountedLockScreen ๋ณ€์ˆ˜์— LockScreen์— Prop์™€ ํ•จ๊ผ Mountํ•ด์ฃผ๊ณ , ์ด๋ฏธ Mount๋œ ๊ฒƒ์„ ๋Œ๋ ค์ค๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” enzyme ReactWrapper๋ฅผ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฑด ๋งค ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ์‚ฌ์šฉํ•  ๊ฒ๋‹ˆ๋‹ค.
  • beforeEach๋Š” props์™€ mountedLockScreen์„ ๋งค ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ์ดˆ๊ธฐํ™”์‹œํ‚ต๋‹ˆ๋‹ค. ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ, ํ•œ ํ…Œ์ŠคํŠธ์˜ ์ƒํƒœ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— mountedLockScreen๋ฅผ undefined๋กœ ์„ค์ •ํ•จ์œผ๋กœ์จ, ๋‹ค์Œ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋  ๋•Œ, lockScreen์„ ๋ถ€๋ฅด๋ฉด ๋งค๋ฒˆ ์ƒˆ๋กœ์šด LockScreen์ด props์™€ ํ•จ๊ป˜ Mount๋ ๊ฒ๋‹ˆ๋‹ค.

์ด ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋Š” Component ํ•˜๋‚˜๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ์—” ๋งŽ์•„๋ณด์ผ์ง€๋„ ๋ชจ๋ฆ…๋‹ˆ๋‹ค๋งŒ, Component๋ฅผ Mountํ•˜๊ธฐ ์ „์— ์ถ”๊ฐ€์ ์œผ๋กœ Props๋ฅผ ์ค€๋น„ํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ Dryํ•˜๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. ์ €๋Š” ์ด๊ฒƒ์„ ๋ชจ๋“  Component ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•˜๊ณ , ์—ฌ๋Ÿฌ๋ถ„๋“ค์—๊ฒŒ๋„ ์œ ์šฉํ•˜๊ธธ ๋น•๋‹ˆ๋‹ค; ์œ ์šฉ์„ฑ์€ ์ด์ œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋“ค์„ ์จ๋ณผ ์ˆ˜๋ก ๋”์šฑ ํ™•์‹คํ•˜๊ฒŒ ์•Œ๊ฒŒ ๋˜์‹ค ๊ฒ๋‹ˆ๋‹ค.

์—ญ์ž ์ฃผ: Dryํ•œ ํ…Œ์ŠคํŠธ๋Š” ๋‹ค๋ฅธ ์™ธ๋ถ€ ์š”์ธ๋“ค์ด ํ†ต์ œ๋œ ํ…Œ์ŠคํŠธ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

Prop๋“ค์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์•ž์„œํ•œ ํ…Œ์ŠคํŠธ์— ๋”ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ”๋€” ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ดˆ๊ธฐํ™”๋ฅผ ์‹œ์ผœ์ค€๋‹ค๋ฉด ์•ž์„œ ์–ด๋–ค ํ…Œ์ŠคํŠธ๊ฐ€ ํ–‰ํ•ด์ง€๋“  ์ง€๊ธˆ ํ…Œ์ŠคํŠธ์—๋Š” ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ์ด ์—†๊ฒŒ ๋˜๋ฏ€๋กœ ์‹ ๋ขฐ์„ฑ ์žˆ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋Š”๊ฒŒ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค. ์ด๋ฅผ Dry ํ…Œ์ŠคํŠธ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ๋ฅผ ์จ๋ณด์ž!

Constraint๋ชฉ๋ก์„ ๋ณด๋ฉฐ ํ•˜๋‚˜์”ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด ๋ด…์‹œ๋‹ค. ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ๋Š” ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ์˜ // All tests will go here ์ฝ”๋ฉ˜ํŠธ ๋’ค๋กœ ์ถ”๊ฐ€๋˜๋Š” ํ˜•์‹์œผ๋กœ ์“ฐ์—ฌ์ ธ๋‚˜๊ฐˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  • div๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.

LockScreen.test.jsx

it("always renders a div", () => {
  const divs = lockScreen().find("div");
  expect(divs.length).toBeGreaterThan(0);
});
  • div๋Š” ๋ Œ๋”๋ง๋˜๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  ๊ฒƒ๋“ค์„ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
describe("the rendered div", () => {
  it("contains everything else that gets rendered", () => {
    const divs = lockScreen().find("div");
    // .find๋ฅผ ์‚ฌ์šฉํ•˜์‹ค ๋•Œ, enzyme๋Š” ๋…ธ๋“œ๋“ค์„ ์ฐจ๋ก€๋กœ ๋‚˜์—ดํ•ด ์ค๋‹ˆ๋‹ค.
    // ์—ฌ๊ธฐ์„œ ๋ฐ”๊นฅ์ชฝ๋ถ€ํ„ฐ ์ฝ์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ์— .first()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด
    // ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ์˜ div๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    const wrappingDiv = divs.first();

    // Enzyme๋Š” .children()๋ฅผ ์“ฐ๋ฉด ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ์˜ Node๋ฅผ ์ƒ๋žตํ•ด์ค๋‹ˆ๋‹ค.
    // ์กฐ๊ธˆ ์–ด๋ ค์šธ์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ์ด๊ฒƒ์œผ๋กœ wrappingDiv๊ฐ€ ๋‹ค๋ฅธ ๋ชจ๋“ 
    // Component๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    expect(wrappingDiv.children()).toEqual(lockScreen().children());
  });
});
  • ClockDisplay๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.
it("always renders a `ClockDisplay`", () => {
  expect(lockScreen().find(ClockDisplay).length).toBe(1);
});
  • ๋ Œ๋”๋ง๋œ ClockDisplay๋Š” ์•„๋ฌด๋Ÿฐ Prop๋„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
describe("rendered `ClockDisplay`", () => {
  it("does not receive any props", () => {
    const clockDisplay = lockScreen().find(ClockDisplay);
    expect(Object.keys(clockDisplay.props()).length).toBe(0);
  });
});
  • SlideToUnlock๋Š” ํ•ญ์ƒ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค.
it("always renders a `SlideToUnlock`", () => {
  expect(lockScreen().find(SlideToUnlock).length).toBe(1);
});

์—ฌ๊ธฐ๊นŒ์ง€ ๋ชจ๋“  Constraint๋“ค์€ ํ•ญ์ƒ ์ฐธ์ด ๋˜๋Š” ๊ฒƒ๋“ค์ด๋ฏ€๋กœ ๋น„๊ต์  ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ๊ฐ€ ์‰ฌ์› ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€๋งŒ, ๋‚˜๋จธ์ง€ Constraint๋“ค์€ "๋งŒ์•ฝ ... ํ•œ๋‹ค๋ฉด" ๊ฐ™์€ ์กฐ๊ฑด์ด๋‚˜ ๋•Œ์™€ ํ•จ๊ป˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒƒ๋“ค์€ ์กฐ๊ฑด์ ์œผ๋กœ ์ฐธ์ด ๋˜๋ฏ€๋กœ beforeEach์™€ ํ•จ๊ป˜ 2๊ฐœ์˜ describe๋ฅผ ์ค€๋น„ํ•  ๊ฒ๋‹ˆ๋‹ค.

  • onUnlocked Prop์ด ์ •์˜๋˜์—ˆ์œผ๋ฉด, ๋ Œ๋”๋ง๋œ SlideToUnlock์€ onSlide Prop์œผ๋กœ onUnlocked๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • onUnlocked Prop์ด ์ •์˜๋˜์ง€ ์•Š์•˜์œผ๋ฉด, ๋ Œ๋”๋ง๋œ SlideToUnlock์€ onSlide Prop ์—ญ์‹œ undefined๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
describe("when `onUnlocked` is defined", () => {
  beforeEach(() => {
    props.onUnlocked = jest.fn();
  });

  it("sets the rendered `SlideToUnlock`'s `onSlide` prop to the same value as `onUnlocked`'", () => {
    const slideToUnlock = lockScreen().find(SlideToUnlock);
    expect(slideToUnlock.props().onSlide).toBe(props.onUnlocked);
  });
});

describe("when `onUnlocked` is undefined", () => {
  beforeEach(() => {
    props.onUnlocked = undefined;
  });

  it("sets the rendered `SlideToUnlock`'s `onSlide` prop to undefined'", () => {
    const slideToUnlock = lockScreen().find(SlideToUnlock);
    expect(slideToUnlock.props().onSlide).not.toBeDefined();
  });
});

์–ด๋–ค ์กฐ๊ฑด์—์„œ๋งŒ ์ผ์–ด๋‚˜๋Š” ๋™์ž‘์„ ๋ฌ˜์‚ฌํ•  ๋•Œ, ๊ทธ ์กฐ๊ฑด์— ๋Œ€ํ•œ ์„ค๋ช…์„ describe์— ๋„ฃ์–ด์ค๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , beforeEach๋ฅผ ํ†ตํ•ด ๊ทธ ์กฐ๊ฑด์„ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

์—ญ์ž ์ฃผ: describe์—๋Š” ํ…Œ์ŠคํŠธํ•˜๋ ค๋Š” ์กฐ๊ฑด์— ๋Œ€ํ•œ ์–˜๊ธฐ๊ฐ€, it์€ ๊ทธ ์กฐ๊ฑด์—์„œ ์–ด๋–ค ๊ฒฐ๊ณผ๊ฐ€ ์–ป์–ด์งˆ์ง€์— ๋Œ€ํ•œ ์„ค๋ช…์ด ๋“ค์–ด๊ฐ€์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์˜์–ด ๋ฌธ๋ฒ•์œผ๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ฝํžˆ๊ธฐ์— ์ผ๋ถ€๋Ÿฌ ๋ฒˆ์—ญํ•˜์ง€ ์•Š์•˜์ง€๋งŒ, ํ•œ๊ตญ์–ด๋กœ ์“ฐ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

describe("`onUnlocked` Prop์ด ์ •์˜๋˜์—ˆ์œผ๋ฉด", () => {
  beforeEach(() => {
    props.onUnlocked = jest.fn();
  });

  it("๋ Œ๋”๋ง๋œ `SlideToUnlock`์€ `onSlide` Prop์œผ๋กœ `onUnlocked`๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.", () => {
    const slideToUnlock = lockScreen().find(SlideToUnlock);
    expect(slideToUnlock.props().onSlide).toBe(props.onUnlocked);
  });
});

describe("`onUnlocked` Prop์ด ์ •์˜๋˜์ง€ ์•Š์•˜์œผ๋ฉด", () => {
  beforeEach(() => {
    props.onUnlocked = undefined;
  });

  it("๋ Œ๋”๋ง๋œ `SlideToUnlock`์€ `onSlide` Prop ์—ญ์‹œ `undefined`๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.", () => {
    const slideToUnlock = lockScreen().find(SlideToUnlock);
    expect(slideToUnlock.props().onSlide).not.toBeDefined();
  });
});
  • ๋งŒ์•ฝ wallpaperPath Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๊ฐ€์žฅ ๋ฐ”๊นฅ์˜ div๋Š” ๊ทธ ๊ฐ’์ด ๋ฌด์—‡์ด๋“  url(...)๋กœ ๊ฐ์‹ธ์—ฌ์ ธ์„œ background-image CSS property๋ฅผ inline ์Šคํƒ€์ผ๋กœ ๊ฐ€์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
describe("when `wallpaperPath` is passed", () => {
  beforeEach(() => {
    props.wallpaperPath = "some/image.png";
  });

  it("applies that wallpaper as a background-image on the wrapping div", () => {
    const wrappingDiv = lockScreen().find("div").first();
    expect(wrappingDiv.props().style.backgroundImage).toBe(`url(${props.wallpaperPath})`);
  });
});
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, TopOverlay๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€๋ฉด, ๊ทธ๊ฒƒ์€ TopOverlay์˜ children์œผ๋กœ ๋„˜๊ฒจ์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
describe("when `userInfoMessage` is passed", () => {
  beforeEach(() => {
    props.userInfoMessage = "This is my favorite phone!";
  });

  it("renders a `TopOverlay`", () => {
    expect(lockScreen().find(TopOverlay).length).toBe(1);
  });

  it("passes `userInfoMessage` to the rendered `TopOverlay` as `children`", () => {
    const topOverlay = lockScreen().find(TopOverlay);
    expect(topOverlay.props().children).toBe(props.userInfoMessage);
  });
});
  • ๋งŒ์•ฝ userInfoMessage Prop์ด ๋„˜๊ฒจ์ง€์ง€ ์•Š๋Š”๋‹ค๋ฉด, TopOverlay๋Š” ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
describe("when `userInfoMessage` is undefined", () => {
  beforeEach(() => {
    props.userInfoMessage = undefined;
  });

  it("does not render a `TopOverlay`", () => {
    expect(lockScreen().find(TopOverlay).length).toBe(0);
  });
});

์ด๊ฑธ๋กœ ๋ชจ๋“  Constraint์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์™„์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ์ตœ์ข… ํ…Œ์ŠคํŠธ ํŒŒ์ผ์€ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"์ด๊ฑด ๋‚ด ์ผ์ด ์•„๋‹Œ๋ฐ"

๊ธ€ ์ฒ˜์Œ์˜ Gif์„ ๋ณด์—ฌ๋“œ๋ ธ์„ ๋•Œ, ์—ฌ๋Ÿฌ๋ถ„์€ ํ…Œ์ŠคํŠธ๋“ค์ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์™„์„ฑ๋ ๊ฑฐ๋ผ ์˜ˆ์ƒํ•˜์…จ๋˜ ๋ถ„๋“ค์ด ์žˆ์œผ์…จ์„ ๊ฒ๋‹ˆ๋‹ค:

  • ์œ ์ €๊ฐ€ ๋ฐ€์–ด์„œ ์ž ๊ธˆํ•ด์ œ๋ฅผ ๋“œ๋ž˜๊ทธ ์˜ค๋ฅธ์ชฝ ๋๊นŒ์ง€ ํ•˜๋ฉด, unlock ์ฝœ๋ฐฑ์ด ๋ถˆ๋Ÿฌ์ง„๋‹ค.
  • ๋“œ๋ž˜๊ทธํ•˜๋Š” ๋„์ค‘์— ํ•ธ๋“ค์„ ๋†“์•„๋ฒ„๋ฆฌ๋ฉด, ํ•ธ๋“ค์€ ์›๋ž˜ ์œ„์น˜๋กœ ๋Œ์•„๊ฐ€๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋ณด์—ฌ์ค€๋‹ค.
  • ์Šคํฌ๋ฆฐ ์ตœ์ƒ๋‹จ์˜ ์‹œ๊ณ„๋Š” ์–ธ์ œ๋‚˜ ํ˜„์žฌ ์‹œ๊ฐ„์„ ๋ณด์—ฌ์ค€๋‹ค.

์ด๋Ÿฌํ•œ ์ƒ๊ฐ๋“ค์€ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฒ๋‹ˆ๋‹ค. ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด์ ์ธ ์‹œ์ ์—์„œ ๋ณด๋ฉด ๋งค์šฐ ๋‹น์—ฐํ•œ ๊ฒƒ๋“ค์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ์ด์— ๋Œ€ํ•ด ์•„๋ฌด๋Ÿฐ ํ…Œ์ŠคํŠธ๋„ ์“ฐ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์™œ๋ƒ๊ณ ์š”? ์ด๊ฑด LockScreen์ด ์‹ ๊ฒฝ์“ธ ๊ฒƒ์ด ์•„๋‹ˆ๋‹ˆ๊นŒ์š”.

React Coponent๊ฐ€ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ๊ฑด, Unit ํ…Œ์ŠคํŠธ๋“ค๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šธ๋ฆด ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ๊ทธ๋ฆฌ๊ณ  Unit ํ…Œ์ŠคํŠธ๋ฅผ ํ• ๋•Œ, ํ•ด๋‹น Unit์ด ์‹ ๊ฒฝ์จ์•ผ ํ•  ๊ฒƒ๋งŒ ํ…Œ์ŠคํŠธ ํ•˜์…”์•ผ ๋ฉ๋‹ˆ๋‹ค. React Component ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋• ์ˆฒ์„ ๋ณด๋Š” ๊ฒƒ๋ณด๋‹ค ๊ฐ๊ฐ์˜ ๋‚˜๋ฌด๋ฅผ ์‹ ๊ฒฝ์“ฐ์‹œ๋Š”๊ฒŒ ๋” ์ข‹์Šต๋‹ˆ๋‹ค.

๋Œ€๋ถ€๋ถ„์˜ React Component๋“ค์ด ์‹ ๊ฒฝ์จ์•ผ ํ•  ๊ฒƒ๋“ค์„ ์„ค๋ช…ํ•˜๋Š” ์ปจ๋‹ ํŽ˜์ดํผ๋ฅผ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค:

  • ๋ฐ›์€ Prop์œผ๋กœ ๋ญ˜ ํ• ๊ฑด๊ฐ€?
  • ์–ด๋–ค Component๋“ค์„ ๋ Œ๋”๋งํ•ด์•ผํ•˜๋‚˜? ๋ Œ๋”๋ง๋œ Component๋“ค์—๊ฒ ๋ฌด์—‡์„ ๋„˜๊ฒจ์ฃผ์–ด์•ผ ํ•˜๋Š”๊ฐ€?
  • State๋ฅผ ๊ฐ€์งˆ ํ•„์š”๊ฐ€ ์žˆ๋‚˜? ํ˜น์‹œ ๊ทธ๋ ‡๋‹ค๋ฉด, ์ƒˆ Prop์„ ๋ฐ›์•˜์„ ๋•Œ ์ดˆ๊ธฐํ™” ์‹œ์ผœ์•ผ ํ•˜๋Š”๊ฐ€? ์–ธ์ œ State๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด์•ผํ•˜๋‚˜?
  • ๋งŒ์•ฝ ํ•ด๋‹น Component๋‚˜ Child Component๋กœ ๋„˜๊ฒจ์ค€ Callback์ด ์œ ์ €์™€ ์ƒํ˜ธ์ž‘์šฉ์œผ๋กœ ๋ถˆ๋Ÿฌ์™€์ง„๋‹ค๋ฉด Component๋Š” ๋ฌด์—‡์„ ํ•ด์•ผ ํ•˜๋Š”๊ฐ€?
  • Mount๋˜์—ˆ์„ ๋•Œ ๋ฌด์—‡์ด ์ผ์–ด๋‚˜๋Š”๊ฐ€? Unmount๋˜์—ˆ์„ ๋•Œ๋Š”?

๊ณ ๋กœ, ์•ž์„œ ๋งํ•œ ๊ธฐ๋Šฅ๋“ค์€ SlideToUnlock์™€ ClockDisplay๊ฐ€ ์‹ ๊ฒฝ์จ์•ผํ•  ๊ฒƒ์ด๋ฏ€๋กœ ์—ฌ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฐ๊ฐ์˜ Component์˜ ํ…Œ์ŠคํŠธ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ• ๊ฒ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์ด๋Ÿฐ ๋ฐฉ๋ฒ•๋“ค์ด ์—ฌ๋Ÿฌ๋ถ„์ด React Component ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š”๋ฐ ๋„์›€์ด ๋˜๊ธธ ๋น•๋‹ˆ๋‹ค. ์š”์•ฝํ•˜์ž๋ฉด:

  • ์šฐ์„  Component์˜ Contract๋ฅผ ํŒŒ์•…ํ•ด๋ผ
  • ์–ด๋–ค Constraint๊ฐ€ ํ…Œ์ŠคํŠธํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š”์ง€ ์ •ํ•ด๋ผ.
  • Prop ํƒ€์ž…๋“ค์€ ํ…Œ์ŠคํŠธํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • Inline ์Šคํƒ€์ผ๋“ค์€ ์ผ๋ฐ˜์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ๋ Œ๋”๋งํ•˜๋ ค๋Š” Component๋“ค๊ณผ ๊ทธ๊ฒƒ๋“ค์—๊ฒŒ ๋„˜๊ฒจ์ฃผ๋Š” Prop๋“ค์€ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ• ๋งŒํผ ์ค‘์š”ํ•˜๋‹ค.
  • ํ•ด๋‹น Component๊ฐ€ ์‹ ๊ฒฝ์“ธ๊ฒŒ ์•„๋‹Œ ๊ฒƒ์€ ํ…Œ์ŠคํŠธ ํ•˜์ง€ ๋ง๋ผ.

๋งŒ์•ฝ ๋‹ค๋ฅธ ์ƒ๊ฐ์ด ์žˆ์œผ์‹œ๊ฑฐ๋‚˜, ์ด ํฌ์ŠคํŠธ๊ฐ€ ๋„์›€์ด ๋˜์…ง๋‹ค๋ฉด, Twitter๋กœ ์–˜๊ธฐํ•ด์ฃผ์„ธ์š”. React Component๋ฅผ ์–ด๋–ป๊ฒŒ ํ…Œ์ŠคํŠธํ•˜๋Š”์ง€ ๋ชจ๋‘ ํ•จ๊ป˜ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์„๊ฑฐ์—์š”.

Stephen Scott๋‹˜์€ ํ†ตํ•ฉ ์Šค๋งˆํŠธํ™ˆ ์ž๋™ํ™” ์‹œ์Šคํ…œ์„ ๋งŒ๋“œ๋Š” Nexia์˜ ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค. Nexia๋Š” ๊ณ ์šฉ์ค‘์ž…๋‹ˆ๋‹ค! Colorado์ฃผ Broomfield์— ์žˆ๋Š” ์‚ฌ๋ฌด์‹ค์—์žˆ๋Š” ์ €ํฌ ๊ฐœ๋ฐœ์ž ํŒ€์— ํ•จ๊ป˜ํ•˜๊ณ  ์‹ถ์œผ์‹œ๋‹ค๋ฉด Twitter๋กœ ์•Œ๋ ค์ฃผ์„ธ์š”.


์ด ๊ธ€์˜ ์ €์ž‘๊ถŒ์€ ๋ณดํ˜ธ๋ฐ›๊ณ  ์žˆ์ง€๋งŒ, ์ด ํฌ์ŠคํŒ…์˜ ๋ชจ๋“  ์ƒ˜ํ”Œ ์ฝ”๋“œ๋“ค์€ MIT ๋ผ์ด์„ผ์Šคํ•˜์— ๊ณต๊ฐœ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. GitHub์—์„œ ์ฐพ์œผ์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.