<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>river.log</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 개발자</description>
        <lastBuildDate>Sun, 02 Feb 2025 12:11:33 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>river.log</title>
            <url>https://velog.velcdn.com/images/jenn_n/profile/e38326c0-00ee-4c4a-8bdd-5966797b70e1/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. river.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jenn_n" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[nestJS]  인증]]></title>
            <link>https://velog.io/@jenn_n/nestJS-%EC%9D%B8%EC%A6%9D</link>
            <guid>https://velog.io/@jenn_n/nestJS-%EC%9D%B8%EC%A6%9D</guid>
            <pubDate>Sun, 02 Feb 2025 12:11:33 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. NestJS에서 인증을 구현하는 방법에 대해 알아보려고 합니다.</p>
<h2 id="인증과-인가-헷갈리시죠">인증과 인가, 헷갈리시죠?</h2>
<p>개발을 하다 보면 &#39;인증&#39;과 &#39;인가&#39;라는 용어를 자주 마주치게 되는데요, 이 둘은 비슷하면서도 명확히 다른 개념입니다.</p>
<p><strong>인증(Authentication)</strong>은 쉽게 말해 &quot;너 누구야?&quot;라고 물어보는 거예요. 우리가 서비스에 로그인할 때처럼, 사용자가 자신이 주장하는 사람이 맞는지 확인하는 과정입니다.</p>
<p><strong>인가(Authorization)</strong>는 &quot;너 이거 할 수 있어?&quot;라고 물어보는 거예요. 예를 들어, 관리자만 접근할 수 있는 페이지에 일반 사용자가 들어가려고 할 때 체크하는 것처럼요.</p>
<h2 id="다양한-인증-방식-살펴보기">다양한 인증 방식 살펴보기</h2>
<p>NestJS에서는 여러 가지 인증 방식을 사용할 수 있는데요, 각각의 특징을 살펴보면서 어떤 상황에서 어떤 방식을 선택하면 좋을지 이야기해보려고 합니다.</p>
<h3 id="jwt-json-web-token---요즘-가장-핫한-인증-방식">JWT (JSON Web Token) - 요즘 가장 핫한 인증 방식</h3>
<p>JWT는 요즘 가장 많이 사용되는 인증 방식인데요, 토큰 기반으로 동작합니다.</p>
<p>JWT는 마치 신분증같은 거예요. Header(신분증 형식), Payload(실제 정보), Signature(위조 방지 장치)로 구성되어 있죠. Base64로 인코딩되어 점(.)으로 구분된 문자열 형태로 만들어집니다.</p>
<h4 id="이렇게-동작해요">이렇게 동작해요</h4>
<ol>
<li>사용자가 로그인에 성공하면 서버가 JWT를 발급해줘요</li>
<li>이후부터는 사용자가 요청할 때마다 이 JWT를 들고 와요</li>
<li>서버는 JWT를 확인하고 유효하면 요청을 처리해줍니다</li>
</ol>
<h4 id="좋은-점">좋은 점</h4>
<ul>
<li>서버가 사용자 정보를 따로 저장할 필요가 없어요</li>
<li>모바일이든 웹이든 어디서나 똑같이 쓸 수 있어요</li>
<li>마이크로서비스 환경에서도 잘 동작해요</li>
</ul>
<h4 id="아쉬운-점">아쉬운 점</h4>
<ul>
<li>토큰에 정보가 많으면 요청할 때마다 데이터가 커져요</li>
<li>토큰을 탈취당하면 만료되기 전까지는 대응하기 어려워요</li>
</ul>
<h3 id="세션-기반-인증---전통적이지만-여전히-강력해요">세션 기반 인증 - 전통적이지만 여전히 강력해요</h3>
<p>세션은 전통적인 방식이지만, 여전히 많이 사용되고 있어요. 특히 서버에서 렌더링하는 웹사이트에서는 아직도 강력한 선택지죠.</p>
<h4 id="이렇게-동작해요-1">이렇게 동작해요</h4>
<ol>
<li>로그인하면 서버에 세션을 만들고 세션ID를 줘요</li>
<li>사용자는 쿠키에 이 세션ID를 저장해두고 요청할 때마다 보내요</li>
<li>서버는 세션ID로 사용자를 확인하고 요청을 처리해요</li>
</ol>
<h4 id="좋은-점-1">좋은 점</h4>
<ul>
<li>서버에서 완벽하게 제어할 수 있어요</li>
<li>문제가 생기면 바로 세션을 없앨 수 있어요</li>
<li>검증된 방식이라 안정적이에요</li>
</ul>
<h4 id="아쉬운-점-1">아쉬운 점</h4>
<ul>
<li>서버가 여러 대면 세션 동기화가 필요해요</li>
<li>사용자가 많아지면 서버 메모리를 많이 차지해요</li>
<li>다른 도메인과의 통신이 까다로워요</li>
</ul>
<h3 id="oauth-20---소셜-로그인의-표준">OAuth 2.0 - 소셜 로그인의 표준</h3>
<p>OAuth 2.0은 요즘 많이 보시는 &#39;구글로 로그인하기&#39;, &#39;카카오로 로그인하기&#39; 같은 기능을 구현할 때 사용하는 표준 프로토콜이에요.</p>
<h4 id="이렇게-동작해요-2">이렇게 동작해요</h4>
<ol>
<li>사용자가 &#39;구글로 로그인&#39; 같은 버튼을 클릭해요</li>
<li>구글 로그인 페이지로 이동해서 로그인하죠</li>
<li>우리 서비스가 사용자 정보를 받아올 수 있게 허용해주면</li>
<li>다시 우리 서비스로 돌아와서 자동으로 로그인이 완료돼요</li>
</ol>
<h4 id="좋은-점-2">좋은 점</h4>
<ul>
<li>사용자가 비밀번호를 따로 기억할 필요가 없어요</li>
<li>구글, 페이스북 같은 큰 기업의 보안을 믿을 수 있어요</li>
<li>이메일, 프로필 사진 같은 정보를 쉽게 가져올 수 있어요</li>
</ul>
<h4 id="아쉬운-점-2">아쉬운 점</h4>
<ul>
<li>외부 서비스에 의존하게 되요</li>
<li>구현이 좀 복잡한 편이에요</li>
<li>외부 서비스 연동이라 속도가 좀 느릴 수 있어요</li>
</ul>
<h3 id="api-키-인증---간단하지만-강력해요">API 키 인증 - 간단하지만 강력해요</h3>
<p>API 키는 주로 서버와 서버 사이, 또는 개발자용 API를 제공할 때 많이 사용하는 방식이에요.</p>
<h4 id="이렇게-동작해요-3">이렇게 동작해요</h4>
<ol>
<li>서비스에서 고유한 API 키를 발급받아요</li>
<li>API 요청할 때마다 이 키를 함께 보내요</li>
<li>서버는 키가 유효한지 확인하고 요청을 처리해요</li>
</ol>
<h4 id="좋은-점-3">좋은 점</h4>
<ul>
<li>구현이 정말 간단해요</li>
<li>키만 확인하면 돼서 처리가 빨라요</li>
<li>API 사용량을 제한하거나 모니터링하기 쉬워요</li>
</ul>
<h4 id="아쉬운-점-3">아쉬운 점</h4>
<ul>
<li>키가 유출되면 큰일 나요</li>
<li>키를 관리하는 게 생각보다 귀찮아요</li>
<li>세세한 권한 제어가 어려워요</li>
</ul>
<h2 id="실제로-한번-구현해볼까요">실제로 한번 구현해볼까요?</h2>
<p>자, 이제 NestJS에서 실제로 JWT 인증을 구현하는 방법을 알아볼게요. JWT를 선택한 이유는 현재 가장 많이 사용되는 방식이기도 하고, NestJS 공식 문서에서도 가장 자세히 다루고 있기 때문입니다.</p>
<h3 id="1-패키지부터-설치하기">1. 패키지부터 설치하기</h3>
<pre><code class="language-bash">npm install @nestjs/jwt @nestjs/passport passport passport-jwt</code></pre>
<h3 id="2-인증-서비스-만들기">2. 인증 서비스 만들기</h3>
<pre><code class="language-typescript">import { Injectable } from &#39;@nestjs/common&#39;;
import { JwtService } from &#39;@nestjs/jwt&#39;;

@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}

  async login(user: any) {
    const payload = { sub: user.id, username: user.username };

    // 실제 서비스에서는 이렇게 토큰 두 개를 발급해요
    return {
      accessToken: this.jwtService.sign(payload, { expiresIn: &#39;15m&#39; }),
      refreshToken: this.jwtService.sign(payload, { expiresIn: &#39;7d&#39; }),
    };
  }

  async validateUser(username: string, password: string) {
    // 실제로는 DB에서 사용자를 찾아야 해요
    if (username === &#39;test&#39; &amp;&amp; password === &#39;password&#39;) {
      return { id: 1, username };
    }
    return null;
  }
}</code></pre>
<h3 id="3-jwt-전략도-필요">3. JWT 전략도 필요</h3>
<pre><code class="language-typescript">import { Injectable } from &#39;@nestjs/common&#39;;
import { PassportStrategy } from &#39;@nestjs/passport&#39;;
import { Strategy, ExtractJwt } from &#39;passport-jwt&#39;;

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}</code></pre>
<h3 id="4-인증이-필요한-api-만들기">4. 인증이 필요한 API 만들기</h3>
<pre><code class="language-typescript">import { Controller, Get, UseGuards, Request } from &#39;@nestjs/common&#39;;
import { JwtAuthGuard } from &#39;./jwt-auth.guard&#39;;

@Controller(&#39;profile&#39;)
export class ProfileController {
  @UseGuards(JwtAuthGuard)
  @Get()
  getProfile(@Request() req) {
    return req.user;
  }
}</code></pre>
<h2 id="더-안전하게-만들기">더 안전하게 만들기</h2>
<p>실제 서비스에서는 보안을 더 강화할 필요가 있습니다. 간단히 아래와 같은 방법들이 있어요.</p>
<ol>
<li><strong>토큰 만료 시간을 짧게</strong>: 액세스 토큰은 15분, 리프레시 토큰은 7일 정도로 설정해요</li>
<li><strong>리프레시 토큰은 안전하게</strong>: Redis같은 곳에 저장해두고 관리하면 좋아요</li>
<li><strong>토큰 검증은 꼼꼼하게</strong>: 서명도 확인하고, 만료 시간도 확인하고!</li>
<li><strong>에러 처리도 중요해요</strong>: 인증 실패했을 때 친절한 메시지를 보여주세요</li>
<li><strong>HTTPS는 필수</strong>: 프로덕션 환경에서는 반드시 HTTPS를 사용해야 해요</li>
</ol>
<h2 id="마무리하며">마무리하며</h2>
<p>지금까지 NestJS에서 인증을 구현하는 여러 가지 방법을 알아봤는데요, 마무리로 인증 방식 선택 가이드를 가볍게 정리해보록 하겠습니다.</p>
<ul>
<li>REST API를 만든다면 JWT</li>
<li>전통적인 웹사이트라면 세션</li>
<li>소셜 로그인이 필요하다면 OAuth 2.0</li>
<li>마이크로서비스를 구축한다면 JWT + Redis</li>
</ul>
<p>실제로 구현하실 때는 프로젝트의 특성을 잘 고려해서 선택해야겠다는 생각이 듭니다~!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우아한테크코스 - 드디어 테코톡을 해치우다]]></title>
            <link>https://velog.io/@jenn_n/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%93%9C%EB%94%94%EC%96%B4-%ED%85%8C%EC%BD%94%ED%86%A1%EC%9D%84-%EC%A1%B8%EC%97%85%ED%95%98%EB%8B%A4</link>
            <guid>https://velog.io/@jenn_n/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%93%9C%EB%94%94%EC%96%B4-%ED%85%8C%EC%BD%94%ED%86%A1%EC%9D%84-%EC%A1%B8%EC%97%85%ED%95%98%EB%8B%A4</guid>
            <pubDate>Sun, 29 Sep 2024 14:29:41 GMT</pubDate>
            <description><![CDATA[<h3 id="1-피할-수-없는-테코톡">1. 피할 수 없는 테코톡</h3>
<p><strong>레벨4에 와서야 테코톡을 드디어 졸업하게됐습니다 😂 🎉</strong>
사실 레벨3 때 신청했었는데, 신청자가 많아서 떨어지게되서 레벨4에 하게되었네요. 하핫..</p>
<img src="https://velog.velcdn.com/images/jenn_n/post/603ec30e-6d66-4872-9fbe-6cfc9a456991/image.png" width="400px">

<p>제 발표 순서가 레벨4 첫 순서였는데 추석 연휴 끝난 바로 다음 날이어서 빨리 끝내는게 낫지라는 생각과 동시에 <del>아 내 추석 연휴</del>라는 생각도 드는건 어쩔 수 없었습니다..</p>
<p>제 테코톡을 향한 레벨3 팀원들의 (익명성에 뒤에 숨은)<del>돌림</del> 응원 덕분에 더 힘내서 준비 할 수 있었습니다. 😹</p>
<h3 id="2-주제-선정은-이미-했는데-비상이다">2. 주제 선정은 이미 했는데 비상이다</h3>
<p>테코톡 신청은 레벨4 초기에 이루어졌는데요, 이 때 성능 최적화 미션이 진행 중이었기 때문에 따로 학습할 기회도 가질 겸 &quot;이미지 성능 최적화&quot;로 발표 주제를 신청했었습니다. 그런데 문제는 이미지 성능 최적화 미션 후 서로 코드 리뷰, 그 후에 또 코치님의 너무 훌륭했던 피드백 수업이 있었습니다. 이미지 성능 최적화에 대해 10분동안 가볍게 훑는 발표를 하게되면 수업의 요약본 그 이상 그 이하도 안될게 뻔히 보였습니다. 그리고 이걸 테코톡에서 또 듣는 크루들이 너무 지루할것도 뻔했습니다.. 그래도 시간내서 봐주시는건데 단순 복습하는 느낌이 아니라 새롭고 도움되는 내용으로 발표 하고 싶었습니다.</p>
<p>그래서 연휴동안 급하게 주제를 좁히게 되었습니다.</p>
<h3 id="3-주제-변경">3. 주제 변경</h3>
<p>주제를 좁혀야할 것 같은데 <code>어떻게 좁힐것이냐?</code>에 대해서도 정말 많이 고민했었습니다. 성능 최적화 미션 중에 이미지를 반응형 크기별로 제공해야되는 부분도 있었는데, 이 부분을 수동으로 사이즈별 이미지를 준비한 크루들이 많았던게 기억나 이 과정을 자동화해주는 webpack plugin을 소개하면 좋을 것 같다는 생각이 들었습니다.</p>
<p>그래서 처음에 생각했던 것은 <code>단일 이미지를 활용한 성능 최적화</code>를 공통점으로 <code>단일 이미지로 반응형 이미지 생성 자동화, 단일 이미지로 이미지 요청 횟수 줄이기(이미지 스프라이트)</code>를 주제로 잡고 스크립트를 작성했었습니다.</p>
<p>그런데 준비하는 과정에서 <code>단일 이미지로 반응형 이미지 생성 자동화</code>부분에서 반응형 이미지는 그럼 몇 개로, 몇 사이즈로 준비해야되지? 라는 궁금증이 생겼고, 이 의문을 저뿐만 아니라 다른 여러 크루들이 했을 것 같았습니다. 그래서 다시 주제를 <code>단일 이미지를 활용한 반응형 이미지 최적화</code>로 좁혀서 앞의 내용을 추가하여 진행하게되었습니다.</p>
<p>10분이라는 분량 조절도 꽤 어려웠습니다. 이 내용도 추가하고싶은데 시간은 한정되어있고... 실제로 중간에 내용들을 많이 생략했는데 아쉬움이 남는 부분입니다.</p>
<blockquote>
<img src="https://velog.velcdn.com/images/jenn_n/post/e14e7882-d0d8-4d9d-9776-48e17cb967b2/image.png" width="400px" alt="쌓여만 가는 테코톡 스크립트...">
쌓여만 가는 테코톡 스크립트 🫠
</blockquote>
<p>이런 주제 변경때문에 추석 연휴인데도 급박하게 준비하게 되었습니다. 발표가 추석 연휴 다음 날이었기 때문에, 마지막 추석 연휴 아침에 디스코드에서 모의 발표를 해보게되었습니다.</p>
<blockquote>
<img src="https://velog.velcdn.com/images/jenn_n/post/92dce969-a8b6-4322-9d2a-5286c7da3ca2/image.png" width="300px">
성묘를 가는 와중에도 들어와서 발표 들어준 크루...
</blockquote>
<p><em>사진은 웃긴 사진으로 첨부하게되었지만</em>, 다들 잠이 덜 깬 목소리로 마이크를 켜고 좋은 피드백을 해줘서 도움이 많이 되었고 힘도 되었었습니다 👍👍👍 이 자리를 빌어 또 감사의 말씀을 전하고싶습니다..</p>
<h3 id="3-발표-당일--발표-후기">3. 발표 당일 &amp; 발표 후기</h3>
<p>저는 현재 레벨4에 잠실캠퍼스로 배정 받았는데요, 프론트엔드는 선릉캠퍼스에서 발표해되서 발표날 선릉캠퍼스에 오랜만에 등교하게되었습니다. 레벨1 온보딩조 크루들 10명 중 8명이 선릉캠퍼스 배정 받았기 때문에, 다들 오랜만에 본다는 기분에 기분 좋게 잠들었습니다. 그런데 막상 발표 당일 날 캠퍼스에 도착하니 좀 떨리기 시작하더니 발표 2시간 전부터는 진짜로 좀 떨리더라구요. 😣</p>
<p>발표 스크립트를 제대로 준비해서 스크립트를 안보고<code>절지 않고 발표하기</code>가 목표였는데, 발표 시간 30분 전 마지막으로 모의 발표해보는데도 꽤 절어서 걱정을 하다가 발표하게 됐습니다.</p>
<p>근데 막상 실전에서는 거의 안절은거 같아서 발표 끝나고 다행이다 싶었습니다 😄 다만 아쉬운 점은 제가 발표 스크립트 외울 때 PPT로 연상법으로 외우다보니 발표 내내 청중을 안보고 PPT만 뚫어져라 보게되더라구요.. 이거는 또 추후 발표 기회가 생기면 고쳐봐야되는 문제겠죠</p>
<blockquote>
<div style="display: flex; justify-content:  start;">
  <img src="https://velog.velcdn.com/images/jenn_n/post/7be68411-9f11-4dfb-8c9f-14a2b4243a5c/image.png" width="50%">
  <img src="https://velog.velcdn.com/images/jenn_n/post/0ee99248-da39-494c-b4a6-809ffeac1af1/image.png" width="50%">
</blockquote>
</div>
> 도움이 됐을까? 라는 걱정도 많이 했는데 발표 후 발표자료를 찾아준 크루들 덕분에 안도가 많이 되었습니다.. 고마워욥


<p>아직 유튜브 업로드까지는 멀은것 같지만 후기는 미리 남겨봤습니다ㅎ</p>
<p>정리해보자면</p>
<ol>
<li>크루들의 응원 너무 감사하다.</li>
<li>테코톡 준비한 나도 고생 많았다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[우아한테크코스 - 웹 접근성 미션 후기]]></title>
            <link>https://velog.io/@jenn_n/%EC%9B%B9-%EC%A0%91%EA%B7%BC%EC%84%B1-%EB%AF%B8%EC%85%98-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@jenn_n/%EC%9B%B9-%EC%A0%91%EA%B7%BC%EC%84%B1-%EB%AF%B8%EC%85%98-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 29 Sep 2024 13:24:19 GMT</pubDate>
            <description><![CDATA[<p>레벨4에서 맞이한 2번째 미션은 <strong>웹 접근성</strong>이었습니다.
사실 레벨1때도 미션 코드 리뷰에서 간혹 웹 접근성에 대한 피드백을 조금씩 받은적은 있지만, 그 뒤로 꽤 많은 미션을 해나가면서 따로 웹 접근성에대해서 신경 쓴 적이 거의 없었던 것 같습니다.</p>
<p>다행히 이번 미션을 하기 전에 사전미션을 경험한 덕분에 &quot;이렇게 구현하는게 맞나?&quot; 라는 생각을 하면서도 무난하게 해 볼 수 있었습니다.</p>
<h3 id="01-웹-접근성은-왜-중요한가">01. 웹 접근성은 왜 중요한가?</h3>
<p>이번 미션은 웹 접근성이구나하면서 시작하게된 미션이지만, 다들 이 생각부터 들었을거같습니다.
<code>결국 웹 접근성은 뭔가? 왜 중요한가? 왜 챙겨야하는가?</code>하는 의문입니다.</p>
<p>사실 이전에는 웹 접근성을 단순히 &quot;장애인을 위해 제공해야 하는 것&quot;으로만 인식하고 있었습니다. 하지만 <a href="https://web.dev/learn/accessibility/why?hl=ko">Web Dev - What is digital accessibility, and why does it matter?</a> 이라는 아티클을 읽고 나서, 제 생각이 얼마나 제한적이었는지 알 수 있었습니다. 웹 접근성은 <code>어느 상황에서,누가 사용해도</code> 이 사이트를 사용하는 최대한 많은 사용자들에게 동일한 경험을 제공하는 것 입니다. </p>
<p>예를 들어 노트북 터치패드가 갑자기 고장 난 경우, 키보드 접근성이 잘 구현된 웹사이트라면 문제없이 사용할 수 있습니다. 또 밝은 햇빛 아래에서 화면을 보기 어려운 상황에서도 적절한 색상 대비가 적용된 웹사이트는 읽기 쉬울 것입니다.
<code>어느 상황에서,누가 사용해도</code>에서 <code>누가</code>에는 사람뿐만 아니라 검색엔진 최적화(SEO) 봇도 해당됩니다. 접근성이 좋은 웹사이트는 SEO 봇이 더 효과적으로 크롤링할 수 있어, 검색 결과에서 더 좋은 순위를 얻을 수 있습니다.</p>
<p>웹 접근성을 <code>웹 접근성</code> 그 자체로 받아들이게 되는 아주 좋은 글이었습니다. 👍</p>
<h3 id="02-웹-접근성-개선후기">02. 웹 접근성 개선후기</h3>
<p><a href="https://github.com/woowacourse/a11y-airline/pull/100">웹 접근성 개선 미션 PR</a></p>
<p>웹 접근성 공부 필요성을 많이 느끼게되는 미션이었습니다. <code>lighthouse</code>에서도 웹 접근성 점수를 확인할 수 있고, <code>Accessibility Insights for Web</code> 같은 웹 접근성 검사 익스텐션도 있지만, 결국 개발자가 수동으로 체크해야되는 부분도 생각보다 많다고 느꼈기 때문입니다. 실제로 color-contrast같은 부분은 <code>colour contrast analyser</code>라는 프로그램을 통해 수동으로 체크해줘야했습니다. 또 tab키로 사용자가 접근할 수 있어야만하는 모든 요소에 알맞는 순서로 접근하는가도 결국 개발자가 수동으로 체크하는게 확실하다고 느꼈습니다. 이와 동시에 실제 서비스에서 웹 접근성을 어느 수준까지 챙겨가야하는가?에 대해서도 많은 고민이 들었습니다. 실제로 이번 미션에서 캐로셀 안쪽 부분에 </p>
<pre><code class="language-javascript">    &lt;p className=&quot;visually-hidden&quot;&gt;
        {`${totalCount}개의 여행 상품 중 ${currentIndex + 1}번 째 상품`}
      &lt;/p&gt;</code></pre>
<p>이렇게 hidden 요소를 넣어 현재 캐로셀이 몇 번째 캐로셀인지 안내해주는 요소를 넣어야했는데, 이 코드 추가를 몇 명의 개발자가 동의해줄까? 라는 생각이 잠깐 들기도했습니다. 그리고 그 상황에서 내가 잘 설득할 수 있는가? 어떻게 설득한 것 인가?하는 고민도 하게되었구요..</p>
<p>협업에서 기능 구현뿐만 아니라 웹 접근성에 대해서도 꽤 많은 대화가 오갈 수 있겠다라는 생각이 들었습니다.</p>
<h3 id="03-프로젝트-사이트의-접근성">03. 프로젝트 사이트의 접근성</h3>
<p>개선 작업을 마치고 레벨3 때부터 해오고있는 팀 프로젝트의 웹 접근성도 검사하게되었습니다. 프로젝트의 핵심 플로우를 생각해보고 그 플로우대로 웹 접근성이 잘 준비되어있는지 확인해본 것인데요
결과는 첫 플로우부터 막힌다였습니다 😅
애초에 메인 페이지에서 썸네일 카드를 키보드로 접근하지 못하더라구요. </p>
<p><img src="https://velog.velcdn.com/images/jenn_n/post/2a5934aa-bc3b-458c-97a9-c24bde5de34e/image.png" alt="">
사실 이전 성능 최적화 미션 진행할 때 <code>lightHouse</code>에서 <strong>Performance</strong> 점수만 체크해두고 확인했었는데요,
이제 <strong>Accessibility</strong> 점수도 확인해보니 제가 경험한 대로 좋지 않은 점수가 나오고있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/jenn_n/post/a6ccc87d-49d6-48fd-a873-9d3a50e7238b/image.png" alt="">
해당 과정에서 알게된 내용이있다면, 위 버튼처럼 배경색을 css로 주는 경우에는 color-contrast 테스트를 자동으로 할 수 있네요.</p>
<p><img src="https://velog.velcdn.com/images/jenn_n/post/2c829bfa-d44e-4cbb-8a0c-580277017841/image.png" alt="">
위 스샷처럼 뒤 배경이 image로 들어가는 경우는 색상 대비가 제일 낮은 부분을 수동으로 직접 찾아서 테스트해봐야하는 것 같습니다.</p>
<hr>
<h3 id="접근성-체크리스트-v1">접근성 체크리스트 v1</h3>
<p><strong>모든 페이지 공통 요소</strong></p>
<ul>
<li><input disabled="" type="checkbox"> 헤더의 버튼 및 drawer를 건너 뛸 수 있도록 본문으로 바로가기 링크를 제공한다.</li>
<li><input disabled="" type="checkbox"> aria를 제공할 이미지와 아닌 이미지를 구별하여 적절히 정보를 제공한다.</li>
<li><input disabled="" type="checkbox"> 포커스되어야하는데 포커스되지 않는 요소가 있는지 확인한다.</li>
</ul>
<p><strong>메인 페이지</strong></p>
<ul>
<li><input disabled="" type="checkbox"> 카드 컴포넌트를 선택하게되면 안에 개별 요소로 타고 들어가지 않고 제목,작성자,태그,좋아요 수 정보를 aria를 통해 알 수 있어야된다.</li>
<li><input disabled="" type="checkbox"> 키보드 사용자는 키보드를 통해 정렬,필터를 선택할 수 있어야하고 현재 선택된 정렬,필터가 뭔지 알 수 있어야한다.</li>
</ul>
<p><strong>여행 계획으로 변환</strong></p>
<ul>
<li><input disabled="" type="checkbox"> 제목이 20자 제한으로되어있는데 20자보다 넘게 입력할 시 20자가 초과됐다는 사실을 알 수 있어야한다.
<strong>장소 검색</strong><ul>
<li><input disabled="" type="checkbox"> 장소 input에 포커스가 됐을 경우 그 밑에 tip 내용도 같이 음성으로 안내하도록 한다.</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> 장소 추가 후, 요소 포커스가 그 장소 아코디언의 할일 추가에 가있도록 한다. </li>
</ul>
<hr>
<p>그 외에도 스크린 리더로 핵심 플로우를 빠르게 훑으면서 간단히 적어본것들인데 많죠 😅 
이 외에도 디테일하게 생각해봐야될 것들이 많을 것 같습니다. 다음 미션은 팀 프로젝트 개선 미션이니 이 과정에서 또 같은 팀 크루들과 많은 이야기를 나눠봐야할 것 같습니다!
그 전에는 안보이던것,볼 생각도 못했던 것들에 대해 또 생각해보고 개선할 수 있게 되어 기분이 좋네요 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우테코 레벨3 방학 보내기 - 라이브러리 제작 몰입기]]></title>
            <link>https://velog.io/@jenn_n/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A0%88%EB%B2%A83-%EB%B0%A9%ED%95%99-%EB%B3%B4%EB%82%B4%EA%B8%B0-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%A0%9C%EC%9E%91-%EB%AA%B0%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@jenn_n/%EC%9A%B0%ED%85%8C%EC%BD%94-%EB%A0%88%EB%B2%A83-%EB%B0%A9%ED%95%99-%EB%B3%B4%EB%82%B4%EA%B8%B0-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%A0%9C%EC%9E%91-%EB%AA%B0%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Mon, 09 Sep 2024 07:19:41 GMT</pubDate>
            <description><![CDATA[<p>레벨3을 무사히 잘 끝마치고 방학을 맞이하게됐습니다 😊
방학이 되면 뭔가 만들어보자는 생각은 하고있었는데요,
방학 기간이 대략 10일 정도였기 때문에 간단한(?) 라이브러리를 만들어보면 좋겠다 라는 생각을 했습니다.
<del>(이 때는 &#39;간단한&#39;인 줄 알았어요....🥲)</del></p>
<p>마침 굉장히 인상 깊게 봤던 영상이 있었는데요</p>
<p><a href="https://www.youtube.com/watch?v=Jq4MzgzSDEE">토스ㅣSLASH 23 - Rally로 3분 만에 애니메이션 완성하기</a>
바로 이 영상입니다</p>
<p>framer motion 공식문서 1회독 및 예제 연습이 어느정도 끝난 시점이었기 때문에 framer motion으로 만들어보면 좋겠다라는 생각이 들었습니다.</p>
<p>처음에는 오히려 설계에 일단 너무 생각하지 말고 0.0.1ver부터 만든 생각으로 조금씩 구현해보자 라는 생각으로 차근차근 개발을 해 나갔습니다.</p>
<p>한 번 개발하기 시작하니 기존에 학습하던 타입스크립트,framer motion 예제 컴포넌트 구현은 다 제껴두게되었네요 😅</p>
<p>결론적으로 말씀드리면 방학동안 다 구현해내지는 못했습니다. 디버깅을 4일동안 붙잡고있었던적도 있었고, 이거 때문에 디버깅하는 꿈까지 2번인가 꿨네요...</p>
<p>&#39;도대체 이 부분을 어떻게 구현해야지?&#39;하는 관문도 많았고, 아직까지 해결하지 못한 부분도 있습니다.</p>
<h3 id="라고-했는데-일단-어제-새벽에-해결했습니다-2주만에"><strong>라고 했는데 일단 어제 새벽에 해결했습니다 2주만에....!</strong></h3>
<p>글을 쓰기에 앞서 Domino 프로젝트 설명을 간단하게 해보겠습니다. 인터렉티브 개발 과정에서 겪는 애니메이션 </p>
<ul>
<li>Timeline,</li>
<li>Domino(Rally에서의 Rally에 해당되는 부분의 이름을 Domino로 수정했습니다. motion과 motion의 주체가되는 target을 연결해주는 부분입니다.) </li>
</ul>
<h3 id="1-첫-번째-구현">1. 첫 번째 구현</h3>
<p>animate 스케줄링을 어떻게 할것인가? 라고 생각했을 때, animateSequence를 props로 받는 <code>useAnimate</code>를 사용되겠다 라는 생각을 했습니다. TimeLine이 자기가 받는 Domino,TimeLine의 animation sequence, playCount들을 모두 모아서 그것들을 combine해서 다룰려 했습니다. </p>
<p>모든 animation sequence을 한 control 안에 모으는 것이기 때문에 구현도 쉽게 되고 animation sequence들을 다루기에도 쉬웠습니다.
하지만 playCount가 2 이상이면 그 횟수 만큼 animation sequence의 배열 길이가 늘어나고, Infinity인 경우는 다를 수 없었습니다. 그래서 다시 수정하게됐습니다. 이 때 구현 사항의 70퍼정도까지 갔다고 생각했고 방학 3~4일차였는데 다시 거의 처음으로 다시 회귀하기로합니다 🥹</p>
<h3 id="2-두-번째-구현">2. 두 번째 구현</h3>
<p>그래서 Domino가 개별적으로 animation control을 갖도록 시도했습니다. 그래서 Domino가 개별적으로 play,playReverse,pause 등등 필요한 함수들을 구현하고 TimeLine에서 Domino의 함수들에 접근해서 TimeLine의 play,playReverse,pause같은 함수들을 구현하기로했습니다. 구현 방식에 문제가 없다고 생각하여 개발을 이어나가는데 계속 Domino 안에서 playCount만큼 반복 재생이 되질 않았습니다.</p>
<p>playCount만큼 repeat 횟수를 넣어도 1회만 재생 후, 다음 애니메이션으로 넘어가는 문제가 계속 있었습니다. playCount를 여러번 넣어줘도 framer motion의 animation control이 자신의 애니메이션이 끝나는 시점을 제대로 파악하질 못하는건가? 싶기도했습니다. repeat 빼고는 다른것들은 다 제대로 적용되었기에 ( duration, 애니메이션의 target과의 연결 등등) framer motion의 animation control의 문제인가? 라고 잠정 결론을 내렸습니다. (검색해도 저와 같은 문제를 겪는 사람이 딱히 나오지 않았지만...🥹) 이미 이 때 방학이 모두 끝나버린 상황이었습니다.</p>
<p>어렵다고 생각하지 않은 부분에서 4일 이상의 시간이 소요되니 정말 현타가 오더라구요... 심지어 디버깅하는 꿈까지 꾸기 시작했습니다 🫠 결국 playCount만큼 for문을 돌리고 그 안에서 그 때마다 새 animation control을 만드는 방식으로 어찌저찌 구현하게됐습니다.. </p>
<p>이미 Domino의 play 함수에서 playCount만큼 새로운 control을 생성하고있기 때문에 Domino 인스턴스들이 개별 animation control을 상태로 가지고있게하냐 마냐도 고민을 많이했습니다. 이미 이때 한 animation control로 playCount만큼 재생되지 않고 다음 애니메이션으로 넘어가는 이슈때문에 animation control의 비동기적인 속성을 다루는데 매우 힘들다는 생각을 하고있었기 때문에 모든 함수들(play,seek,pause 등등)을 실행 할 때 마다 새 playCount를 만들고, 그 대신 duration,currentTime 같은 값들을 상태로 가지고있게했습니다. </p>
<h3 id="3-타입-단언의-위험성-2주동안-고생이ㅎ">3. 타입 단언의 위험성 (2주동안 고생이...ㅎ)</h3>
<p>일단 구현은 되긴했지만(?) 진짜 for문으로 그 횟수만큼 돌린다는게 너무 코드가 보기 싫었고, for문 마다 새 animate control을 만든다는게 너무 마음에 들지 않았습니다. 일단 다른건 다 되는데 repeat만 왜 안되는지가 너무 답답하고 궁금했습니다..</p>
<p>결론부터 말하면 제가 Domino,TimeLine에서 모아서 정리한 animation segment들이 animate안에
<code>animate(&#39;.red&#39;,{x:100},{repeat:2})</code> 이렇게 들어가고있지 않고 <code>animate([&#39;.red&#39;,{x:100},{repeat:2}])</code> 이렇게 배열에 한번 쌓여서 들어가고있었습니다. animation segment들 정리 과정에서 타입 단언을 사용했었는데, 그 당시에 동작을 하니 타입 단언을 해도 잘 들어가는구나 싶어서 아예 생각지도 못한 부분이었습니다........
이걸 2주만에 발견한것이죠 🥹🥹🥹🥹🥹🥹🥹🥹🥹🥹🥹🥹🥹🥹🥹🥹
아예 애니메이션이 재생 안됐으면 모를까 다른건 다 되는데 repeat만 안되니깐 진짜 이 부분에 대해서 아예 생각지도 못했습니다.</p>
<h3 id="4-결론">4. 결론</h3>
<p>타입 단언이 정말 위험하다 위험하다 말로만 들었지 이렇게 정말 큰 코 다칠지 몰랐습니다... 타입스크립트 공부 후에 이렇게 호되게 데이니 감회가 더 새롭습니다.. 정말 허탈하기도하면서도 앞으로 타입 단언 사용할 때 더 조심히 사용할 수 있을거같습니다 ...!! 2주동안 디버깅하는 꿈도 여러번 꾸면서 마음 고생은 했지만 그래도 해결이 되서 기쁜 마음이 더 크긴하네요</p>
<p>제일 신경쓰이던 부분이 디버깅됐으니 마저 또 열심히 구현해 나가야겠습니다..!</p>
<p>일단 stop부분에만 다시 또 버그를 발견해서 이 부분만 수정하고,
TimeLine에 playback 추가, 토큰들 추가정도하고 리팩토링 및 테스트 추가 진행하면 될 것 같습니다.</p>
<p>중간에 진짜 디버깅 애초에 불가능한것을 내가 붙잡고있는건가? 일단 잠정 멈춰 놓는 상태로 둘까 정말 많이 고민했었는데, 그래도 붙잡고있다가 이렇게 디버깅되니 관두질 않길 잘 했다 싶습니다..</p>
<p>그럼 이만 글을 줄이겠습니다 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Framer motion 1주차 학습 회고 (사실 한달이상 질질 끈...)]]></title>
            <link>https://velog.io/@jenn_n/Framer-motion-1%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%ED%9A%8C%EA%B3%A0-%EC%82%AC%EC%8B%A4-%ED%95%9C%EB%8B%AC%EC%9D%B4%EC%83%81-%EC%A7%88%EC%A7%88-%EB%81%88</link>
            <guid>https://velog.io/@jenn_n/Framer-motion-1%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%ED%9A%8C%EA%B3%A0-%EC%82%AC%EC%8B%A4-%ED%95%9C%EB%8B%AC%EC%9D%B4%EC%83%81-%EC%A7%88%EC%A7%88-%EB%81%88</guid>
            <pubDate>Sun, 18 Aug 2024 13:20:30 GMT</pubDate>
            <description><![CDATA[<h3 id="0-서론">0. 서론</h3>
<p>인터렉티브 개발자가 되자!라고 다짐한 후, 첫 학습 목표로 잡은 것은
<code>Framer motion</code>입니다.</p>
<p>솔직하게 말하자면 framer motion, react spring, gsap 등등 있었는데 검색해보니 <code>framer motion</code>이 원체 이쪽에서 유명하기도 하고, 리액트와 호환성도 높고,..도 있지만</p>
<p>크루에게 뭐부터 배워볼까요?했을 때 </p>
<blockquote>
<p><del>framer motion? 저 framer motion 배워볼려다가 말아서..</del></p>
</blockquote>
<p>이런 답변을 받아서 &quot;아 그래요? 그럼 framer motion 부터 해봐야지&quot;
하고 바로 채택했습니다.. 😅</p>
<h3 id="1-오직-공식문서만이-존재한다">1. 오직 공식문서만이 존재한다</h3>
<p>솔직하게 말하자면 저는 리액트도 그렇고 처음 학습할 때 공식문서부터 보는 사람은 아니었습니다.</p>
<blockquote>
<p>공식문서? 영어 읽기싫어, 한글 공식문서있어도 공짜인 공식문서보다는 돈 받고 파는 유명한 책,인강의 내용 더 좋을거야(?)</p>
</blockquote>
<p>라는 생각에 찌들어있던 사람이기때문입니다.
이런 저를 벌하는걸까요(?)
학습을 위해 Framer motion 책, 강의를 찾아보는데, 아무것도 나오지 않았습니다.. 진짜 udemy에는 있을 줄 알았는데 여기에도 없었습니다 😳</p>
<p>최후의 보루로 한글 번역 공식문서 github라도?! 하고 찾아보는데 이 역시 없었습니다</p>
<p>그렇게 공식 문서 번역 외길을 걷기 시작했습니다
사실 이 때 계속 번역하면서도 &#39;이게 맞나?&#39; 라는 생각을 계속 했던것 같습니다. 인공지능으로 복붙해가며 머리 비우고 번역을 돌리면서, 분명 이 번역본이 있어야 학습할 수 있는거 알면서도 이 시간이 아깝게 느껴졌습니다.
그래도 끝까지 꾸역꾸역 번역을 돌려 완성한것이 아래 레포지토리입니다.</p>
<blockquote>
<p><a href="https://github.com/0jenn0/framer-motion-docs-ko">Framer motion 공식문서 번역 레포지토리</a></p>
</blockquote>
<p>지금 현시점에서 생각해보면, 공식문서로 제대로 학습해본적도 없는 저였기에 제가 돌린 번역본이 제 학습에 끼칠 영향력을 낮게 봤기에 쓸모 없는 시간을 보내고있는 것 같다라고 느꼈던 것 같습니다.</p>
<p>이렇게 간신히 만들어낸게 무려 <strong>4주전</strong> 입니다..
번역본을 만들어 놓고도 3주를 계속 질질 끈것이죠.</p>
<p>사실 강의,책을 보면 학습 순서도 다 정해져있고 그에 맞춰서 그 적절한 난이도의 예제 코드도 자세한 설명과 함께 다 서술되어있잖아요?</p>
<p>이게 없다보니 어떻게 해야될지도 모르겠고, 우테코 생활이 바쁘다는 핑계를 대며, 질질 끌며 고민만 하는 시간만 보냈습니다.</p>
<h3 id="2-나의-구원자-캐러셀✨">2. 나의 구원자 캐러셀...✨</h3>
<p>여기서 부터 정말 1주차 학습의 시작점은 캐러셀 구현이라고 생각합니다.</p>
<blockquote>
<p><a href="https://codesandbox.io/p/sandbox/framer-motion-image-gallery-pqvx3?from-embed=">Framer motion | AnimatePresence 예제 - 캐러셀 컴포넌트</a></p>
</blockquote>
<p><code>AnimatePresence</code>가 Framer motino의 첫 부분도 아니었지만 이 에제를 첫 구현해보기 목표로 잡은 이유는 우테코 레벨3 프로젝트에서 개발 첫 주에 제가 맡은 부분이 캐러셀 컴포넌트였기 때문입니다.</p>
<p>첫번 째 주에 거의 mvp 구현 거의 끝난거 같은데? 라는 말이 나올 정도로 말그대로 갈아졌었습니다. 
이 때 그래서 사실 클로드에 거의 업혀서 구현을 서둘러 하고 시간 문제상 코드리뷰도 못하고 바로 머지하는 경험을 했습니다.
프로젝트의 최우선은 시간내 구현이라고 생각했기에 어쩔 수 없다고 생각하면서도 허탈한 감정이 드는 것도 어쩔 수 없더라구요.
이 때 생각이 나서 여기서라도 캐러셀 구현을 스스로 똑같이 해봐야겠다고 다짐했습니다.</p>
<h3 id="3-캐러셀-구현기">3. 캐러셀 구현기</h3>
<p>우테코에서 배웠던 문제 쪼개기가 여기서 도움이 많이 됐습니다.
처음에는 구현하고자하는 핵심 기능 ver0.0.1을 만들어보는 것입니다.</p>
<ol>
<li>일단 버튼을 누르면 이미지가 다음 이미지가 렌더링되게한다.</li>
<li>현재 이미지가 언마운트될때, 다음 이미지가 마운트될 때 애니메이션이 동작하게한다.</li>
<li>드래그했을 때도 이미지가 넘어가도록 한다.</li>
</ol>
<p>1번은 쉽게 구현했고, 2번 기능을 적고나서
이게 왜 <code>AnimatePresence</code>의 첫 번째 예제인지 알겠다라는 생각을 했습니다. <code>AnimatePresence</code>는 컴포넌트의 moun,unmount를 포착하여 애니메이션 구현을 해줄 수 있는 컴포넌트이기때문입니다.</p>
<p>3번도 공식문서에 검색으로 바로 나왔기에 쉽게 구현할 수 있었습니다.
예전이긴하지만 구현하기 전에 공식문서를 얇게라도 한 번 훑어봤던 것도 도움이 많이 된 것 같습니다.</p>
<p>첫 번째 예제가 <code>AnimatePresence</code>에서 가장 복잡한 예제였기 때문에 나머지 예제들도 금방 구현할 수 있었습니다.</p>
<blockquote>
<p><a href="https://github.com/0jenn0/framer-motion-practice/tree/main/src/components/2.Components/2-2.AnimatePresence/1_r/self">AnimatePresence - 캐러셀</a></p>
</blockquote>
<h3 id="4-함께-학습한-타입스크립트가-도움이">4. 함께 학습한 타입스크립트가 도움이</h3>
<p><code>Reorder.Group</code>의 <code>onReorder</code>에는 setState 함수를 전해줘야합니다.하지만 바로 전해주면</p>
<pre><code>&#39;(newOrder: Ingredients[]) =&gt; void&#39; 타입은 &#39;(newOrder: unknown[]) =&gt; void&#39; 타입에 할당할 수 없습니다.
&#39;newOrder&#39;와 &#39;newOrder&#39; 매개변수의 타입이 호환되지 않습니다.
&#39;unknown[]&#39; 타입은 &#39;Ingredients[]&#39; 타입에 할당할 수 없습니다.
&#39;unknown&#39; 타입은 &#39;Ingredients&#39; 타입에 할당할 수 없습니다.</code></pre><p>위와 같은 에러가 뜹니다. Framer Motion은 자신이 재정렬할 요소들의 타입을 알 방법이 없기에 onReorder prop의 타입을 (newOrder: unknown[]) =&gt; void로 정의하고 있기 때문입니다.</p>
<pre><code class="language-javascript">const handleReorder = (newOrder: unknown[]) =&gt; {
  if (newOrder.every((item): item is Ingredients =&gt;
    typeof item === &#39;object&#39; &amp;&amp; item !== null &amp;&amp; &#39;icon&#39; in item &amp;&amp; &#39;label&#39; in item
  )) {
    setItems(newOrder);
  }
};</code></pre>
<p>따라서 위와 같이 타입을 맞춰서 <code>handleReorder</code>함수를 만들어줬습니다.  우아한 리액트 타입스크립트에서 &quot;오오~&quot;하며 읽었던 부분(<code>A is B</code> 사용하는 예제)이라 여기서 딱 배운 내용을 사용할 수 있어서 좋았습니다.
사실 타입스크립트,framer motion,우테코의 프로젝트.. 제가 너무 학습면에서 욕심을 내서 오히려 덜하는것 못하는 output을 내는게 아닌지 걱정하는 부분도 있었는데 이 걱정을 사라지게 해주는 경험이어서 기억에 남습니다.😊</p>
<h3 id="5-예제-컴포넌트가-없으면-간단한거라도-만들어보기">5. 예제 컴포넌트가 없으면 간단한거라도 만들어보기</h3>
<blockquote>
<p><a href="https://www.framer.com/motion/use-transform/">Framer motion 공식문서 - useTransfrom</a></p>
</blockquote>
<p>위 링크처럼 예시 코드는 있지만 컴포넌트 예제는 아예 없는 파트도 있습니다.
뭔가 생각해서 눈에 보이게 구현할 때 학습이 잘되는 편이기때문에
간단한 예제 컴포넌트라도 구현해봤습니다</p>
<p>useTransfrom이 motionValue를 받아서 mapping도 해줄 수 있기 때문에, useVelocity값으로 요소가 드래그되는 속도를 받아서 이 값을 1~2사이로 mapping해서 속도가 높아지면 요소가 최대 2배까지 커지는 컴포넌트를 구현해보았습니다</p>
<img src ="https://velog.velcdn.com/images/jenn_n/post/d667788b-1277-40df-9a83-7fc41f6b41a5/image.gif" />

<p><a href="https://github.com/0jenn0/framer-motion-practice/tree/main/src/components/3.MotionValues/3-6.useTransform">github | useTransform 간단 예제 만들어보기 </a></p>
<h3 id="6-아쉬운-점">6. 아쉬운 점</h3>
<p>사실 커밋을 위에서 문제쪼개기처럼 단계적으로 하지 못했습니다.
이번 주에 구현한 거의 모든 예제를 이미 구현을 다 마친후 한번에 commit했습니다.
단계적으로 커밋한다면 제 사고과정을 나중에도 알기 쉬울거같아서 아쉬게 생각하는 부분입니다.</p>
<h3 id="7-다음-주-학습-액션플랜">7. 다음 주 학습 액션플랜</h3>
<ul>
<li>1.예제를 보며 이 예제가 왜 이 학습파트의 예제가 됬는지 생각해보기</li>
<li>2.예제가 없더라도 간단하게 만들어보기</li>
<li>3.commit을 좀 더 쪼개서 하기 </li>
</ul>
<p>이번주에 이미 실행한 액션플랜 1,2번에 아쉬웠던 부분을 보완할 3번을 추가해서 3가지로 정리해보았습니다 ✏️</p>
<h3 id="8-마무리--경험으로-얻은-것">8. 마무리 (+ 경험으로 얻은 것)</h3>
<p>인터렉티브 개발자가 되고싶어서 시작한 Framer motion 학습이었지만,
Framer motion 학습 내용뿐만 아니라 앞으로 제 개발 학습에 영향을 크게 끼칠만한 경험들도해서 정말 뜻깊었습니다.</p>
<ol>
<li>한 번에 너무 여러개 공부하는게 아닐까? 했는데 오히려 <strong>시너지 효과</strong>를 얻었다. (framer motion + typescript)</li>
<li>공식문서 학습 공포증을 이겨냈다.(공식문서 학습 루틴을 어떻게 가져가야할지 대충 감이 잡히고 자신감을 얻음)</li>
</ol>
<p>1번때문에 공부 범위를 줄여야되나?하고 고민하고있다가 오히려 자바스크립트 공부까지 추가할까 진지하게 고민하고있네요 😅</p>
<p>글이 많이 길어진것 같은데 이번 회고는 여기서 마무리해보도록하겠습니다
읽어주셔서 감사합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 챌린지 2주차 학습 회고]]></title>
            <link>https://velog.io/@jenn_n/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%B1%8C%EB%A6%B0%EC%A7%80-2%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jenn_n/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%B1%8C%EB%A6%B0%EC%A7%80-2%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 18 Aug 2024 11:29:40 GMT</pubDate>
            <description><![CDATA[<h3 id="0-잡설">0. 잡설</h3>
<p>아아 시간이 정말 빠르네요..
마지막 데모데이 준비를 하며 이번 주에도 타입스크립트 챌린지를 꼭 챙기려 노력했습니다 😆</p>
<p>아니 근데 typescript challenge 익스텐션이 있더라구요..?
크루덕분에 알게되서 요긴하게 썼습니다.
확실히 typescript challenge 깃허브 들어가는거보다는 훨씬 편했습니다
다만, <strong>에러 디버깅 유무와 상관 없이</strong> 저장만하면 무조건 풀었다고 체크되는 문제가 있었습니다.. 빨간줄 없어지기 전까지는 저장 누르지 않기 하하</p>
<h3 id="1-이번-주-공부">1. 이번 주 공부</h3>
<img src="https://velog.velcdn.com/images/jenn_n/post/fab2bcd2-3325-42c8-b5c6-01c0c0e650aa/image.png" width="300">

<p>2주차 학습내용에는 템플릿 리터럴 비중이 높았습니다.
문자타입 제네릭 매개변수를 템플릿 리터럴로 분리할 수 있다라는걸 템플릿 리터럴 첫 문제에서 확인하고 그 뒤로는 거의 비슷해서 템플릿 리터럴 문제는 거의 다 무난했습니다. 반면에 &quot;정말 어렵다..!&quot; 라고 느낀 문제들도 분명 있었습니다. </p>
<h3 id="2-메타인지">2. 메타인지</h3>
<blockquote>
<p>1.타입스크립트 챌린지처럼 코딩 문제를 풀게 될 경우, 답안을 보고나서 고대로 다시 적어냈다고 이해했다고 착각하지 말기.</p>
<ol start="2">
<li>개발 용어를 정확하게 머리 속에 넣자.</li>
</ol>
<ol start="3">
<li>애매모호한 용어 사용으로 질문을 하게 될 경우 내 질문을 정확한 개발용어를 사용해서 수정해달라고 요구하기.</li>
</ol>
</blockquote>
<p>빼놓을 수 없는 메타인지 파트입니다.
일단 1주차 때 잡았던 메타인지 액션플랜을 가져와봤습니다.</p>
<p>일단 <code>2.개발 용어를 정확하게 머리 속에 넣자</code> 부터 확인해보겠습니다.</p>
<p>일반 난이도 문제를 풀면서 느낀거지만 타입스크립트 챌린지의 쉬움 난이도 13문제가 정말 잘 뽑혔다고 생각합니다.
정말 기본적으로 알아야되는것들. union,intersection 부터 해서 extends,infer, 조건부 타입, as를 사용한 조건부타입, 조건부에서 튜플과 객체 타입 순환 방법까지 가장 기본적인 것들을 모두 다루기때문입니다.</p>
<p>그래서 사실 <code>개발 용어 사용 측면</code>에서 2주차 안에서 크게 문제될 부분이 없었습니다.
크게 추가된 개발 용어가</p>
<ul>
<li>템플릿 리터럴</li>
</ul>
<p>이 정도 인것 같습니다.</p>
<p>그렇기에 <code>3. 애매모호한 용어 사용으로 질문을 하게 될 경우 내 질문을 정확한 개발용어를 사용해서 수정해달라고 요구하기.</code> 이 액션플랜을 사용할 일도 거의 없었습니다.</p>
<p>다시 한번 쉬움 난이도 문제를 찬양(?)하게 되네요..👍👍👍</p>
<p>사실 제일 어려운 액션플랜은
<strong><code>1.타입스크립트 챌린지처럼 코딩 문제를 풀게 될 경우, 답안을 보고나서 고대로 다시 적어냈다고 이해했다고 착각하지 말기.</code></strong>
라고 생각합니다.</p>
<p>지금보니 애초에 액션플랜이라고 할 수가 없네요..
착각하지말기. 그래서 어떻게? 어떤 행동으로?가 없기 때문입니다.</p>
<p>다행히도 이번주에 이 액션플랜 부제에 대한 해답을 찾아낼 수 있었습니다</p>
<h4 id="2-1-속성-과외를-하게되다">2-1. 속성 과외를 하게되다</h4>
<p>사실 제가 타입스크립트 쉬움 난이도 13문제를 떠벌떠벌 찬양하면서
친한 사람에게 쉬움 난이도 속성 과외 요청이 들어왔습니다.</p>
<p>1주차 때 쉬움 난이도 문제를 여러번 (많이 푼 문제는 4~5번도 풀었습니다) 풀었기에 자신만만한 상태였고 바로 좋아!를 외치고 카페에서 모였습니다.</p>
<p>상대방에게 일단 정확한 사실을 알려줘야되다보니 제가 스스로 체킹하는것과 차원이 다르더라구요.</p>
<p>쉬움 난이도 문제를 일주일동안 여러번 풀어보면서 머리 속에 개념이 잘 잡혀가고있는것 같다라고 느끼긴했는데,이 일주일보다 이 속성과외 시간 30분에 느낀게 훨씬 컸습니다.</p>
<p>아마 생각이라는거 자체는 정제된 문장으로 한다기보다는, 단어,동사의 엉성한 연결정도인데, 말은 말그대로 완성된 문장을 말해야되다보니 30분이라는 짧은 시간에 &quot;생각정리&quot;라는것이 더 강하게 느껴진것 같습니다.</p>
<p>학습에서 <code>남에게 설명해보기</code>가 얼마나 강력한지는 누구나 다 알고있을겁니다.
하지만 개발 생활을 이어나가며 코드 한 줄 더 짜야돼 이런 생각에 쉽게 행동으로 옮기지 못하고있었는데, 이번 경험으로 &#39;역시 이 부분도 꼭 챙겨봐야겠다&#39; 라는 생각을 하게됐습니다 </p>
<p>이 외에, 제가 또 추가해본 액션플랜이 있는데요 오답노트입니다.</p>
<h4 id="2-2-오답-노트">2-2. 오답 노트</h4>
<p>오답을 내는 이유는 잘못된 지식,지식의 부재라고 생각합니다.
오답 코드와 함께 이 내용들을 정리해두면 도움이 많이 될 것 같아서 바로 실천해보았습니다~</p>
<img src="https://velog.velcdn.com/images/jenn_n/post/1d510abc-11d2-40a0-8a06-7c455ab6ab3e/image.png" width="400" />
<img src="https://velog.velcdn.com/images/jenn_n/post/6e310436-ffca-478f-a7bd-10cf07676bc4/image.png" width="400" />

<p>이런 식으로 정답 밑에 내가 냈던 오답과 그 오답을 냈던 이유, 잘못된 지식이나 지식의 부재를 정리해뒀는데 나중에 볼 때, 풀었던 당시가 생각나면서 기억에 확실히 더 오래 남는거같아서 좋았습니다 👍</p>
<h3 id="3메타인지-액션-플랜-구체화-및-다음주차-목표">3.메타인지 액션 플랜 구체화 및 다음주차 목표</h3>
<h4 id="3-1-메타인지-액션-플랜-구체화">3-1. 메타인지 액션 플랜 구체화</h4>
<p>위에서 언급한 <code>남에게 설명하기</code>를 다음 주차부터 꼭 챙겨가고싶은데요,
사실 다들 바쁘고 서로 시간 맞추기도 어렵다는 문제가 있습니다.</p>
<p>그래서 생각해낸것이 <code>인공지능</code> 입니다.</p>
<p>일단 생각해본것은</p>
<ol>
<li>인공지능을 타입스크립트 챌린지 쉬움 난이도 문제를 무난히 풀 수 있는 정도의 학생으로 설정한다.</li>
<li>어렵다라고 느껴진 일반 문제를 골라 인공지능에게 타입스크립트 선생님이 된거처럼 설명해준다.</li>
<li>이 때 인공지능에게 제 설명에 대한 질문을 받으며 질문 디펜스를 한다.</li>
</ol>
<p>입니다!</p>
<h4 id="3-2-다음주차-목표">3-2. 다음주차 목표</h4>
<p>다음주는 2주에 한 번 돌아오는 데모데이 주이기때문에 더 바빠질거라,
타입스크립트 챌린지 문제 진도를 빼진 않겠습니다
다만 2주차에서 어렵다라고 느껴진 문제들이 아마 4~5개 되는것 같은데요
이 문제들을 위에서 추가된 액션플랜 2가지를 적용해보는것을 목표로 잡았습니다 </p>
<h3 id="4마무리">4.마무리</h3>
<h4 id="추가수정된-액션플랜-정리">추가,수정된 액션플랜 정리</h4>
<p><del>1.타입스크립트 챌린지처럼 코딩 문제를 풀게 될 경우, 답안을 보고나서 고대로 다시 적어냈다고 이해했다고 착각하지 말기.</del></p>
<ol>
<li>어려운 문제는 인공지능에게 문제 설명해보기</li>
<li>개발 용어를 정확하게 머리 속에 넣자.</li>
<li>애매모호한 용어 사용으로 질문을 하게 될 경우 내 질문을 정확한 개발용어를 사용해서 수정해달라고 요구하기.</li>
<li>오답을 낸 문제들은 오답 노트 작성하기.</li>
</ol>
<p>1,2주차를 돌이켜보니 학습 개념 넣기에도 바빴지만, 메타인지 액션플랜에 대한 생각과 수정도 많이 이루어진 것 같습니다.</p>
<p>앞으로도 수정이 일어날지? 일어난다면 얼마나 일어날지 궁금해지네요
그럼 다음 3주차도 열심히 달려보도록하겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 챌린지 1주차 학습 회고]]></title>
            <link>https://velog.io/@jenn_n/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%B1%8C%EB%A6%B0%EC%A7%80-1%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jenn_n/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%B1%8C%EB%A6%B0%EC%A7%80-1%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 12 Aug 2024 15:06:22 GMT</pubDate>
            <description><![CDATA[<h2 id="타입스크립트에-빠지다">타입스크립트에 빠지다</h2>
<p>타입스크립트때문에 고생한 기억이 많습니다.</p>
<blockquote>
<p><del>나는 분명히 다 타입 지정해줬는ㄷ...</del></p>
</blockquote>
<p>걸핏하면 해당 타입을 할당할 수 없다는 에러를 마주했죠. 타입스크립트의 유용성 보다는 타입 체크에 지쳐가는 개발 생활을 했던것 같습니다. 타입스크립트 공부해야지 해야지 하고만 있다가, 몇 주 전에 <code>리액트 마이크로 상태관리</code>라는 책을 5장까지 정독하며 개발서적의 참맛을 느끼는 경험을 하게되었습니다. 이 후로 또 재밌는 개발 서적 없나? 하는 참에 앞서 말해드린 타입스크립트 공부에 대한 수요와 맞물려 <code>우아한 리액트 타입스크립트</code>라는 책으로 정하게 된것입니다.</p>
<p>이 책(이하 <code>우리타</code>라고 줄이겠습니다.)을 보면서 타입스크립트에 대한 흥미가 높아졌습니다. 일단 에러 예시들이 상당수가 제가 다 마주했던 에러들이라 개발 서적인데도 맞아 맞아 하면서 더 몰입해서 읽을 수 있었습니다.</p>
<p>이렇게 우리타 4장까지 보고 나니깐
<code>&quot;아 나 이제 타입스크립트 알겠다 (?)&quot;</code> 라는 지경까지 갔습니다. (심지어 타입스크립트 학습 부분이 4장이 끝이 아닌데도..) 사실 제네릭을 순수 제 힘으로 사용해서 추상화해본 경험이 없었는데 이 책을 읽고나서 처음으로 이번 우테코 레벨3 프로젝트에서 테이블 컴포넌트를 추상화하는 부분에 제 힘으로 제네릭을 성공적으로 써본것이 처음이라 고양감에 더 그랬던것 같습니다. 😅</p>
<h2 id="나는-이제-타잘알이-된-줄-알았지">나는 이제 타잘알이 된 줄 알았지..</h2>
<p>이 도파민을 계속 이어나가기위해 더 할 수있는것이 없을까? 라고 생각하다가 예전에 어떤 크루가 알려준 <code>타입스크립트 챌린지</code>가 생각 났습니다.</p>
<p>들어가보니 쉬움부터 난이도 별로 문제가 잘 정리되어있어서 지금의 저에게 딱이구나 싶었습니다.</p>
<p>제네릭도 쓸 줄 아는 나(?)지만 쉬움 레벨부터 풀어주지 라는 생각으로 기세등등하게 문제를 보는데.. 든 생각은</p>
<blockquote>
<p>이거 쉬움 맞아??</p>
</blockquote>
<p>바로 겸손해지며 쉬움 첫 문제부터 차근차근 헤쳐나가기 시작했습니다...</p>
<h2 id="기억이-나는-것과-이해는-다르다">기억이 나는 것과 이해는 다르다</h2>
<p>쉬움 문제를 풀며 4장 이후의 내용인 extends 조건부 타입,infer 등 추가적인 학습을 이어 나가니 4장까지만 읽고나서 문제를 마주쳤을 때 보다는 확실히 체감 난이도가 낮아지는것을 느낄 수 있었습니다.</p>
<p>그러함에도 불구하고 정말 이게 쉬움 맞아 정말로? 라고 느꼈던 문제가 있었는데요</p>
<blockquote>
<p><a href="https://github.com/type-challenges/type-challenges/blob/main/questions/00898-easy-includes/README.ko.md">타입스크립트 챌린지 | 쉬움 난이도 | Includes&lt;&gt;</a></p>
</blockquote>
<p>Includes&lt;&gt;를 Include를 사용하지 않고 구현하는 문제입니다.
골머리를 썩다가 결국 답안를 보았는데, 답안를 봐도 이해하기 힘들더라구요. 풀이를 보니 이미 타입스크립트를 정말 잘 아시는 분이 <code>이렇게 짧게 풀 수도있다</code>라는 느낌으로 작성된 답안이었습니다. 지금 제 수준에는 맞지 않다고 느껴 더 정석적인 답안이 필요하다 생각해서 클로드에게 물어봤는데 제가 딱 원하는 느낌의 답안이 나왔습니다.</p>
<pre><code class="language-javascript">type Includes&lt;T extends readonly any[], U&gt; = T extends [infer First, ...infer Rest]
  ? Equal&lt;First, U&gt; extends true
    ? true
    : Includes&lt;Rest, U&gt;
  : false;</code></pre>
<p>바로 위 코드입니다. 첫 원소부터 돌아가면서 확인하는 풀이인데 굉장히 합리적이라 생각했습니다.</p>
<p>이해했다고 생각했고, 다시 문제를 풀어봤을 때도 똑같이 적었기에 제가 이 문제는 이제 제가 이해했고, 또 풀 줄도 아는 문제라고 생각하고 넘어갔습니다.</p>
<p>이틀 후 이 문제가 다시 생각나서 다시 풀어볼려했는데 웬걸</p>
<pre><code class="language-javascript">T extends [infer First, ...infer Rest]</code></pre>
<p>이 첫 부분부터 못적고 있는 저를 발견했습니다.
이유는 단순합니다. 제가 infer를 잘 모르고 있었기 때문입니다.</p>
<p>분명 우리타 책에도 infer는</p>
<blockquote>
<p>extends로 조건을 서술하고 infer로 타입을 추론하는 방식을 취한다.</p>
</blockquote>
<p>라고 잘 적혀있고 형광펜도 쳐놨습니다.</p>
<p>하지만 정작 제 머리속에 각인된것은?</p>
<pre><code class="language-typescript">type UnpackPromise&lt;T&gt; = T extends Promise&lt;infer K&gt;[] ? K : any;
</code></pre>
<p>인거죠. 이 예시가 저 설명에 대한 하나의 예시로 머리속에 들어온게 아니라, 굉장히 편협적으로 <code>아 제네릭 안에 infer 쓰는구나</code>라고만 머리에 박힌거죠.</p>
<p>infer에 대한 이해가 이렇게 되어있으니
<code>T extends [infer First, ...infer Rest]</code>라는 부분을 당장 답안에 대한 단기 기억력이 남아있을 때만 바로 적어낼 수 있었고 하루가 지나니 바로 기억나지 않은거죠.</p>
<p>사실 처음 저 코드를 봤을 때부터 저런 이유로 굉장히 생소하게 느껴졌지만 일단 많이 풀고 봐야겠다는 심정에 대충 넘어간것같습니다.</p>
<p>이틀 후 바로 리셋된 제 기억력에 충격받고 클로드에게 아래와 같은 질문을 하게됩니다..
<img src="https://velog.velcdn.com/images/jenn_n/post/3b124f50-8073-40ba-a36b-a8772ba34fa7/image.png" alt="">
여기서 또 발견한 제 문제..
보시면 아시겠지만 정확한 용어를 모르는 상태입니다. 
타입 변수라는 말도 굉장히 애매모호한 상태에서 사용한 단어입니다.
이런 상태야말로 그 개념을 잘 모르고있다라는 확실한 지표 중 하나라고 생각합니다.</p>
<p><img src="https://velog.velcdn.com/images/jenn_n/post/507b084e-8358-4e98-8c9d-0ea9be02bfdb/image.png" alt="">
확실히 하기위해 다시 이런 질문을 했습니다.</p>
<blockquote>
<p>네, 질문을 개발 용어를 사용하여 더 정확하고 깔끔하게 재구성해 드리겠습니다:
&quot;조건부 타입의 extends 절 내에서 infer 키워드를 사용할 때, 추론된 타입 변수를 선언하는 위치에 제한이 있나요? 예를 들어, T extends [infer First, ...infer Rest]와 같은 구문에서 infer로 선언된 타입 변수들(First와 Rest)은 배열 또는 튜플 타입의 요소 위치에 자유롭게 배치될 수 있는 건가요? 이러한 infer 키워드의 사용 패턴이 TypeScript의 타입 시스템에서 일반적으로 허용되는 문법인가요?&quot;
이렇게 재구성된 질문은 다음과 같은 핵심 개념들을 포함합니다:</p>
<ul>
<li>조건부 타입 (Conditional Types)</li>
</ul>
</blockquote>
<ul>
<li>extends 절 (extends clause)</li>
<li>infer 키워드 (infer keyword)</li>
<li>추론된 타입 변수 (Inferred Type Variables)</li>
<li>배열 및 튜플 타입 (Array and Tuple Types)<blockquote>
<p>이를 통해 TypeScript의 고급 타입 시스템에 대한 이해를 더 명확히 표현할 수 있습니다.</p>
</blockquote>
</li>
</ul>
<p>굉장히 만족스러운 답변이 나왔습니다. </p>
<p>위 답변으로 제가 위에서 표현한 &#39;제네릭 안에서만 쓸 수 있는 줄 알았다&#39; 라는 표현도 뭔가 정확한 표현이 아니라는것을 알 수 있죠</p>
<p>다시 명확하게 표현해보겠습니다</p>
<blockquote>
<p>나는 infer를 제네릭 타입 선언 안에서만 사용할 수 있는 줄 알았는데, 타입 변수들 앞에서 사용되어 그 타입 변수를 추론하는데 사용할 수 있다. </p>
</blockquote>
<h2 id="메타인지를-위한-액션플랜-추려보기">메타인지를 위한 액션플랜 추려보기</h2>
<p>위와 같은 경험으로 앞으로의 학습에서 메타인지를 높이기 위해 취해야할 액션 플랜 몇 개를 뽑아봤습니다.</p>
<ol>
<li><p>타입스크립트 챌린지처럼 코딩 문제를 풀게 될 경우, 답안을 보고나서 고대로 다시 적어냈다고 이해했다고 착각하지 말기.</p>
</li>
<li><p>개발 용어를 정확하게 머리 속에 넣자.</p>
</li>
<li><p>애매모호한 용어 사용으로 질문을 하게 될 경우 내 질문을 정확한 개발용어를 사용해서 수정해달라고 요구하기.</p>
</li>
</ol>
<h2 id="마무리">마무리</h2>
<p>타입스크립트 챌린지 1주차는 일단 이정도로 마무리됐습니다.
1주차에서 일단 쉬움 난이도는 다 풀긴풀었는데, 이번 글에서 다룬 Includes 문제뿐만 아니라 다른 문제에서도 위와 같은 이슈가 분명이 있으거라 생각됩니다.</p>
<p>쉬움 난이도 문제들이긴하지만, 우리타 5장까지 다룬 개념들의 다반수가 다뤄졌습니다. 그렇기에 이번 주는 바로 다음 단계인 보통 난이도로 가지않고, 이번 학습 회고글에서 새운 액션 플랜을 지키며 모든 문제를 다시 복습해 보겠습니다.</p>
<p>레벨3 프로젝트의 마지막 4차 데모데이를 앞두고 굉장히 바쁠것이지만, 또 마음 급하다고 대충하지 말고 최대한 메타인지를 발휘해보아야겠습니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우테코 6기 레벨2 글쓰기) 강 위를 걷고싶다]]></title>
            <link>https://velog.io/@jenn_n/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%EB%A0%88%EB%B2%A82-%EA%B8%80%EC%93%B0%EA%B8%B0-%EA%B0%95-%EC%9C%84%EB%A5%BC-%EA%B1%B7%EA%B3%A0%EC%8B%B6%EB%8B%A4</link>
            <guid>https://velog.io/@jenn_n/%EC%9A%B0%ED%85%8C%EC%BD%94-6%EA%B8%B0-%EB%A0%88%EB%B2%A82-%EA%B8%80%EC%93%B0%EA%B8%B0-%EA%B0%95-%EC%9C%84%EB%A5%BC-%EA%B1%B7%EA%B3%A0%EC%8B%B6%EB%8B%A4</guid>
            <pubDate>Sun, 16 Jun 2024 02:21:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>레벨2도 정말 순식간에 지나갔습니다. 이번 글 역시 벨로그에도 기록해봅니다.</p>
</blockquote>
<h2 id="강-위를-걸어보자">강 위를 걸어보자</h2>
<h3 id="1-강은-강이다">1. 강은 강이다.</h3>
<blockquote>
<p><code>나는 잔잔한 사람이 되었다.</code></p>
</blockquote>
<p>레벨 1 글의 마무리입니다. 정말 호기롭게 저렇게 글을 끝내놨더라구요. 그러나 지금 이 글을 쓰는 시점에서 돌이켜보면, 결국 그때의 잔잔함은 오래가지 못했습니다. 그럼에도 불구하고 저 문장을 쓰던 저를 놀리거나 비웃고 싶은 생각은 없습니다. 레벨1 마지막 즈음에는 잔잔한 상태를 몇 주나마 누린 것은 사실이고 그것에 좋아했던것도 사실이기 때문입니다. 하지만 영원하다는 것은 없다죠? 잔잔한 강도 결국 강일 수 밖에 없었던 것입니다. 속에 언제 생겼는지도 모를 소용돌이 때문이든, 바깥에서 날아온 돌 때문이든, 물로 이루어졌기에 즉시 파동치기 시작했습니다.</p>
<p>레벨 1 때에는 심한 낯가림과 낯선 환경에 적응하느라 마음고생도 했어요. 크루들과 친해지면서 잔잔해지는가 싶었지만, &#39;노는 게 제일 좋아&#39;라는 생각이 가득 차 개발을 향한 집중력이 확 떨어졌어요. 사람이 참 갈대같다고 느꼈습니다. 개발 공부 측면에서 프리코스 때 몰입하던 모습은 진작에 없어진것 같고, 우아한테크코스 밖에서 생기는, 사적인 영역에서의 문제들도 저를 동시에 건드리기 시작했습니다. 강 양쪽 끝에서 몰아치는 해일 사이에 낑겨 어쩔 줄 몰라했던것같습니다.</p>
<h3 id="2-잔잔한-강이-되려하기-보다는-강-위에-떠보자">2. 잔잔한 강이 되려하기 보다는 강 위에 떠보자</h3>
<p>해일 사이에 낑겼을 때 일단 가라앉으려고 했습니다. 처음 문제가 생겼다는 것을 어렴풋이 알게되었을 때, 그리고 곧 제 감정 상태를 느꼈을 때 가장 먼저 느낀 감정은 <code>무섭다</code>였기때문입니다. 강의 표면에서 해일에 이리저리 치일 바에는 강 속 깊이 숨어드는게 낫겠다 싶었습니다. 고민에 대한 생각은 범람하려하는데 꾸역꾸역 술도 더 자주 마시고, 집에서 유튜브 쇼츠만 멍때리고 보면서 문제 상황을 최대한 회피하거나 생각을 비우려고 했습니다. 이러다보니 남는 것은 결국 허망함밖에 없더라구요. 도망친 곳에 낙원은 없다는것이 이런거구나 싶었습니다. 고통스럽더라도 최대한 직면해봐야겠다는 생각이들었습니다.</p>
<h3 id="3-땟목에-올라타다">3. 땟목에 올라타다</h3>
<p>직면할려고해도 <code>이 상황 속에서 어떻게 해야하는거지?</code>라는 생각 밖에 안들더라구요. 고민 속에 빠져있던 찰나 <code>레벨 2 때부터는 면담 열테니깐 그 때까지 최대한 멘탈 붙잡고있어봐요</code> 라고 말해주던 포비가 생각이 났습니다. 하지만 이 때는 이미 포비 면담 예약이 꽉 차 있던 상태였습니다. 근데 제가 고민이 있다는 것을 알고 저에게 양보해준 크루 덕분에 면담 예약을 잡을 수 있었습니다. (이 자리를 빌어 <code>@헤일리</code>한테 고맙다는 말 다시 한번 하고싶어요 🥹) &#39;우테코에 집중하고 싶은데 외부 요인들 때문에 집중하기 힘들다.&#39;라는 주제로 상담을 받았습니다. 상담 도중에 사실 포비가 저에게 던진 질문에 <code>이건 생각해 본적 없는 질문인데?</code> 라는 것도 많았고, 20초 이상 뜸들이고도 잘 모르겠다고 답변하지 못한 질문들도 꽤 있었습니다. 사실 저는 저에대해서 잘 모르는 편은 아니라고 생각했는데, 내가 생각 안해본 부분도 많구나 라는 생각이 들었어요.</p>
<p>면담 후 문제를 해결할 방법에 대해 생각해보는 것보다 &#39;문제 회피 기간동안 왜 그런 행동을 했고, 왜 그런 선택을 했는가?&#39; 에 대해서 진지하게 생각해는 시간을 가졌습니다. 생각해보니 레벨1 때 많이 사라진줄 알았던 완벽주의 성향이 제 자신이 벼랑 끝에 몰리는 상황이 되니 다시 발동되었더라구요. 아무리 생각해도 완벽하게 문제를 해결할 방도를 못 찾겠으니 자기파괴적으로 저를 놔버린 것 같습니다.</p>
<p>이렇게 대략적으로 제 행동을에 대한 원인을 대략적으로 찾았을 때 최근에 현업 개발자 특강에 들었던 말이 떠오르더라구요.</p>
<blockquote>
<p>퍼포먼스를 잘 내는 개발자들은 추상화된 서비스를 툭 잘라서 개발해라고 던져줬을 때, 이 큰 덩어리를 작게 잘라서 본인만의 태스크를 만든다.</p>
</blockquote>
<!-- 제 인생 고민들을 디버깅한다는 생각으로 제 고민들도 잘게 잘라보면 좋겠다라는 생각이 들었습니다. 일단 머리 속을 어지럽히는 문제들을 최대한 분류해보기 시작했습니다. 고민사항들은 우테코 밖,안으로 크게 나누었습니다. 그런 후 각각에 고민 사항을 하나씩 적어 내려갔습니다.

그리고 위와 같이 고민 사항 밑에 각각 `원인`, `포기해야되는 부분`, `이 문제로 인해서 새로 알게된 내 모습`, `앞으로 해야되는 것`을 적어보았습니다. 원인은 고민에 있어서 기본적으로 적어야되는 사항이라고 생각하고, 고민을 회피하고 상황을 놔버린것의 이유를 완벽주의로 뽑은만큼 고민을 해결하는데 있어서 완벽주의를 버리고 `포기해야되는 부분`도 꼭 적어볼 필요가있다고해서 문항으로 뽑아보았습니다.
저렇게 템플릿을 만들어놓고 정말 정제하지 않고 생각나는대로 적어보았습니다. 그러다보니 `포기해야 되는 부분 항목`을 적으면서 자연스럽게 밑에 항목인 `이 문제로 인해서 새로 알게된 내 모습`도 자연스럽게 이어서 적히는 경험도 했고, `포기하지 말아야될 것`을 자연스럽게 떠올리는 경험도 했습니다. -->

<p>제 인생 고민들을 디버깅한다는 생각으로 제 고민들도 잘게 잘라보면 좋겠다라는 생각이 들었습니다. 일단 머리 속을 어지럽히는 문제들을 최대한 분류해보기 시작했습니다. 고민사항들은 우테코 밖,안으로 크게 나누었습니다. 그런 후 각각에 고민 사항을 하나씩 적어 내려갔습니다.
그리고 위와 같이 고민 사항 밑에 각각 원인, 포기해야되는 부분, 이 문제로 인해서 새로 알게된 내 모습, 앞으로 해야되는 것을 적어보았습니다.</p>
<p>원인은 고민에 있어서 기본적으로 적어야되는 사항이라고 생각하고, 제가 고민을 회피하고 상황을 놔버린것의 이유를 완벽주의로 뽑은만큼 고민을 해결하는데 있어서 완벽주의를 버리고 포기해야되는 부분도 꼭 적어볼 필요가있다고해서 문항으로 뽑아보았습니다.</p>
<p>저렇게 템플릿을 만들어놓고 정말 정제하지 않고 생각나는대로 적어보았습니다. 그러다보니 포기해야 되는 부분 항목을 적으면서 자연스럽게 밑에 항목인 이 문제로 인해서 새로 알게된 내 모습도 자연스럽게 이어서 적히는 경험도 했고, 포기하지 말아야될 것을 자연스럽게 떠올리는 경험도 했습니다.</p>
<p>하지만 여기서 또 지우고 다시 항목별로 글을 깔끔하게 나눈다거나 그러진 않았습니다. 오히려 이런 부분을 생각하다보면 자연스럽게 이 부분도 떠올리게되는구나 하고 신기하다, 항목을 잘 뽑은것 같다라는 생각으로 그냥 두었습니다. 그리고 위 3가지 항목을 적다보니 앞으로 해야되는것도 쉽게 적을 수 있더라구요. 이 부분은 todo 형식으로 정리해놓았습니다.</p>
<p>머리 속에있던 것을 템플릿에 글로 정리해보았을 뿐인데, 그저 머리 속으로 생각하는 것과 내가 적어 놓은 글(심지어 정제되어있지 않은데도)을 내 눈으로 다시 읽을 때 드는 느낌이 정말 다르구나 싶었어요. 처음엔 다소 감정적인 상태로 글을 적기 시작해도, 아무리 정제하지 않는다고해도 말이 되는 문장을 적어야되긴하니깐 이 노력만으로도 이성적으로 생각하고 메타인지적으로 저를 돌아 볼 수 있었습니다. 그러다보니 전보다 차분해지고 담담해지는 것 같았습니다.
깊은 강 속에서 올라와 작은 땟목에 몸을 실은 기분이었습니다.</p>
<h3 id="4-강-위를-걷고-싶다">4. 강 위를 걷고 싶다</h3>
<p>레벨1 때 글처럼 마냥 &quot;해결 된 것 같아 짜잔 해피엔딩&quot; 이런 느낌으로 글을 마무리 짓고 싶진 않네요. 지금은 그저 해일이 난무하는 강 위에서 작은 땟목에 몸을 걸친 상태라고 생각합니다. 제가 템플릿에서 적은 <code>앞으로 지켜야할 것</code> 항목들을 제가 마음먹은대로 지켜낸다는 보장도 없고, 저런 활동으로 인해 고민사항이 마법처럼 해결되는 것도 절대 아니니깐요. 다만 제가 고민하고 있는것들에 대해 최종 결단을 내려야만하는 상황이 왔을 때 <code>이 선택을 했다고 후회하면 어떡하지?</code> 같은 걱정은 확실히 줄 것 같습니다. 또 후회하게 되는 상황이 온다고해도 그런 결정을 내린 과거의 저를 비난하기보다는 존중해줌으로서 다시 마음을 다 잡는데 도움이 될 것 같아요. 상황이 어떻게 흘러가든 차분히 생각을 정리하고 노력도 들어간 선택의 결과이니깐요.</p>
<p>일단 남은 기간동안은 제가 적은 <code>앞으로 지켜야할 것</code>을 지켜 나가는데에 집중하려합니다. 살짝 옆길로 새어나가려해도 저를 비난하지 않고 괜찮다고 다독여줘서 다시 원래 길로 돌아오도록 하는것이 이 실험에서 가장 중요한 요소라고 생각됩니다.</p>
<p>강이 요동치는것은 제가 막을 수 없습니다. 다만 요동치더라도 회피하지않고 그 위에서 어느 부분이 요동치는지 차분하게 살펴보고 저를 사랑해주다보면 언젠가는 강의 상태와 상관없이 강 위를 걷고있는 저를 볼 수 있지 않을까? 라는 기대도 살짝 해봅니다.</p>
<p>긴 글이었는데 끝까지 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우테코 6기 1레벨 글쓰기 ) 해일에서 잔잔함을 찾기까지 🌊]]></title>
            <link>https://velog.io/@jenn_n/%EC%9A%B0%ED%85%8C%EC%BD%94-1%EB%A0%88%EB%B2%A8-%EA%B8%80%EC%93%B0%EA%B8%B0-%ED%95%B4%EC%9D%BC%EC%97%90%EC%84%9C-%EC%9E%94%EC%9E%94%ED%95%A8%EC%9D%84-%EC%B0%BE%EA%B8%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@jenn_n/%EC%9A%B0%ED%85%8C%EC%BD%94-1%EB%A0%88%EB%B2%A8-%EA%B8%80%EC%93%B0%EA%B8%B0-%ED%95%B4%EC%9D%BC%EC%97%90%EC%84%9C-%EC%9E%94%EC%9E%94%ED%95%A8%EC%9D%84-%EC%B0%BE%EA%B8%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Sat, 06 Apr 2024 12:27:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>우아한테크코스에서 글쓰기 미션을 수행하게되었습니다. 처음에는 귀찮아하며 시작한 글쓰기지만 쓰는 과정, 쓰고 나서 크루들과의 감정 공유 과정에서 큰 만족감을 느꼈어요. 깃허브에만 남기기 아쉽다 생각하여 벨로그에도 올려봅니다.</p>
</blockquote>
<h3 id="1-내-닉네임은-왜-리버인가">1. 내 닉네임은 왜 리버인가?</h3>
<p>우테코를 시작하고 가장 많이 받은 질문이 &quot;왜 닉네임이 리버에요?&quot; 였습니다. 스타크래프트 리버(...) 아니고 강(river)에서 따온 이름이에요. 어렸을 적부터 생각과 걱정이 많아 강처럼 잔잔해 보이는 사람들에게 항상 눈길이 갔었어요. 그런 사람들을 보며 &quot;어떻게 저럴 수 있지? 나도 저렇게 되고 싶다.&quot; 하고 동경했고, 이런 이유로 이 닉네임을 갖게 되었습니다.</p>
<h3 id="2-잔잔한-강이-되고자-했으나-해일이-몰아치다">2. 잔잔한 강이 되고자 했으나 해일이 몰아치다</h3>
<p>다들 최종 코딩테스트 때 많이 떨었나요? 저는 사실 코딩테스트 시작 30분 전까지는 하나도 안 떨렸었어요. 그 당시에 이미 프리코스만으로도 만족도가 높았고 &quot;떨어지면 혼자 취준하지 뭐&quot;라는 생각이었습니다. 그 뒷편에 항상 기대한 마음의 곱절을 뛰어넘는 실망에 대한 방어기제가 있었던 것도 사실이긴해요.</p>
<p>전날 밤 일찍 잠들어 코딩테스트에 여유롭게 갈 수 있었어요. 약 2개월간의 선발 과정에 대한 감정은 기대감이었습니다. 하지만 막상 우테코 시작 전날인 2월 12일이 되자 너무 떨리며 &#39;올 게 왔구나&#39;라는 감정을 느꼈어요.</p>
<p>저는 원래 낯을 너무 많이 가려요. 처음 보는 사람들을 어떻게 대할지에 대한 걱정이 해일처럼 밀려왔습니다. 첫 날을 어떻게 보냈는지 기억도 잘 안 나네요. 심지어 연극이라는 미션까지! 모든 것이 제 정신을 혼미하게 하기 위해 완벽하게 셋팅된 느낌이었어요.</p>
<p>연극 미션을 하면서 전보다 친해지긴 했지만 이제와서 솔직히 말하자면, 저는 연극 후에도 여전히 낯을 많이 가렸어요. 연극이라는 충격요법도 제 낯가림을 격파하지 못했던 것입니다(..!) 연극 후 캠퍼스 분위기가 바뀐 게 느껴졌어요. 다들 편해진 느낌이었죠. 그 속에서도 여전히 불편함을 느끼는 저를 보며 할 수 있는 말은 &quot;너는 도대체 뭐가 문제냐&quot; 밖에 없었습니다.</p>
<hr>
<h3 id="3-해일을-잠재우고자-선택했던-것">3. 해일을 잠재우고자 선택했던 것</h3>
<p>솔직히 말하자면 우테코를 지원하면서 소프트 스킬 부분은 깊게 생각하지 않았습니다. &quot;개발을 잘하는 개발자가 되자&quot;는 생각으로 들어왔어요. 그래서 내가 개발에 몰두하면 (해일의 원인이 개발이 아니었음에도 불구하고) 마음속의 해일이 잠재워지지 않을까 했습니다. 최대한 눈앞에 있는 미션에 집중하려 했어요. 하지만 당연하게도 해일 뒤에 개발이라는 해일이 더 추가됐을 뿐, 나아지는 것은 하나도 없었습니다.</p>
<hr>
<h3 id="4-해일-진압은-크루들이">4. 해일 진압은 크루들이</h3>
<p>저에게는 5명이 함께해야 했던 연극보다는 1대1로 조가 이루어지는 페어 미션이 크루들과 친해지기에 더 좋은 기회였습니다. 이때 개발 이야기뿐만 아니라 스몰토크도 많이 해보려고 했어요. 페어와 함께 선릉캠에서 밤 10시 30분까지 남아보기도 하고, 아침 7시에 와보기도 했습니다. 지하철역에서 &quot;내일 봐요&quot;가 아닌 &quot;이따 봐요&quot;라고 하며 헤어질 때면 동료애가 마구 샘솟더라고요.</p>
<p>한두 명씩 더 친해지면서 신기하게도 해일이 점점 잠잠해졌습니다. 더 놀라운 것은 개발이라는 해일까지 잠잠해지는 거였어요. 낯가림 해일이 잠잠해지는 것은 당연해 보이지만, 개발에 대한 고민도 잦아드는 게 정말 신기했습니다. 저희 조 코치인 왼손이 말한 &quot;심리적 안정감&quot;이 이런 걸까요? 지금 돌이켜보면 심리적 불안정으로 인해 개발적인 측면에서도 매우 예민해져 있었던 것 같아요.</p>
<hr>
<h3 id="5-강-속은-과연-잠잠한가">5. 강 속은 과연 잠잠한가?</h3>
<p>어느 정도 잠잠해진 마음을 가지고 오늘 선릉캠 로비에서 포비와 대화를 나눌 수 있는 시간이 있었습니다. 포비에게 &quot;어떤 사람이 되고 싶나요?&quot;라는 질문을 받았고, 여전히 1번 문단과 똑같이 대답했습니다. 잔잔한 사람이 되고 싶다고요.</p>
<p>글을 써내려가며 이 대화가 떠오르면서 문득 이런 생각이 들더라고요. 내가 너무 잔잔함에 집착하는 건 아닌가? 잔잔해 보이는 강도 속에서 흐르듯이, 지금 제 상태 정도면 한 달 전의 저보다는 그런 모습에 어느 정도 가까워지지 않았나 싶습니다.</p>
<p>&quot;아아~ 잔잔해지고 싶어<del>&quot;라고 하기보다는 &quot;이정도면 잔잔한 편으로 치자</del>&quot;라고 하는 것도 나쁘지 않은 것 같아요. 새로운 레벨을 시작하거나 나중에 어떻게 될지는 모르겠지만, 일단 지금은 스스로에게 한 달 동안 나름대로 고생했다고, 이 정도면 많이 잔잔해졌다고 말해주고 싶네요.</p>
<hr>
<p>읽어주셔서 감사힙니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[cypress e2e 테스트에서의 트러블 슈팅에서 시작된 실행 명령어 알아보기]]></title>
            <link>https://velog.io/@jenn_n/cypress</link>
            <guid>https://velog.io/@jenn_n/cypress</guid>
            <pubDate>Mon, 18 Mar 2024 16:20:31 GMT</pubDate>
            <description><![CDATA[<p>이번 주에는 점심 뭐먹지 2단계 미션을 진행하면서 cypress로 e2e 테스트 코드를 작성해야했다.</p>
<p>미션의 package.json에 등록되어있는 cypress 테스트 실행 명령은 아래와 같았다.</p>
<pre><code class="language-javascript">npx cypress open</code></pre>
<p>(우테코 미션에서는 <code>npm run test</code> 명령어였다)</p>
<p>나는 이번에 e2e 테스트 코드를 한 파일에 적지 않고 아래와 같이 컴포넌트 단위로 짜게되었는데, e2e 테스트 코드를 작성한 후 코드를 리팩토링할 때 마다 아래 테스트들을 하나 하나씩 눌러 돌려보는데 너무 귀찮았다. 
<img src="https://velog.velcdn.com/images/jenn_n/post/6f677156-26c3-4ce8-b857-80ed716a2aca/image.png" alt="e2e 테스트 구성"></p>
<p>jest 테스트 코드를 돌릴 때 처럼 분명히 한번에 모든 e2e테스트를 돌려보는 명령어가 있을거라 생각했고 그리해서 발견한 것이 아래의 cypress 테스트 실행 명령어였다.</p>
<pre><code class="language-zsh">npx cypress run --headed</code></pre>
<ul>
<li>브라우저가 열리고 모든 e2e테스트가 자동으로 빠르게 실행된다.그리고 그 결과가 터미널에 출력된다.</li>
</ul>
<pre><code class="language-zsh">npx cypress run</code></pre>
<ul>
<li>브라우저가 열리지 않고 모든 e2e테스트가 자동으로 빠르게 실행된다.그리고 그 결과가 터미널에 출력된다.</li>
</ul>
</br>

<h2 id="마주한-에러">마주한 에러</h2>
<p>⬇️ <code>npx cypress run</code>으로 cypress를 실행한 결과 스샷
<img src="https://velog.velcdn.com/images/jenn_n/post/62cac022-1234-46c7-937b-6be4835410e3/image.png" alt=""><img src="https://velog.velcdn.com/images/jenn_n/post/1a7bf4a7-b791-464a-afe7-274b1128b88a/image.png" alt=""></p>
<p>내가 딱 원하는대로 모든 e2e케이스를 자동으로 돌려주고 결과를 터미널창에서 깔끔하게 보여준다.</p>
<p>이렇게 편안함에 딱 만족할려던 차에!</p>
<p><img src="https://velog.velcdn.com/images/jenn_n/post/fd891d0f-d74c-49d3-8bfa-3eeb8a046eea/image.png" alt=""></p>
<p><code>npx cypress open</code>로 돌릴 때는 잘 통과되던 테스트가
<code>npx cypress run</code>으로 실행하니 통과하질 못하는 상황이 벌어졌다.<code>npx cypress run --headed</code>로 실행해봐도 마찬가지였다.</p>
<p>참고로 이 테스트가 돌아갈 때 사용되는 비즈니스 로직 변경은 아무것도 없었다. </p>
<p><strong>이유가 도대체뭘까?</strong></p>
<h3 id="에러-메시지">에러 메시지</h3>
<p>일단 에러메시지를 살펴보자.</p>
<blockquote>
<p>CypressError: Timed out retrying after 4000ms: <code>cy.find()</code> failed because the page updated as a result of this command, but you tried to continue the command chain. The subject is no longer attached to the DOM, and Cypress cannot requery the page after commands such as <code>cy.find()</code>.
Common situations why this happens:
    - Your JS framework re-rendered asynchronously
    - Your app code reacted to an event firing and removed the element
    You can typically solve this by breaking up a chain. For example, rewrite:
 <code>cy.get(&#39;button&#39;).click().should(&#39;have.class&#39;, &#39;active&#39;)</code>
to
 <code>cy.get(&#39;button&#39;).as(&#39;btn&#39;).click()</code>
 <code>cy.get(&#39;@btn&#39;).should(&#39;have.class&#39;, &#39;active&#39;)</code>
<a href="https://on.cypress.io/element-has-detached-from-dom">https://on.cypress.io/element-has-detached-from-dom</a></p>
</blockquote>
<p>번역본 ⬇️</p>
<blockquote>
<p>CypressError: 4000ms 후에 재시도하는 동안 시간이 초과되었습니다: cy.find()가 이 명령으로 인해 페이지가 업데이트 되었기 때문에 실패했고, 명령 체인을 계속 시도했습니다. 대상이 더 이상 DOM에 연결되어 있지 않고, cy.find()와 같은 명령 후에 Cypress가 페이지를 다시 쿼리할 수 없습니다.
이런 상황이 발생하는 일반적인 경우는:
여러분의 자바스크립트 프레임워크가 비동기적으로 다시 렌더링되었습니다
앱 코드가 이벤트 발생에 반응하여 요소를 제거했습니다
이 문제를 해결하는 일반적인 방법은 체인을 분리하는 것입니다. 예를 들어, 다음과 같이 작성하세요:
cy.get(&#39;button&#39;).click().should(&#39;have.class&#39;, &#39;active&#39;)
를
cy.get(&#39;button&#39;).as(&#39;btn&#39;).click()
cy.get(&#39;@btn&#39;).should(&#39;have.class&#39;, &#39;active&#39;)
로 바꾸세요.
<a href="https://on.cypress.io/element-has-detached-from-dom">https://on.cypress.io/element-has-detached-from-dom</a></p>
</blockquote>
<p>찾아보니 해당 에러 메시지는 페이지가 업데이트되어 대상 요소가 DOM에서 분리되었기 때문에 <code>cy.find()</code> 명령어가 실패한 것이라한다. 이는 비동기적인 렌더링이나 이벤트 처리로 인해 요소가 제거되었을 가능성이 있다라고한다.</p>
<p>select에서 sort값을 고르면(&#39;이름순&#39;,&#39;거리순&#39;) 음식점 데이터를 비동기적으로 받아온 후 정렬해서 렌더링하는 방식으로 구현했기때문에 위에서 언급된 <strong>비동기적인 렌더링</strong>이 이 에러의 원인인것같다.</p>
<p>특정 cypress 실행 환경에서만 테스트가 실패하고있기 때문에 각 명령어의 실행환경 차이점을 알아보자.</p>
<h2 id="cypress-실행-명령어들의-실행환경">cypress 실행 명령어들의 실행환경</h2>
<h4 id="npx-cypress-open">npx cypress open</h4>
<ul>
<li>이 명령어는 Cypress의 대화형 테스트 러너(Test Runner)를 실행한다.</li>
<li>Cypress의 그래픽 사용자 인터페이스(GUI)가 열리고, 테스트 파일 목록이 표시된다.</li>
<li>개발자가 테스트 파일을 선택하면 해당 테스트가 브라우저에서 실행되며, 실시간으로 테스트 진행 상황을 확인할 수 있다.</li>
<li>테스트 실행 중에 개발자는 브라우저를 직접 조작하거나 디버깅 도구를 사용할 수 있다.</li>
<li>개발자가 테스트 실행을 제어할 수 있으므로, 필요한 경우 수동으로 대기 시간을 조정하거나 특정 상황에 맞게 대처할 수 있다.<ul>
<li>이러한 이유로 <code>npx cypress open</code>에서는 <code>cy.find()</code>실행에서 런타임 타임아웃 에러가 발생하지 않는 경향이 있다.</li>
</ul>
</li>
<li>따라서 개발 과정에서는 <code>npx cypress open</code>을 사용하여 테스트를 작성하고 디버깅하는 것이 효과적이다.</li>
</ul>
<h4 id="npx-cypress-run---headed">npx cypress run --headed</h4>
<ul>
<li>이 명령어는 Cypress 테스트를 headed 모드로 실행한다.</li>
<li>실제 브라우저 창이 열리고 테스트가 자동으로 실행되지만, 개발자의 상호작용은 제한된다.</li>
<li>테스트 실행 과정은 브라우저에서 시각적으로 확인할 수 있지만, 개발자가 직접 브라우저를 조작하거나 디버깅 도구를 사용할 수 없다.</li>
<li>테스트 실행 속도와 브라우저 렌더링 속도는 로컬 환경에서의 수동 실행과 다를 수 있다.<ul>
<li>이로 인해 <code>npx cypress run --headed</code>에서는 <code>cy.find()</code> 실행에서 런타임 타임아웃 에러가 발생할 수 있다.</li>
</ul>
</li>
<li>CI/CD 파이프라인에서는 <code>npx cypress run</code>을 사용하여 완전히 자동화된 테스트 실행을 수행하는 것이 일반적이다.</li>
</ul>
<h4 id="npx-cypress-run">npx cypress run</h4>
<ul>
<li>이 명령어는 Cypress 테스트를 헤드리스(headless) 모드로 실행한다.</li>
<li>브라우저 창이 열리지 않고 백그라운드에서 테스트가 실행된다.</li>
<li>테스트 실행 과정이 시각적으로 표시되지 않으며, 개발자의 상호작용은 불가능하다.</li>
<li>헤드리스 모드에서는 브라우저의 동작 속도와 렌더링 속도가 다를 수 있다.<ul>
<li>이로 인해 <code>npx cypress run</code>에서도 <code>cy.find()</code> 실행에서 런타임 타임아웃 에러가 발생할 수 있다.</li>
</ul>
</li>
<li>테스트 실행 과정을 시각적으로 확인하고 싶지만 개발자의 개입은 최소화하고 싶은 경우에는 <code>npx cypress run --headed</code>를 사용할 수 있다.</li>
</ul>
<p>정리해보자면 <code>npx cypress open</code>으로 실행할 때는 브라우저에서 직접 테스트를 확인하면서 진행하기 때문에 비동기 작업의 완료를 기다릴 수 있다고한다.
하지만 <code>npx cypress run</code>으로 실행할 때는 자동화된 환경에서 테스트가 빠르게 진행되므로, 비동기 작업이 완료되기 전에 다음 테스트 단계로 넘어갈 수 도 있다는 것이다.
그래서 이 경우 cy.wait()같은 명령어로 명시적인 시간을 추가해주는 작업 등을 해줘야 될수도있다.</p>
<p><strong>그럼 여기서 다시 드는 의문 !</strong></p>
<p>&#39;이름순&#39;,&#39;거리순&#39;은 둘다 똑같은 <code>sortKey</code>라는 함수를 통해 데이터를 처리하고, 렌더링 로직도 똑같은데,
왜 &#39;이름순&#39;은 통과하고 &#39;거리순&#39;은 통과하지 못하는가?라는 의문이 들었다.
⬇️ 아래는 sort하는 함수이다.</p>
<pre><code class="language-javascript">  sortByKey(data: RestaurantInfo[], sorting: SortingValues): RestaurantInfo[] {
    const result = data.slice().sort((a, b) =&gt; {
      if (sorting === &#39;이름순&#39;) {
        if (a.name &lt; b.name) return -1;
        if (a.name &gt; b.name) return 1;
        return 0;
      }
      if (sorting === &#39;거리순&#39;) {
        return a.distance - b.distance;
      }

      return 0;
    });

    return result;
  }</code></pre>
<p>&#39;이름순&#39;정렬과 &#39;거리순&#39;정렬의 차이점은 sortByKey 함수안의 각각의 정렬 로직 차이 밖에 없다.</p>
<h3 id="에러-원인---가설-1">에러 원인 - 가설 1</h3>
<p>따라서 여기서 내린 결론은 &#39;거리순&#39;정렬로직 보다 &#39;이름순&#39;정렬 로직 처리 속도가 더 빨라서, &#39;거리순&#39;정렬 테스트에서만 저런 에러가 나는게 아닌가? 였다.</p>
<pre><code class="language-javascript">if (sorting === &#39;거리순&#39;) {
  return a.distance - b.distance;
}</code></pre>
<pre><code class="language-javascript">if (sorting === &#39;이름순&#39;) {
  if (a.name &lt; b.name) return -1;
  if (a.name &gt; b.name) return 1;
  return 0;
}</code></pre>
<p>하지만 &#39;거리순&#39;정렬 로직은 두 숫자 차이 계산이기 때문에 매우 빠르고, &#39;이름순&#39;정렬은 두 문자열 비교이기때문에 비교적으로 숫자열 비교보다 느리다고한다.하지만 이 경우 정렬 로직 둘 다 매우 간단한 연산이기때문에 이런 차이가 테스트 결과에 큰 영향을 미칠 가능성은 낮아보인다.</p>
<p>그럼 도대체 이유가 뭘까?</p>
<h3 id="에러-원인---가설-2">에러 원인 - 가설 2</h3>
<p>곰곰히 생각하던 중 두 정렬의 큰 차이점 하나가 떠올랐다.</p>
<p>바로 각 정렬이 <strong>정렬 필터의</strong> <strong>기본값</strong>인지 아닌지이다.</p>
<p>정렬 필터의 기본값은 <strong>&quot;이름순&quot;</strong>이었다.
그리고 이 때 &#39;이름순&#39;정렬 테스트는 통과했지만 &#39;거리순&#39;정렬 테스트는 통과하지 못했다.</p>
<p>애초에 첫 페이지가 이름순으로 이미 정렬되어있었기 때문에 &#39;이름순&#39;정렬 테스트에서는 <code>cy.find()</code> 명령어 실행 중 타임아웃 에러가 발생하지 않았고,
&#39;거리순&#39;으로 바꿨을 때 비로소 e2e 테스트에서 sorting로직이 처음 발생한것이고 이에 따라 <code>cy.find()</code> 타임아웃 에러가 발생이 한게 아닐까?</p>
<p>⬇️바로 정렬 카테고리 기본값을 아래와 같이 &#39;거리순&#39;으로 바꿔보았다.
<img src="https://velog.velcdn.com/images/jenn_n/post/3d568db9-10eb-4282-b840-bfd11d7fcc24/image.png" alt=""></p>
<p>바로 테스트를 돌려보았다.. 과연 내 생각이 맞을까??</p>
<p><img src="https://velog.velcdn.com/images/jenn_n/post/8a2345f3-0c6b-4e85-897b-e38eadf15b01/image.png" alt=""></p>
<p>맞았다 😭 단순했던 원인을 참 멀리멀리 돌아서 찾은 느낌이다..</p>
<h2 id="그렇다면-해결-방법은">그렇다면 해결 방법은?</h2>
<p>비동기적인 렌더링이 원인이었기때문에  렌더링이 다 될 때 까지 기다리게 하면 해결된다.</p>
<h4 id="1명시적으로-대시-시간-추가해주기">1.명시적으로 대시 시간 추가해주기</h4>
<pre><code class="language-javascript">  it(&#39;거리순을 선택하면 거리순으로 정렬되어야한다.&#39;, function () {
    cy.get(&#39;#sort-filter&#39;).select(&#39;거리순&#39;);
    cy.wait(2000); // 명시적인 대기 시간 추가

    // ....
      });
  });</code></pre>
<h4 id="2재시도-옵션-추가">2.재시도 옵션 추가</h4>
<pre><code class="language-javascript"> it(&#39;거리순을 선택하면 거리순으로 정렬되어야한다.&#39;, function () {
    cy.get(&#39;#sort-filter&#39;).select(&#39;거리순&#39;);

    const sortedRestaurants = [...this.restaurantsData].sort((a, b) =&gt; a.distance - b.distance);

    cy.get(&#39;.restaurant-list-container .restaurant&#39;, { timeout: 10000 }) // 재시도 옵션 추가
      .each(($restaurant, index) =&gt; {
        cy.wrap($restaurant)
          .find(&#39;.restaurant__name&#39;)
          .should(&#39;have.text&#39;, sortedRestaurants[index].name);
        cy.wrap($restaurant)
          .find(&#39;.restaurant__distance&#39;)
          .should(&#39;have.text&#39;, `캠퍼스부터 ${sortedRestaurants[index].distance}분 내`);
      });
  });</code></pre>
<h4 id="3랜더링-완료-확인하기">3.랜더링 완료 확인하기</h4>
<pre><code class="language-javascript">it(&#39;거리순을 선택하면 거리순으로 정렬되어야한다.&#39;, function () {
    cy.get(&#39;#sort-filter&#39;).select(&#39;거리순&#39;);

    const sortedRestaurants = [...this.restaurantsData].sort((a, b) =&gt; a.distance - b.distance);

    cy.get(&#39;.restaurant-list-container .restaurant&#39;) 
      .should(&#39;have.length&#39;, sortedRestaurants.length) // 렌더링 완료 확인
      .each(($restaurant, index) =&gt; {
        cy.wrap($restaurant)
          .find(&#39;.restaurant__name&#39;)
          .should(&#39;have.text&#39;, sortedRestaurants[index].name);
        cy.wrap($restaurant)
          .find(&#39;.restaurant__distance&#39;)
          .should(&#39;have.text&#39;, `캠퍼스부터 ${sortedRestaurants[index].distance}분 내`);
      });
  });</code></pre>
<ul>
<li><pre><code class="language-javascript">Cypress.Commands.add(&#39;waitForRender&#39;, () =&gt; {
cy.get(&#39;.restaurant-list-container&#39;).should(&#39;be.visible&#39;);
});``` // 이렇게 command에 등록후 사용할 수도있음. cy.waitForRender(); 

</code></pre>
</li>
</ul>
<h3 id="트러블-슈팅-회고">트러블 슈팅 회고</h3>
<p> sort 작업에 비동기적인 작업이 들어간다는 사실을 초장에 잘 짚고 넘어갔으면 </p>
<blockquote>
<p>비동기적인 렌더링이 원인이다 
-&gt; 근데 e2e 테스트 에러는 &#39;이름순&#39;,&#39;거리순&#39;중에 한 케이스에서만 에러가 난다
-&gt; 그럼 e2e 테스트에서 sort 작업이 한 번만 이루어지는게 아닌가? 
-&gt; 카테고리 필터의 초기값 차이?</p>
</blockquote>
<p>이렇게 빠르게 에러 원인을 파악할 수 있었을거같은데, 내가 짠 코드를 내가 잘 파악하지 못해 트러블 슈팅을 오랫동안 하게된것같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<dialog> showModal(),close() 동작 안할 때]]></title>
            <link>https://velog.io/@jenn_n/dialog-showModalclose-%EC%9E%91%EB%8F%99-%EC%95%88%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@jenn_n/dialog-showModalclose-%EC%9E%91%EB%8F%99-%EC%95%88%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Mon, 04 Mar 2024 08:46:19 GMT</pubDate>
            <description><![CDATA[<p>이번 로또 미션에서 이런 모달창을 구현해야했다.
시맨틱 태그를 최대한 활용하고 싶어서 <code>&lt;dialog&gt;</code> 태그를 사용하기로했다.</p>
<ul>
<li><code>&lt;dialog&gt;</code> : 기본적으로 open 이라는 boolean 속성을 가지고있으며 이 속성에 따라 보여지고 안보여지고가 정해진다.</li>
<li><code>showModal()</code> :  <code>&lt;dialog&gt;</code> 태그에 open 속성을 추가함으로써 모달창처럼 top layer에 띠운다.</li>
<li><code>close()</code> : <code>&lt;dialog&gt;</code> 태그에서 open 속성을 삭제함으로써 모달창을 닫아주는 인스턴스 메서드이다.</li>
<li><code>::backdrop CSS</code> : 뷰포트 크기의 상자로, 최상위 레이어에서 표시되는 모든 요소 바로 아래에 렌더링된다. deem을 만들기 위해 따로 html 요소를 만들 필요 없다.</li>
</ul>
<p>위와 같은 유용한 인스턴스 메서드,CSS를 이용해 쉽게 모달창을 구현 할 수 있다.</p>
<pre><code class="language-html">&lt;body&gt;
  &lt;dialog id=&quot;modal&quot;&gt;
    &lt;!-- dialog contents --&gt;
  &lt;/dialog&gt;
&lt;/body&gt;</code></pre>
<pre><code class="language-css">#modal {
    display: flex;
}</code></pre>
<p>처음에 이렇게 구현했는데 showModal(),close()를 정상적으로 호출해도 <code>&lt;dialog&gt;</code>창이 계속 떠있었다.</p>
<p>#modal에 넣어둔 display: flex; css 속성 때문에, showModal()을 하기도 전에 브라우저에 dialog가 렌더링되어있었다. 심지어 close()를 해도 이 css 속성 때문에 닫히지 않았다.</p>
<p>이유는 css cascading,브라우저 동작 방식,브라우저 기본 스타일의 충돌 때문인것같다...</p>
<h4 id="css-cascading">css cascading</h4>
<ol>
<li><strong>인라인 스타일</strong>: HTML 요소에 직접 적용된 스타일 (style 속성 사용)이 가장 높은 우선순위를 가진다.</li>
<li><strong>ID 선택자</strong>: #id 형태의 선택자가 다음으로 높은 우선순위를 가집니다.</li>
<li><strong>클래스, 의사 클래스 및 속성 선택자</strong>: .class, :pseudo-class, [attribute] 형태의 선택자가 이에 해당합니다.</li>
<li><strong>요소(태그) 및 의사 요소 선택자</strong>: element, ::pseudo-element 형태의 선택자입니다.</li>
<li><strong>범용 선택자 (*), 관계 선택자 (+, &gt;, ~)</strong>: 가장 낮은 우선순위를 가집니다.</li>
<li>브라우저 기본 스타일 : 제일 꼴지다.</li>
</ol>
<blockquote>
<p>⚠️ <code>&lt;dialog open&gt;</code>이 cascading에서 속성 선택자 css로 작동해서 그보다 우선순위인 ID 선택자 CSS에게 먹혀서(?) 안되는건 줄 알았는데, <code>&lt;dialog&gt;</code>태그의 showModal,close 작동은 css cascading과 관련 없다고한다..</p>
</blockquote>
<h3 id="해결-방법">해결 방법</h3>
<pre><code class="language-css">#modal {
 /* display: flex; 를 제외한 나머지 CSS들 */
}

dialog:open {
  display: flex;
}</code></pre>
<p>이렇게 display css를 속성 선택자 css로 빼왔더니 showModal(),close()가 제대로 작동했다.
`</p>
<blockquote>
</blockquote>
<p>참고 링크 
<a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/dialog">MDN | <code>&lt;dialog&gt;</code> : 대화 상자 요소</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal">MDN | HTMLDialogElement: showModal() 메서드</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::backdrop">MDN | CSS ::backdrop</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/close">MDN | HTMLDialogElement: close() method </a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/css". Strict MIME type checking is enforced for module scripts per HTML spec.]]></title>
            <link>https://velog.io/@jenn_n/Failed-to-load-module-script-Expected-a-JavaScript-module-script-but-the-server-responded-with-a-MIME-type-of-textcss.-Strict-MIME-type-checking-is-enforced-for-module-scripts-per-HTML-spec</link>
            <guid>https://velog.io/@jenn_n/Failed-to-load-module-script-Expected-a-JavaScript-module-script-but-the-server-responded-with-a-MIME-type-of-textcss.-Strict-MIME-type-checking-is-enforced-for-module-scripts-per-HTML-spec</guid>
            <pubDate>Sun, 03 Mar 2024 15:15:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jenn_n/post/929389bc-6efc-496d-b7b6-0b12af285078/image.png" alt="">
git pages에 배포했는데 이런 에러를 마주했다.
이 메시지는 &quot;예상한 것은 JavaScript 모듈 스크립트였지만 서버가 &#39;text/css&#39; MIME 유형으로 응답해서 생긴 에러&quot; 라는 뜻이다.</p>
<p><img src="https://velog.velcdn.com/images/jenn_n/post/eacf85bb-a8d4-4026-9480-3a326bc44b9b/image.png" alt="">
크롬 네트워크 창에서 확인해보니
<code>index.html</code>에서 <code>link</code> 태그로 <code>import</code>한 <code>css</code>들은 <code>stylesheet</code>으로 잘 불러져오고있지만 <code>step2-index.js</code> 에서 import를 한 css들은 stylesheet이 아닌 script로 불러와져있었다.</p>
<pre><code class="language-javascript">// 수정 전
import &#39;./web/css/index.css&#39;;
import &#39;./web/css/reset.css&#39;;

import &#39;./controller/lottoWebGameController.js&#39;;</code></pre>
<pre><code class="language-javascript">// 수정 후 
import &#39;./controller/lottoWebGameController.js&#39;;</code></pre>
<p>step2-index.html에서 css import들은 삭제했더니 해결되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[html] semantic tag]]></title>
            <link>https://velog.io/@jenn_n/html-semantic-tag</link>
            <guid>https://velog.io/@jenn_n/html-semantic-tag</guid>
            <pubDate>Sun, 03 Mar 2024 14:40:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-semantic-tag란">1. Semantic Tag란?</h2>
<blockquote>
<p>semantic | 의미의, 의미론적인</p>
</blockquote>
<p>html에서 <strong>semantic tag</strong>란 의미를 가지는 tag를 말합니다.</p>
<h3 id="1-1--semantic-tag가-생기기-전">1-1 ) semantic tag가 생기기 전</h3>
<p>  HTML5 이전에는 웹 페이지의 구조를 나타내기 위해 위와같이 <code>&lt;div&gt;</code>나 <code>&lt;span&gt;</code> 같은 범용 태그를 사용했습니다. 이 태그들은 내용을 담는 기능은 하지만, 그 내용이 어떤 의미를 가지는지 명시하지 않습니다.</p>
<pre><code class="language-javascript">/* 
.title {
  font-size: 32px;
  font-weight: 600;
} 
*/
&lt;span class=&quot;title&quot;&gt; 글 제목 &lt;span&gt;</code></pre>
<p>글 제목을 나타내는 큰 글씨를 쓰고 싶다면 위와 같이 id나 class 속성을 사용해 이를 구분해야만 했습니다.</p>
<h3 id="1-2-semantic-tag가-생긴-후">1-2. semantic tag가 생긴 후</h3>
<pre><code class="language-javascript">&lt;h1&gt; 글 제목 &lt;h1&gt;</code></pre>
<p>HTML5에서부터 semantic tag가 생기고 난 후,위와 같이 간단히 사용할 수 있게 되었습니다.</p>
<p>  <code>&lt;h1&gt;</code>외에도 <code>&lt;article&gt;</code>, <code>&lt;footer&gt;</code>, <code>&lt;header&gt;</code>등 많은 semantic tag가 생겼고, 이런 다양한 semantic tag가 생김으로서 <strong>높은 가독성</strong>이 생기게 된것입니다.</p>
</br>


<h2 id="2-semantic-tag가-주는-이점">2. semantic tag가 주는 이점</h2>
<p>의미를 가짐으로써 <strong>가독성</strong>이 향상된 태그. 개발자만 이런 가독성을 누리는걸까요?</p>
<h3 id="2-1--웹-크롤러---seo">2-1 ) 웹 크롤러 - SEO</h3>
<p>semantic tag는 개발자의 가독성 뿐만 아니라 웹 크롤러에 의한 가독성도 향상시켜 검색 엔진 최적화(SEO)에 기여합니다.
semantic tag를 활용하면 웹사이트의 메타데이터와 콘텐츠가 명확하게 구조화되어, 검색 엔진은 관련 키워드와 내용을 연결짓는데 필요한 맥락을 더 쉽게 파악할 수 있습니다. 이는 검색 결과에서 웹사이트의 노출 순위를 높이는데 결정적인 역할을 하며, 특정 검색어에 대한 관련성과 정확성을 향상시키는 데에 기여합니다.</p>
<h3 id="2-2--사용자---접근성-향상">2-2 ) 사용자 - 접근성 향상</h3>
<p>웹페이지를 시각적인게 아니라 음성으로 읽어주는 스크린 리더를 이용하거나 키보드만을 이용해서 웹사이트를 이용하는 경우, 적절한 semantic tag를 이용한 웹사이트라면 문제없이 잘 동작 할 수 있습니다.</p>
<h3 id="2-1--개발자---maintainability">2-1 ) 개발자 - Maintainability</h3>
<p>개발자가 코드를 봤을 때 한 눈에 구조를 파악 할 수 있고, 유지보수성도 더 높여서 개발 할 수 있습니다.</p>
<h2 id="3-자주쓰이는-semantic-tag들">3. 자주쓰이는 semantic tag들</h2>
<h4 id="header"><code>&lt;header&gt;</code></h4>
<p><code>&lt;header&gt;</code> 태그는 문서나 섹션의 머리말을 지정합니다. 로고, 탐색 링크, 제목 등 페이지의 소개 정보가 포함된 상단 부분을 정의합니다.</p>
<h4 id="nav"><code>&lt;nav&gt;</code></h4>
<p><code>&lt;nav&gt;</code> 태그는 웹사이트의 메뉴, 탭, 탐색 경로 등을 포함하는 탐색 링크 섹션을 정의합니다. 사용자가 웹사이트를 쉽게 탐색할 수 있도록 돕습니다.</p>
<h4 id="main"><code>&lt;main&gt;</code></h4>
<p><code>&lt;main&gt;</code> 태그는 웹사이트의 주요 콘텐츠를 담습니다. 문서 내에서 한 번만 사용되어야 하며, <code>&lt;article&gt;</code>, <code>&lt;aside&gt;</code>, <code>&lt;footer&gt;</code>, <code>&lt;header&gt;</code>, <code>&lt;nav&gt;</code> 등 다른 페이지 구조 태그들과 구분됩니다.</p>
<h4 id="aside"><code>&lt;aside&gt;</code></h4>
<p><code>&lt;aside&gt;</code> 태그는 페이지 주 콘텐츠와 간접적으로 관련된 콘텐츠를 담는 태그로, 주로 사이드바 혹은 콜아웃 박스로 사용됩니다.</p>
<h4 id="dialog"><code>&lt;dialog&gt;</code></h4>
<p><code>&lt;dialog&gt;</code> 태그는 대화 상자(dialog box) 또는 팝업 창을 나타냅니다. 모달(modal) 또는 비모달(non-modal) 대화 상자를 구현할 때 사용됩니다.</p>
</br>



<h2 id="4-헷갈리는-semantic-tag들">4. 헷갈리는 semantic tag들</h2>
<h3 id="4-1--article-vs-section">4-1 ) <code>&lt;article&gt;</code> vs <code>&lt;section&gt;</code></h3>
<table>
<thead>
<tr>
<th>tag</th>
<th>description</th>
</tr>
</thead>
<tbody><tr>
<td><code>&lt;article&gt;</code></td>
<td>문서, 페이지, 사이트 안에서 독립적으로 구분해 배포하거나 재사용할 수 있는 구획을 나타냅니다. </br>예를 들어, 블로그의 개별 포스트나 신문의 개별 기사 등이 이에 해당합니다. </br><code>&lt;main&gt;</code> 태그 안에서 다른 내용과 전혀 상관 없이 독립적으로 고유한 정보를 나타낼 때 사용합니다.</td>
</tr>
<tr>
<td><code>&lt;section&gt;</code></td>
<td><code>&lt;article&gt;</code> 안에는 여러 정보들이 들어있을 수 있으며, 이때 서로 연관 있는 내용을 묶을 때 사용합니다.</br> <code>&lt;article&gt;</code>의 내부뿐만 아니라 외부에서도 사용할 수 있습니다.</td>
</tr>
</tbody></table>
</br>


<h3 id="4-2--button-vs-a">4-2 ) <code>&lt;button&gt;</code> vs <code>&lt;a&gt;</code></h3>
<p>두 태그 다 CSS만 적용하면 버튼처럼 보이기 때문에 헷갈려하는 경우가 많습니다.</p>
<table>
<thead>
<tr>
<th>tag</th>
<th>description</th>
</tr>
</thead>
<tbody><tr>
<td><code>&lt;button&gt;</code></td>
<td>로그인, 장바구니에 넣기, 구매와 같이 사용자가 특정한 액션을 해야할 때 사용합니다.</td>
</tr>
<tr>
<td><code>&lt;a&gt;</code></td>
<td>홈 버튼과 같이 사용자가 클릭해서 어딘가로 이동해야 할 때 사용합니다.</td>
</tr>
</tbody></table>
</br>


<h3 id="4-3--i-vs-em">4-3 ) <code>&lt;i&gt;</code> vs <code>&lt;em&gt;</code></h3>
<p>둘 다 시각적으로 글꼴을 기울이게해주는 태그지만 의미적으로 차이가 있습니다.</p>
<table>
<thead>
<tr>
<th>tag</th>
<th>description</th>
</tr>
</thead>
<tbody><tr>
<td><code>&lt;i&gt;</code></td>
<td>의미 없이 단순히 시각적으로 글꼴을 기울이고 싶을 때 사용합니다.</td>
</tr>
<tr>
<td><code>&lt;em&gt;</code></td>
<td>글꼴을 기울이면서 <strong>강조</strong>하고 싶을 때 사용합니다.</td>
</tr>
</tbody></table>
</br>



<h3 id="4-4--b-vs-strong">4-4 ) <code>&lt;b&gt;</code> vs <code>&lt;strong&gt;</code></h3>
<p>둘 다 시각적으로 글꼴을 두껍게 해주는 태그지만 의미적으로 차이가 있습니다.</p>
<table>
<thead>
<tr>
<th>tag</th>
<th>description</th>
</tr>
</thead>
<tbody><tr>
<td><code>&lt;b&gt;</code></td>
<td>의미 없이 단순히 시각적으로 글꼴을 두껍게하고 싶을 때 사용합니다.</td>
</tr>
<tr>
<td><code>&lt;strong&gt;</code></td>
<td>글꼴을 두껍게하면서 <strong>강조</strong>하고 싶을 때 사용합니다.</td>
</tr>
</tbody></table>
</br>


<h3 id="4-5--ul-vs-ol-vs-dl">4-5 ) <code>&lt;ul&gt;</code> vs <code>&lt;ol&gt;</code> vs <code>&lt;dl&gt;</code></h3>
<p>공통적으로 리스트를 의미하는 semantic tag들입니다.</p>
<table>
<thead>
<tr>
<th>tag</th>
<th>description</th>
</tr>
</thead>
<tbody><tr>
<td><code>ul</code></td>
<td>순서가 없는 리스트를 나타냅니다.</td>
</tr>
<tr>
<td><code>ol</code></td>
<td>순서가 있는 리스트를 나타냅니다. 순서가 중요할 때 사용합니다.</td>
</tr>
<tr>
<td><code>dl</code></td>
<td>설명이나 정의가 필요한 항목을 나열할 때 사용합니다.</td>
</tr>
<tr>
<td><code>dt</code></td>
<td><code>dl</code> 태그 안에서 사용되며, 설명하고자 하는 용어나 단어를 정의할 때 사용합니다.</td>
</tr>
<tr>
<td><code>dd</code></td>
<td><code>dl</code> 태그 안에서 사용되며, <code>dt</code>로 정의된 용어나 단어에 대한 상세 설명을 제공할 때 사용합니다.</td>
</tr>
</tbody></table>
</br>



<h3 id="4-6--css-background-image-vs-img">4-6 ) <code>css: background-image</code> vs <code>&lt;img&gt;</code></h3>
<p>이미지 요소를 넣을 때 css의 <code>background-image</code>를 쓸지 <code>&lt;img&gt;</code> 태그를 써야할지 헷갈릴 때가 있다.</p>
<table>
<thead>
<tr>
<th>tag</th>
<th>description</th>
</tr>
</thead>
<tbody><tr>
<td><code>background-image</code></td>
<td>이미지에 의미가 딱히 없고 단순히 시각적인 효과를 위해 배경이미지 등으로 사용할 때는 css로 추가한다.</td>
</tr>
<tr>
<td><code>&lt;img&gt;</code></td>
<td>해당 페이지를 이해할 때 이 이미지가 꼭 필요하다 싶을 때는 <code>&lt;img&gt;</code>태그를 사용한다.</td>
</tr>
</tbody></table>
</br>





<hr>
<h3 id="참고-자료">참고 자료</h3>
<ul>
<li><p><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element">https://developer.mozilla.org/ko/docs/Web/HTML/Element</a></p>
</li>
<li><p><a href="https://www.youtube.com/watch?v=T7h8O7dpJIg&amp;t=7s">https://www.youtube.com/watch?v=T7h8O7dpJIg&amp;t=7s</a></p>
</li>
<li><p><a href="https://seo.tbwakorea.com/blog/what-is-semantic-tag/#part6">https://seo.tbwakorea.com/blog/what-is-semantic-tag/#part6</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🤖 좋은 테스트 코드 | CORRECT 원칙]]></title>
            <link>https://velog.io/@jenn_n/%EC%A2%8B%EC%9D%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-CORRECT-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@jenn_n/%EC%A2%8B%EC%9D%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-CORRECT-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Wed, 01 Nov 2023 09:16:51 GMT</pubDate>
            <description><![CDATA[<h1 id="1-correct-원칙이란-">1. CORRECT 원칙이란 ?</h1>
<hr>
<h4 id="correct는-테스트-케이스를-작성할-때-고려해야-할-다양한-상황들을-묶어놓은-체크리스트"><strong>CORRECT</strong>는 테스트 케이스를 작성할 때 고려해야 할 다양한 상황들을 묶어놓은 체크리스트</h4>
<blockquote>
<p><strong>C</strong> - Conformance (준수)
<strong>O</strong> - Ordering (순서)
<strong>R</strong> - Range (범위)
<strong>R</strong> - Reference (참조)
<strong>E</strong> - Existence (존재)
<strong>C</strong> - Cardinality (개수)
<strong>T</strong> - Time (시간)</p>
</blockquote>
</br>

<h1 id="2-예시와-설명">2. 예시와 설명</h1>
<h2 id="1-c---conformance-준수">1) C - Conformance (준수)</h2>
<hr>
<h3 id="💬상황"><strong>💬&nbsp;&nbsp;상황</strong></h3>
<blockquote>
<p>테스트는 표준, 형식, 명세 등을 잘 따르고 있는지 확인한다. 예를 들어, 이메일 주소나 전화번호 형식을 제대로 준수하는지 테스트한다.</p>
</blockquote>
<h3 id="💻예시-코드"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { validatePhoneNumber } = require(&#39;./phoneValidation&#39;);

describe(&#39;Phone Number Validation Tests&#39;, () =&gt; {
  test(&#39;should conform to phone number format&#39;, () =&gt; {
    expect(validatePhoneNumber(&#39;+12345678900&#39;)).toBeTruthy();
    expect(validatePhoneNumber(&#39;invalid-phone&#39;)).toBeFalsy();
  });
});

// phoneValidation.js
function validatePhoneNumber(phone) {
  const re = /^\+\d{11}$/;
  return re.test(phone);
}

module.exports = { validatePhoneNumber };</code></pre>
<ul>
<li>이 코드는 전화번호 형식을 제대로 준수하는지 확인하는 <code>validatePhoneNumber</code> 함수에 대한 테스트를 보여준다. 올바른 형식의 전화번호 <code>(&#39;+12345678900&#39;)</code>와 잘못된 형식 <code>(&#39;invalid-phone&#39;)</code>에 대한 검증을 수행함으로써, 전화번호의 정확한 형식을 올바르게 준수하는지 확인한다. 이처럼 명세와 표준을 준수하는 테스트는 소프트웨어가 외부 시스템과 잘 통합될 수 있도록 보장한다.</br>

</li>
</ul>
<h2 id="5-o---ordering-순서">5) O - Ordering (순서)</h2>
<hr>
<h3 id="💬상황-1"><strong>💬&nbsp;&nbsp;상황</strong></h3>
<blockquote>
<p>테스트는 데이터나 이벤트의 순서가 중요할 때 그 순서를 잘 지키고 있는지 확인한다. 예를 들어, 거래 내역이 올바른 순서대로 저장되는지, 프로세스 단계가 올바른 순서로 진행되는지 등을 살핀다.</p>
</blockquote>
<h3 id="💻예시-코드-1"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { processSteps } = require(&#39;./processManager&#39;);

describe(&#39;Process Ordering Tests&#39;, () =&gt; {
  test(&#39;should follow the correct order of process steps&#39;, () =&gt; {
    const stepsOrder = processSteps();
    expect(stepsOrder).toEqual([&#39;Step1&#39;, &#39;Step2&#39;, &#39;Step3&#39;]);
  });
});

// processManager.js
function processSteps() {
  // 프로세스 단계 실행 로직
  return [&#39;Step1&#39;, &#39;Step2&#39;, &#39;Step3&#39;];  // 예시를 위한 단순화된 순서
}

module.exports = { processSteps };</code></pre>
<ul>
<li>이 코드는 프로세스의 단계가 올바른 순서대로 진행되는지 확인하는 <code>processSteps</code> 함수에 대한 테스트를 나타낸다. &#39;Step1&#39;, &#39;Step2&#39;, &#39;Step3&#39; 순서로 진행되는지 검증함으로써, 프로세스의 순서가 올바르게 유지되고 있는지 확인한다. 이러한 순서적 정확성을 검증하는 테스트는 복잡한 워크플로우와 프로세스가 올바르게 관리되고 있음을 보장한다.</li>
</ul>
</br>

<h2 id="6-r---range-범위">6) R - Range (범위)</h2>
<hr>
<h3 id="💬상황-2"><strong>💬&nbsp;&nbsp;상황</strong></h3>
<blockquote>
<p>테스트는 입력이나 출력 값의 범위를 검사한다. 예를 들어, 나이 입력 필드가 0에서 120 사이의 값을 받는지, 계산 결과가 특정 범위 내에 있는지 등을 확인한다.</p>
</blockquote>
<h3 id="💻예시-코드-2"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { calculateInterest } = require(&#39;./financialCalculator&#39;);

describe(&#39;Interest Calculation Range Tests&#39;, () =&gt; {
  test(&#39;should return interest within valid range&#39;, () =&gt; {
    const interest = calculateInterest(1000, 5);
    expect(interest).toBeGreaterThanOrEqual(50);
    expect(interest).toBeLessThanOrEqual(150);
  });
});

// financialCalculator.js
function calculateInterest(principal, rate) {
  // 복리 계산을 위한 간단한 로직 예시
  return principal * (rate / 100);
}

module.exports = { calculateInterest };</code></pre>
<ul>
<li>이 코드는 금융 계산의 결과가 특정 범위 내에 있는지 확인하는 <code>calculateInterest</code> 함수에 대한 테스트를 나타낸다. 예금액 1000에 대해 이자율 5%가 적용되었을 때, 계산된 이자가 50 이상 150 이하인지 검증함으로써, 이자 계산이 합리적인 범위 내에서 이루어지고 있는지 확인한다. 이처럼 범위를 검증하는 테스트는 입력과 출력 값이 예상 가능한 범위 내에서 정확하게 처리되고 있음을 보장한다.</li>
</ul>
</br>

<h2 id="3-r---reference-참조">3) R - Reference (참조)</h2>
<hr>
<h3 id="💬상황-3"><strong>💬&nbsp;&nbsp;상황</strong></h3>
<blockquote>
<p>테스트는 외부 시스템이나 모듈에 대한 참조가 올바르게 이루어지고 있는지 확인한다. 예를 들어, 데이터베이스 연결, 파일 시스템 접근 등 외부 자원의 참조를 확인한다.</p>
</blockquote>
<h3 id="💻예시-코드-3"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { readConfig } = require(&#39;./configReader&#39;);

describe(&#39;Config File Reference Tests&#39;, () =&gt; {
  test(&#39;should correctly reference and read the config file&#39;, () =&gt; {
    const config = readConfig(&#39;/path/to/config.json&#39;);
    expect(config).toHaveProperty(&#39;databaseUrl&#39;);
  });
});

// configReader.js
const fs = require(&#39;fs&#39;);

function readConfig(filePath) {
  try {
    const rawData = fs.readFileSync(filePath);
    return JSON.parse(rawData);
  } catch (error) {
    throw new Error(&#39;Unable to read config file&#39;);
  }
}

module.exports = { readConfig };</code></pre>
<ul>
<li>이 코드는 외부의 JSON 구성 파일을 참조하여 필요한 데이터를 올바르게 읽어오는지 검사하는 <code>readConfig</code> 함수에 대한 테스트를 보여준다. 특히, 구성 파일이 databaseUrl 속성을 포함하고 있는지 확인함으로써, 외부 파일에 대한 참조와 데이터 읽기 과정이 정상적으로 이루어지고 있는지 검증한다. 이처럼 참조를 검증하는 테스트는 소프트웨어가 외부 자원을 정확하게 다루고 있음을 확인한다.</br>

</li>
</ul>
<h2 id="4-e---existence-존재">4) E - Existence (존재)</h2>
<hr>
<h3 id="💬상황-4"><strong>💬&nbsp;&nbsp;상황</strong></h3>
<blockquote>
<p>테스트는 필요한 값, 변수, 파일, 데이터 등이 실제로 존재하는지 확인한다. 예를 들어, 필요한 파일이 디렉토리에 존재하는지, 필수 데이터 필드가 비어 있지 않은지 등을 검사한다.</p>
</blockquote>
<h3 id="💻예시-코드-4"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const fs = require(&#39;fs&#39;);
const { expect } = require(&#39;chai&#39;);

describe(&#39;File Existence Tests&#39;, () =&gt; {
  it(&#39;should confirm that the required file exists&#39;, () =&gt; {
    const filePath = &#39;./data/users.json&#39;;
    const fileExists = fs.existsSync(filePath);
    expect(fileExists).to.be.true;
  });
});

// data/users.json
// {
//   &quot;users&quot;: [...]
// }</code></pre>
<ul>
<li>이 코드는 특정 경로에 필요한 <code>users.json</code> 파일이 실제로 존재하는지를 검사한다. 파일의 존재 여부를 확인함으로써, 애플리케이션이 필요한 데이터에 접근할 수 있는지 검증한다. 존재성을 확인하는 테스트는 애플리케이션의 안정성과 데이터 무결성을 보장하는 데 중요한 역할을 한다.</li>
</ul>
</br>

<h2 id="5-c---cardinality-개수">5) C - Cardinality (개수)</h2>
<hr>
<h3 id="💬상황-5">💬&nbsp;&nbsp;상황</h3>
<blockquote>
<p>테스트는 콜렉션이나 그룹 내의 요소 개수가 정확하고 적절한지 확인한다. 예를 들어, 사용자 목록에서 100명의 사용자가 중복 없이 있는지 등을 검증한다.</p>
</blockquote>
<h3 id="💻예시-코드-5">💻&nbsp;&nbsp;예시 코드</h3>
<pre><code class="language-javascript">const { getUserCount } = require(&#39;./userManagement&#39;);

describe(&#39;User Count Validation Tests&#39;, () =&gt; {
  test(&#39;should have correct number of users without duplication&#39;, () =&gt; {
    const users = [
      { id: 1, name: &quot;Alice&quot; },
      { id: 2, name: &quot;Bob&quot; },
      // ... 기타 사용자
      { id: 100, name: &quot;Zoe&quot; }
    ];
    expect(getUserCount(users)).toBe(100);
  });
});

// userManagement.js
function getUserCount(users) {
  const uniqueUsers = new Set(users.map(user =&gt; user.id));
  return uniqueUsers.size;
}

module.exports = { getUserCount };</code></pre>
<ul>
<li>이 코드는 사용자 목록에서 중복 없이 100명의 사용자가 있는지 확인하는 <code>getUserCount</code> 함수에 대한 테스트를 보여준다. <code>Set</code> 객체를 사용하여 중복된 사용자 ID를 제거하고, 결과적으로 유일한 사용자의 수를 반환한다. 이런 식으로 요소의 개수를 정확하게 관리하는 테스트는 데이터의 정확성과 신뢰성을 보장한다.</li>
</ul>
</br>

<h2 id="6-t---time-시간">6) T - Time (시간)</h2>
<hr>
<h3 id="💬상황-6">💬&nbsp;&nbsp;상황</h3>
<blockquote>
<p>테스트는 처리 시간, 응답 시간, 이벤트 순서 등 시간과 관련된 속성들이 기대치를 만족하는지 확인한다. 예를 들어, 데이터 처리에 소요되는 시간이나 캐시 만료 시간을 검증한다.</p>
</blockquote>
<h3 id="💻예시-코드-6">💻&nbsp;&nbsp;예시 코드</h3>
<pre><code class="language-javascript">const { processData } = require(&#39;./dataProcessor&#39;);

describe(&#39;Data Processing Time Tests&#39;, () =&gt; {
  test(&#39;should process data within acceptable time limit&#39;, () =&gt; {
    const startTime = performance.now();
    processData(largeDataSet);
    const endTime = performance.now();
    const processingTime = endTime - startTime;

    expect(processingTime).toBeLessThan(1000); // 예상 처리 시간 1초 미만
  });
});

// dataProcessor.js
function processData(data) {
  // 복잡한 데이터 처리 로직
}

module.exports = { processData };</code></pre>
<ul>
<li>이 코드는 대용량 데이터 세트를 처리하는 데 걸리는 시간을 측정하여 예상 처리 시간 내에 완료되는지 검증하는 <code>processData</code> 함수에 대한 테스트를 보여준다. 성능이 중요한 애플리케이션에서는 이러한 시간 기반 테스트를 통해 사용자 경험을 향상시키고 시스템의 효율성을 보장한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🤖 좋은 테스트 코드 | Right-BICEP 원칙]]></title>
            <link>https://velog.io/@jenn_n/%EC%A2%8B%EC%9D%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-RIGHT-BICEP-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@jenn_n/%EC%A2%8B%EC%9D%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-RIGHT-BICEP-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Wed, 01 Nov 2023 07:55:23 GMT</pubDate>
            <description><![CDATA[<h1 id="1-right-bicep-원칙이란-">1. Right-BICEP 원칙이란 ?</h1>
<blockquote>
<p>&quot;Right-BICEP&quot;은 효과적인 단위 테스트를 위한 기본 원칙들을 나타내는 약어이며, 테스트가 중점을 두어야 하는 핵심 영역들을 강조한다.</p>
</blockquote>
</br>

<h2 id="1-r---right-정확함">1) R - Right (정확함)</h2>
<hr>
<h3 id="💬상황-">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>테스트가 실제로 중요한 사항을 검증하고 있는지 확인해야 한다. 예를 들어, 사용자의 이메일 주소 형식이 올바른지 검증하는 테스트의 경우, 다양한 형식의 이메일 주소에 대한 테스트를 진행하여, 실제로 유효한 형식의 이메일 주소만을 허용하도록 해야 한다.</p>
</blockquote>
<h3 id="💻예시-코드"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">  const { validateEmail } = require(&#39;./validation&#39;);

  describe(&#39;Email Validation Tests&#39;, () =&gt; {
    test(&#39;should validate email address correctly&#39;, () =&gt; {
      expect(validateEmail(&#39;test@example.com&#39;)).toBeTruthy();
      expect(validateEmail(&#39;invalid-email&#39;)).toBeFalsy();
    });
  });

  // validation.js
  function validateEmail(email) {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return re.test(email);
  }

  module.exports = { validateEmail };</code></pre>
<ul>
<li>이 코드는 이메일 주소가 올바른 형식인지 검증하는 <code>validateEmail</code> 함수에 대한 테스트를 보여준다. 유효한 이메일 형식 (&#39;test@example.com&#39;)과 유효하지 않은 이메일 형식 (&#39;invalid-email&#39;)에 대한 검증을 수행함으로써, 이메일 주소의 정확한 형식을 올바르게 검증하는지 확인한다. 이처럼 중요한 사항을 정확히 검증하는 테스트는 소프트웨어의 신뢰성을 높이는 데 기여한다.</li>
</ul>
</br>

<h2 id="2-b---boundary-conditions-경계-조건">2) B - Boundary Conditions (경계 조건)</h2>
<hr>
<h3 id="💬상황--1">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>경계 조건 테스트는 배열, 리스트, 문자열 등의 경계에서 발생할 수 있는 특별한 상황들을 다룬다. 예를 들어, 배열의 첫 번째나 마지막 요소를 처리하는 로직, 빈 배열이나 문자열을 다루는 로직 등이 이에 해당한다. 이러한 경계 조건에서도 코드가 올바르게 작동하는지 확인하는 것이 중요하다.</p>
</blockquote>
<h3 id="💻예시-코드-1"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { getLastItem } = require(&#39;./arrayUtil&#39;);

describe(&#39;Array Boundary Conditions Tests&#39;, () =&gt; {
  test(&#39;should handle empty array&#39;, () =&gt; {
    expect(getLastItem([])).toBeNull();
  });

  test(&#39;should return last item for non-empty array&#39;, () =&gt; {
    expect(getLastItem([1, 2, 3])).toBe(3);
  });
});

// arrayUtil.js
function getLastItem(array) {
  if (array.length === 0) return null;
  return array[array.length - 1];
}

module.exports = { getLastItem };</code></pre>
<ul>
<li>이 코드는 배열의 경계 조건을 테스트한다. <code>getLastItem</code> 함수는 주어진 배열의 마지막 요소를 반환하는데, 빈 배열일 경우 <code>null</code>을 반환해야 한다. Jest를 사용하여 빈 배열과 비어 있지 않은 배열 모두를 테스트함으로써, 이 함수가 배열의 경계 조건에서도 올바르게 작동하는지 검증한다. 경계 조건을 테스트함으로써 소프트웨어의 견고함을 높일 수 있다.</li>
</ul>
</br>

<h2 id="3-i---inverse-relationship-역-관계">3) I - Inverse Relationship (역 관계)</h2>
<hr>
<h3 id="💬상황--2">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>역 관계 테스트는 소프트웨어의 다른 부분이 반대 또는 상호 보완적인 기능을 수행하는지 확인한다. 예를 들어, 추가(Add) 기능을 테스트한 후, 제거(Remove) 기능을 테스트하여 두 기능이 서로 올바르게 동작하는지 확인하는 경우가 이에 해당한다.</p>
</blockquote>
<h3 id="💻예시-코드-2"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { addItem, removeItem, getItems } = require(&#39;./listManager&#39;);

describe(&#39;List Inverse Relationship Tests&#39;, () =&gt; {
  test(&#39;adding and then removing an item should result in an empty list&#39;, () =&gt; {
    addItem(&#39;apple&#39;);
    removeItem(&#39;apple&#39;);
    expect(getItems()).toEqual([]);
  });
});

// listManager.js
let items = [];

function addItem(item) {
  items.push(item);
}

function removeItem(item) {
  const index = items.indexOf(item);
  if (index &gt; -1) {
    items.splice(index, 1);
  }
}

function getItems() {
  return items;
}

module.exports = { addItem, removeItem, getItems };</code></pre>
<ul>
<li>이 코드에서는 <code>addItem</code>으로 항목을 추가하고, <code>removeItem</code>으로 같은 항목을 제거한 후, <code>getItems</code>를 사용하여 최종 목록이 비어 있는지 검증한다. 이런 역 관계 테스트는 소프트웨어의 서로 다른 기능들이 상호 보완적으로 올바르게 작동하는지 확인하는 데 유용하다.</li>
</ul>
</br>

<h2 id="4-c---cross-check-교차-검증">4) C - Cross-check (교차 검증)</h2>
<hr>
<h3 id="💬상황--3">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>교차 검증은 서로 다른 방식으로 계산하거나 확인하는 것을 의미한다. 예를 들어, 두 가지 다른 알고리즘으로 동일한 결과를 계산하여 두 결과를 비교하는 것이다. 이 방식으로, 알고리즘의 정확성을 보다 확실하게 검증할 수 있다.</p>
</blockquote>
<h3 id="💻예시-코드-3"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { sumDirect, sumIterative } = require(&#39;./sumCalculators&#39;);

describe(&#39;Sum Calculation Cross-Check Tests&#39;, () =&gt; {
  test(&#39;both sum calculation methods should return the same result&#39;, () =&gt; {
    const numbers = [1, 2, 3, 4, 5];
    expect(sumDirect(numbers)).toEqual(sumIterative(numbers));
  });
});

// sumCalculators.js
function sumDirect(numbers) {
  return numbers.reduce((a, b) =&gt; a + b, 0);
}

function sumIterative(numbers) {
  let sum = 0;
  for (let num of numbers) {
    sum += num;
  }
  return sum;
}

module.exports = { sumDirect, sumIterative };</code></pre>
<ul>
<li>이 코드에서는 <code>sumDirect</code> 메서드가 reduce 함수를 사용하여 배열의 합을 계산하고, <code>sumIterative</code> 메서드는 반복문을 사용하여 동일한 작업을 수행한다. 두 메서드가 동일한 입력에 대해 동일한 결과를 반환하는지 Jest로 테스트하여 교차 검증한다.</li>
</ul>
</br>

<h2 id="5-e---error-conditions-오류-조건">5) E - Error conditions (오류 조건)</h2>
<hr>
<h3 id="💬상황--4">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>오류 조건 테스트는 소프트웨어가 예상 및 예상치 못한 오류 상황에서 올바르게 동작하는지 확인한다. 예를 들어, 잘못된 입력값, 시스템 리소스 부족, 의존 서비스의 실패 등 다양한 오류 상황을 시뮬레이션하고 적절한 오류 처리가 이루어지는지 검사한다.</p>
</blockquote>
<h3 id="💻예시-코드-4"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { processData } = require(&#39;./dataProcessor&#39;);

describe(&#39;Error Conditions Test&#39;, () =&gt; {
  test(&#39;should handle invalid input gracefully&#39;, () =&gt; {
    expect(() =&gt; processData(null)).toThrow(&#39;Invalid input&#39;);
    expect(() =&gt; processData({})).toThrow(&#39;Invalid data structure&#39;);
  });

  test(&#39;should handle external service failure&#39;, async () =&gt; {
    // 여기서는 외부 서비스 실패를 시뮬레이션하는 방법이 포함될 수 있습니다.
    // 예를 들어, Mock이나 Spy를 사용하여 네트워크 오류를 시뮬레이션 할 수 있다.
    // 해당 부분은 프로젝트와 테스트 환경에 따라 다르게 구현될 수 있다.
  });
});

// dataProcessor.js
function processData(data) {
  if (!data) {
    throw new Error(&#39;Invalid input&#39;);
  }
  // ... 데이터 처리 로직 ...
}

module.exports = { processData };</code></pre>
<ul>
<li>이 코드는 <code>processData</code> 함수가 유효하지 않거나 예상치 못한 입력값을 받았을 때 적절한 오류 메시지를 발생시키는지 테스트한다. 이러한 종류의 테스트는 예외 상황에서도 시스템이 견고하게 동작하는지 확인하는 데 중요하다.</li>
</ul>
</br>

<h2 id="6-p---performance-characteristics-성능-특성">6) P - Performance characteristics (성능 특성)</h2>
<hr>
<h3 id="💬상황--5">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>성능 특성 테스트는 소프트웨어의 처리 속도, 메모리 사용량, 디스크 공간 사용 등의 성능 지표를 평가한다. 예를 들어, 웹 애플리케이션의 API 응답 시간을 측정하거나, 대용량 데이터 처리 시 메모리 사용량을 분석할 수 있다.</p>
</blockquote>
<h3 id="💻예시-코드-5"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { largeDataProcessor } = require(&#39;./processor&#39;);

describe(&#39;Performance Test&#39;, () =&gt; {
  test(&#39;should process data within acceptable time&#39;, () =&gt; {
    const largeData = generateLargeData(); // 대용량 데이터 생성 함수
    const startTime = performance.now();

    largeDataProcessor(largeData);

    const endTime = performance.now();
    const processingTime = endTime - startTime;

    expect(processingTime).toBeLessThan(1000); // 1000ms 이내 완료되어야 함
  });

  test(&#39;should not exceed memory limit&#39;, () =&gt; {
    const largeData = generateLargeData();

    const initialMemoryUsage = process.memoryUsage().heapUsed;
    largeDataProcessor(largeData);
    const finalMemoryUsage = process.memoryUsage().heapUsed;

    expect(finalMemoryUsage - initialMemoryUsage).toBeLessThan(100000000); // 100MB 이내로 제한
  });
});

// processor.js
function largeDataProcessor(data) {
  // ... 대용량 데이터 처리 로직 ...
}

module.exports = { largeDataProcessor };</code></pre>
<ul>
<li>이 코드는 대용량 데이터 처리 함수 <code>largeDataProcessor</code>가 정해진 시간 내에 데이터를 처리할 수 있으며, 메모리 사용량이 특정 한계를 넘지 않는지 확인한다. 이러한 성능 테스트는 시스템의 효율성과 확장성을 검증하는 데 중요하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🤖 좋은 테스트 코드 | FIRST 원칙]]></title>
            <link>https://velog.io/@jenn_n/%EC%A2%8B%EC%9D%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-FIRST-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@jenn_n/%EC%A2%8B%EC%9D%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-FIRST-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Wed, 01 Nov 2023 06:41:28 GMT</pubDate>
            <description><![CDATA[<h1 id="1-first-원칙이란">1. FIRST 원칙이란?</h1>
<hr>
<h4 id="first--효과적인-테스트-코드를-작성하기-위한-원칙들의-첫-글자를-모아-만든-약어">FIRST : 효과적인 테스트 코드를 작성하기 위한 원칙들의 첫 글자를 모아 만든 약어</h4>
<blockquote>
<p><strong>F</strong> - Fast (빠름): 테스트는 빠르게 실행되어야 한다.
<strong>I</strong> - Independent/Isolated (독립적/격리된): 각 테스트는 서로 독립적이어야 하며, 다른 테스트와 공유 상태를 가지지 않아야 한다.
<strong>R</strong> - Repeatable (반복 가능): 테스트는 어떤 환경에서도 반복 가능해야 한다.
<strong>S</strong> - Self-Validating (자체 검증 가능): 테스트는 예상 결과를 스스로 검증할 수 있어야 하며, 수동 검사를 요구하지 않아야 한다.
<strong>T</strong> - Timely (적시의): 테스트는 적절한 시기에 작성되어야 한다. 일반적으로 테스트 코드는 실제 코드를 작성하기 전이나 동시에 작성되어야 한다.</p>
</blockquote>
</br>

<h1 id="2-예시와-설명">2. 예시와 설명</h1>
<hr>
<h2 id="2-1-f---fast-빠름">2-1) F - Fast (빠름)</h2>
<h3 id="💬상황-">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>웹 서비스의 로그인 기능을 테스트하는 경우, 실제 데이터베이스나 외부 시스템 호출 대신에 Mock 객체나 인-메모리 데이터베이스를 사용함으로써 테스트 속도를 개선한다.</p>
</blockquote>
<h3 id="💻예시-코드"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">const { AuthenticationService, UserRepository } = require(&#39;./myservice&#39;);

describe(&#39;FastLoginTest&#39;, () =&gt; {
  test(&#39;test login with mock&#39;, () =&gt; {
    // UserRepository의 Mock 객체를 생성함
    const mockUserRepo = {
      validateUser: jest.fn()
    };

    // Mock 객체가 항상 True를 반환하도록 설정함
    mockUserRepo.validateUser.mockReturnValue(true);

    // AuthenticationService 인스턴스를 Mock UserRepository와 함께 생성함
    const authService = new AuthenticationService(mockUserRepo);

    // 로그인 함수가 예상대로 작동하는지 검증함
    expect(authService.login(&#39;username&#39;, &#39;password&#39;)).toBeTruthy();
  });
});</code></pre>
<ul>
<li>이 코드에서 <strong>UserRepository</strong>는 Mock 객체로 대체되어 데이터베이스나 네트워크 호출 없이 <code>validate_user</code> 메소드가 호출될 때 항상 <code>True</code>를 반환하도록 설정되었다. 이런 방식은 테스트 속도를 대폭 향상시킨다.</li>
</ul>
</br>

<h2 id="2-2-i---independentisolated-독립적격리된">2-2) I - Independent/Isolated (독립적/격리된)</h2>
<hr>
<h3 id="💬상황--1">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>각 단위 테스트는 다른 테스트의 결과나 상태에 영향을 받지 않고 독립적으로 실행되어야 한다. 예를 들어, 사용자 관리 시스템에서 한 테스트가 사용자를 추가하는 경우, 다른 테스트에서는 이 추가된 사용자에 의존해서는 안 된다.</p>
</blockquote>
<h3 id="💻예시-코드-1"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">   const { UserService } = require(&#39;./UserService&#39;);

describe(&#39;IndependentUserTests&#39;, () =&gt; {
  let userService;

  // 각 테스트 전에 userService를 새로 초기화하여 각 테스트가 독립적으로 실행됨
  beforeEach(() =&gt; {
    userService = new UserService();
  });

  // 새로운 사용자 추가 기능을 테스트
  test(&#39;test add user&#39;, () =&gt; {
    userService.addUser(&quot;newuser1&quot;);
    expect(userService.userExists(&quot;newuser1&quot;)).toBeTruthy();
  });

  // 사용자 제거 기능을 테스트
  test(&#39;test remove user&#39;, () =&gt; {
    userService.addUser(&quot;temporaryuser&quot;);
    userService.removeUser(&quot;temporaryuser&quot;);
    expect(userService.userExists(&quot;temporaryuser&quot;)).toBeFalsy();
  });
});</code></pre>
<ul>
<li>변환된 코드에서 <code>beforeEach</code>는 각 테스트가 시작하기 전에 <code>userService</code> 인스턴스를 초기화한다. 이렇게 하면 각각의 테스트가 서로 독립적으로 실행된다. 첫 번째 테스트(&#39;test add user&#39;)는 사용자 추가 기능을, 두 번째 테스트(&#39;test remove user&#39;)는 사용자 제거 기능을 테스트한다. 이 방식을 통해 각 테스트는 다른 테스트의 실행 상태나 결과에 영향을 받지 않으며 독립적으로 수행된다.</li>
</ul>
</br>


<h2 id="2-3-s---self-validating-자체-검증">2-3) S - Self-Validating (자체 검증)</h2>
<hr>
<h3 id="💬상황--2">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>테스트 케이스는 스스로 결과가 올바른지를 판단할 수 있어야 한다. 예를 들어, 파일에서 특정 데이터를 읽는 기능을 테스트할 때, 테스트 코드는 예상되는 결과 값을 가지고 실제 결과와 자동으로 비교해야 한다. 수동으로 결과를 확인할 필요가 없어야 한다.</p>
</blockquote>
<h3 id="💻예시-코드-2"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript"> const { DataProcessor } = require(&#39;./DataProcessor&#39;);

describe(&#39;SelfValidatingTest&#39;, () =&gt; {
  test(&#39;data reading from file&#39;, () =&gt; {
    const processor = new DataProcessor();
    const expectedOutput = { name: &quot;John&quot;, age: 30 };
    expect(processor.readDataFromFile(&quot;user_data.txt&quot;)).toEqual(expectedOutput);
  });
});</code></pre>
<ul>
<li><p>위의 코드에서 <strong>DataProcessor</strong>의 <code>readDataFromFile</code> 메소드를 사용하여 <code>user_data.txt</code> 파일에서 데이터를 읽는다. 테스트는 읽은 데이터가 예상되는 결과인 <code>{ name: &quot;John&quot;, age: 30 }</code>와 같은지를 자동으로 검증한다. 결과 확인이 자동화되어 있기 때문에 개발자는 매번 수동으로 결과를 검사할 필요가 없다. 이렇게 자동 검증을 하는 테스트는 오류 가능성을 줄이고, 테스트 과정의 효율성을 높여준다.</p>
</br>

</li>
</ul>
<h2 id="2-4-t---timely-적시에">2-4) T - Timely (적시에)</h2>
<hr>
<h3 id="💬상황--3">*<em>💬&nbsp;&nbsp;상황 *</em></h3>
<blockquote>
<p>테스트는 적절한 시기에 작성되어야 한다. 이는 보통 테스트 대상 기능이 개발되기 바로 전을 의미한다. 예를 들어, 새로운 기능을 개발하기 전에 그 기능을 검증할 테스트를 먼저 작성하는 것을 말한다. 이 방식은 테스트 주도 개발(Test-Driven Development, TDD)의 핵심 원칙 중 하나이다.</p>
</blockquote>
<h3 id="💻예시-코드-3"><strong>💻&nbsp;&nbsp;예시 코드</strong></h3>
<pre><code class="language-javascript">    import unittest
    from my_app import new_feature_function

    class TimelyTest(unittest.TestCase):
        def test_new_feature(self):
            # 아직 구현되지 않은 새로운 기능을 테스트한다.
            # 테스트가 실패하는 것은 구현되지 않았기 때문이다.
            self.assertEqual(new_feature_function(), expected_result)

    if __name__ == &quot;__main__&quot;:
        unittest.main()</code></pre>
<ul>
<li>이 코드에서 <code>new_feature_function</code>은 아직 구현되지 않았다. 우리는 먼저 이 기능에 대한 테스트를 작성하고, 그 다음에 실제 기능을 구현한다. 이렇게 테스트를 먼저 작성하면 설계의 문제를 조기에 발견하고, 추후 코드를 리팩토링할 때 실수를 줄일 수 있는 이점이 있다. 또한, 기능이 올바르게 작동하는지 확인할 수 있는 자동 검증 수단을 미리 갖추게 된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🗒️ Jest 기본 문법 정리]]></title>
            <link>https://velog.io/@jenn_n/Jest-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jenn_n/Jest-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 01 Nov 2023 03:58:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://github.com/0jenn0/javascript-racingcar-6">우테코 6기 프리코스 2주차</a>에 접어들면서
테스트 코드 작성을 위해 기본적인 Jest 문법을 정리해보았다.</p>
</blockquote>
<h2 id="0-jest-설치-및-설정">0. Jest 설치 및 설정</h2>
<h3 id="0-1-설치">0-1. 설치</h3>
<pre><code class="language-bash">npm init</code></pre>
<pre><code class="language-bash">npm install jest --save-dev</code></pre>
</br>

<ul>
<li>추천 : vscode 코드 자동 완성 기능 사용 가능<pre><code class="language-bash">npm install jest @types/jest --save-dev
</code></pre>
</li>
</ul>
<pre><code>&lt;/br&gt;

### 0-2. 설정
+ **package.json** 수정해준다.
```javascript
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;jest&quot;
  },</code></pre><blockquote>
<p>jest는 <strong>test</strong> 폴더 안에 넣은 파일들, <code>.test.js</code> ,<code>.spec.js</code>형식의 파일들을 자동으로 test 코드로 인식한다.</p>
</blockquote>
</br>
</br>

<h2 id="1-테스트-기본-구조">1. 테스트 기본 구조</h2>
<h3 id="1-1-테스트-블록">1-1. 테스트 블록</h3>
<ul>
<li><p>test 및 it: Jest에서는 test와 it이 동일한 기능을 수행한다. 두 함수 모두 단일 테스트 케이스를 정의한다.</p>
<pre><code class="language-javascript">// test 함수 사용 예
test(&#39;두 숫자의 합&#39;, () =&gt; {
expect(1 + 2).toBe(3);
});

// it 함수 사용 예
it(&#39;객체 할당 확인&#39;, () =&gt; {
const obj = {};
expect(obj).toEqual({});
});</code></pre>
<br/>
### 1-2. describe 블록

</li>
</ul>
<ul>
<li><p><strong>구조</strong>: <code>describe</code> 블록을 사용하여 관련 테스트 케이스를 그룹화한다. 이는 테스트가 어떤 모듈이나 기능에 관련되어 있는지 명확하게 표현하는 데 도움이 된다.</p>
<pre><code class="language-javascript">// describe 블록 사용 예
describe(&#39;String 관련 테스트&#39;, () =&gt; {
  test(&#39;문자열 합치기&#39;, () =&gt; {
    expect(&#39;Hello&#39; + &#39;World&#39;).toBe(&#39;HelloWorld&#39;);
  });

  it(&#39;문자열 길이&#39;, () =&gt; {
    expect(&#39;Hello&#39;.length).toEqual(5);
  });
});</code></pre>
<br/>
### 1-3. before와 after 훅
</li>
<li><p><strong>사용 목적</strong>: 공통된 준비 작업이나 정리 작업을 위해 사용된다.</p>
<ul>
<li><code>beforeEach</code>: 각 테스트가 실행되기 전에 실행된다.</li>
<li><code>afterEach</code>: 각 테스트가 실행된 후에 실행된다.</li>
<li><code>beforeAll</code>: 모든 테스트가 실행되기 전에 단 한 번만 실행된다.</li>
<li><code>afterAll</code>: 모든 테스트가 실행된 후에 단 한 번만 실행된다.</li>
</ul>
<pre><code class="language-javascript">// before와 after 훅 사용 예
describe(&#39;Array 관련 테스트&#39;, () =&gt; {
  let array;

  beforeEach(() =&gt; {
    array = [1, 2, 3];
  });

  afterEach(() =&gt; {
    array = [];
  });

  test(&#39;배열 요소 추가&#39;, () =&gt; {
    array.push(4);
    expect(array).toEqual([1, 2, 3, 4]);
  });

  it(&#39;배열 요소 제거&#39;, () =&gt; {
    array.pop();
    expect(array).not.toContain(3);
  });
});</code></pre>
<br/>
### 1-4. 테스트 필터링
</li>
<li><p><strong>특정 테스트 실행</strong>: <code>test.only</code> 또는 <code>it.only</code>를 사용하여, 특정 테스트 또는 테스트 그룹만 실행할 수 있다.</p>
</li>
</ul>
<pre><code class="language-javascript">
</code></pre>
<br/>
<br/>

<h2 id="2-단언-assertions">2. 단언 (Assertions)</h2>
<h3 id="2-1-기본-단언">2-1. 기본 단언</h3>
<ul>
<li><p><strong>toEqual vs toBe</strong>: <code>toEqual</code>은 객체의 내용이 같은지 확인하고, <code>toBe</code>는 같은 객체인지 (즉, 동일한 메모리 주소를 가리키는지) 확인한다.</p>
<pre><code class="language-javascript">test(&#39;객체 내용 비교&#39;, () =&gt; {
  const obj = { a: 1, b: 2 };
  expect(obj).toEqual({ a: 1, b: 2 });
  expect(obj).not.toBe({ a: 1, b: 2 }); // toBe는 메모리 주소까지 비교하기 때문에 실패
});
</code></pre>
<br/>

</li>
</ul>
<h3 id="2-2-truthiness">2-2. Truthiness</h3>
<ul>
<li><p><strong>toBeNull, toBeUndefined, toBeTruthy, toBeFalsy</strong>:</p>
<pre><code class="language-javascript">test(&#39;null 검사&#39;, () =&gt; {
  const n = null;
  expect(n).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeUndefined();
  expect(n).not.toBeTruthy();
  expect(n).toBeFalsy();
});</code></pre>
<br/>
#### 2-3. 숫자 관련 단언
</li>
<li><p><strong>toBeGreaterThan, toBeLessThan</strong>: 숫자 비교를 위한 단언들이다.</p>
<pre><code class="language-javascript">test(&#39;숫자 비교&#39;, () =&gt; {
  const value = 2 + 2;
  expect(value).toBeGreaterThan(3);
  expect(value).toBeLessThan(5);
  expect(value).toBe(4);
  expect(value).toEqual(4);
});</code></pre>
</br>
</li>
<li><p><strong>toBeCloseTo</strong> : 부동 소수점 숫자들을 비교한다. 
JavaScript의 부동 소수점 계산 오차 때문에 유용하다. 
예를 들어, 0.2 + 0.1이 정확히 0.3이 아닐 때, 이 단언은 두 숫자가 충분히 가까운지를 판단한다. 두 번째 인자로 소수점 아래 자릿수를 지정해 비교 정밀도를 조절한다.</p>
<pre><code class="language-javascript">  test(&quot;adding floats&quot;, () =&gt; {
    const value = 0.1 + 0.2;
    expect(value).toBeCloseTo(0.3);
    expect(value).toBeCloseTo(0.29); // fail
    expect(value).toBeCloseTo(0.299); // pass
  });</code></pre>
</br>
</li>
<li><p><strong>toBeCloseTo</strong> : 두 번째 인자로 소수점 아래 자릿수를 지정할 수 있어, 비교의 정밀도를 조절할 수 있다. 
예를 들어, 두 숫자가 소수점 아래 두 자리까지만 비교하려면 다음과 같이 사용한다.</p>
<pre><code class="language-javascript">expect(0.223).toBeCloseTo(0.22, 2); // pass</code></pre>
</br>

</li>
</ul>
<h3 id="2-4-문자열-관련-단언">2-4. 문자열 관련 단언</h3>
<ul>
<li><p><strong>toMatch</strong>: 정규식을 사용해 문자열을 테스트할 수 있다.</p>
<pre><code class="language-javascript">test(&#39;문자열에 특정 문자가 있는지 확인&#39;, () =&gt; {
  expect(&#39;team&#39;).not.toMatch(/I/);
  expect(&#39;Christoph&#39;).toMatch(/stop/);
});</code></pre>
<br/>

</li>
</ul>
<h3 id="2-5-배열과-반복-가능-객체">2-5. 배열과 반복 가능 객체</h3>
<ul>
<li><p><strong>toContain</strong>: 배열이나 반복 가능 객체가 특정 항목을 포함하는지 확인한다.</p>
<pre><code class="language-javascript">test(&#39;배열에 특정 요소가 있는지 확인&#39;, () =&gt; {
  const shoppingList = [&#39;diapers&#39;, &#39;kleenex&#39;, &#39;trash bags&#39;, &#39;paper towels&#39;, &#39;milk&#39;];
  expect(shoppingList).toContain(&#39;milk&#39;);
  expect(new Set(shoppingList)).toContain(&#39;milk&#39;);
});</code></pre>
<br/>
</li>
<li><p><strong>toContainEqual</strong> : 배열이 특정 요소를 포함하고 있는지 확인한다. 요소가 객체인 경우에도 정확한 일치 여부를 검사한다.</p>
<pre><code class="language-javascript">// 배열 안에 { name: &quot;Bob&quot; } 객체가 포함되어 있는지를 확인
expect([{ name: &quot;Alice&quot; }, { name: &quot;Bob&quot; }]).toContainEqual({ name: &quot;Bob&quot; });</code></pre>
<br/>

</li>
</ul>
<h3 id="2-6-예외">2-6. 예외</h3>
<ul>
<li><p><strong>toThrow</strong>: 함수가 특정 예외를 던지는지 테스트한다.</p>
<pre><code class="language-javascript">function compileAndroidCode() {
  throw new Error(&#39;you are using the wrong JDK&#39;);
}

test(&#39;특정 오류를 던지는지 확인&#39;, () =&gt; {
  expect(() =&gt; compileAndroidCode()).toThrow();
  expect(() =&gt; compileAndroidCode()).toThrow(Error);

  // 오류 메시지나 정규식을 이용한 오류 검사
  expect(() =&gt; compileAndroidCode()).toThrow(&#39;you are using the wrong JDK&#39;);
  expect(() =&gt; compileAndroidCode()).toThrow(/JDK/);
});</code></pre>
<br/>

</li>
</ul>
<h2 id="3-모의-함수-mock-functions">3. 모의 함수 (Mock Functions)</h2>
<h3 id="3-1-모의-함수-생성">3-1. 모의 함수 생성</h3>
<ul>
<li><p><code>jest.fn()</code>: 모의 함수를 생성하고, 호출 정보를 추적한다.</p>
<pre><code class="language-javascript">test(&#39;모의 함수 예제&#39;, () =&gt; {
  const mockFn = jest.fn();
  mockFn(1);
  expect(mockFn).toHaveBeenCalled();
  expect(mockFn).toHaveBeenCalledWith(1);
});</code></pre>
</br>
### 3-2. 모의 함수 사용</li>
<li><p><strong>함수 모의</strong> : 외부 함수 호출의 영향을 줄이기 위해, 실제 함수 대신 모의 함수를 사용한다.</p>
<pre><code class="language-javascript">test(&#39;모의 구현 예제&#39;, () =&gt; {
  const mockFn = jest.fn().mockImplementation(scalar =&gt; 42 + scalar);
  // 또는 jest.fn(scalar =&gt; 42 + scalar);

  expect(mockFn(1)).toBe(43);
});
</code></pre>
</br></li>
<li><p><strong>모의 구현</strong> : mockImplementation()을 사용하여 모의 함수의 구체적인 동작을 정의할 수 있다.</p>
<pre><code class="language-javascript">test(&#39;모의 구현 예제&#39;, () =&gt; {
  const mockFn = jest.fn().mockImplementation(scalar =&gt; 42 + scalar);
  // 또는 jest.fn(scalar =&gt; 42 + scalar);

  expect(mockFn(1)).toBe(43);
});
</code></pre>
</br>
### 3-3. 모의 함수 검증</li>
<li><p><code>toHaveBeenCalled</code>, <code>toHaveBeenCalledTimes</code> : 함수가 호출되었는지, 특정 횟수만큼 호출되었는지 확인한다.</p>
<pre><code class="language-javascript">test(&#39;모의 함수 호출 여부 및 횟수 검증&#39;, () =&gt; {
  const mockFn = jest.fn();
  mockFn();
  mockFn([1, 2, 3]);

  // 함수가 호출되었는지
  expect(mockFn).toHaveBeenCalled();

  // 정확히 2번 호출되었는지
  expect(mockFn).toHaveBeenCalledTimes(2);

  // 두 번째 호출 때 첫 번째 인자가 [1, 2, 3]이었는지
  expect(mockFn.mock.calls[1][0]).toEqual([1, 2, 3]);
});</code></pre>
</br>
</br>
## 4. 비동기 테스트

</li>
</ul>
<h3 id="4-1-promise-테스트">4-1. Promise 테스트</h3>
<ul>
<li><p><strong>resolves, rejects</strong>: Promise가 해결되거나 거부될 때의 상태를 테스트한다.</p>
<pre><code class="language-javascript">test(&#39;Promise가 성공적으로 해결되는지 확인&#39;, () =&gt; {
  return expect(Promise.resolve(&#39;성공&#39;)).resolves.toBe(&#39;성공&#39;);
});

test(&#39;Promise가 실패하며 오류를 던지는지 확인&#39;, () =&gt; {
  return expect(Promise.reject(new Error(&#39;실패&#39;))).rejects.toThrow(&#39;실패&#39;);
});
</code></pre>
</br>
### 4-2. async/await 사용
</li>
<li><p><strong>비동기 함수</strong>: <code>async</code> 함수 내에서 <code>await expect()</code>을 사용하여 비동기 함수를 테스트한다.</p>
<pre><code class="language-javascript">test(&#39;async/await을 사용한 비동기 테스트&#39;, async () =&gt; {
  await expect(Promise.resolve(&#39;성공&#39;)).resolves.toBe(&#39;성공&#39;);
  await expect(Promise.reject(new Error(&#39;실패&#39;))).rejects.toThrow(&#39;실패&#39;);
});
</code></pre>
</br>

</li>
</ul>
<hr>
<h3 id="📓-정보-출처">📓 정보 출처</h3>
<ul>
<li><a href="https://jestjs.io/docs/getting-started">Jest docs</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>