<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>CodingING</title>
    <link>https://m2nseop.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 8 Apr 2026 14:27:47 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>codingtori</managingEditor>
    <image>
      <title>CodingING</title>
      <url>https://tistory1.daumcdn.net/tistory/6178286/attach/3659d556238d4f7db292fde79b1801b7</url>
      <link>https://m2nseop.tistory.com</link>
    </image>
    <item>
      <title>멀티 모듈</title>
      <link>https://m2nseop.tistory.com/127</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;멀티모듈이란, 하나의 프로젝트를 여러 개의 독립적인 모듈로 나누어 관리하는 프로젝트 구조&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통으로 사용하는 코드를 공통으로 관리, 각 모듈을 독립적으로 배포가능, 모듈 간 서로 의존성을 가지며 연결함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 : 독립적으로 운영될 수 있는 의미를 가지는 구성요소 단위&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;○ Web과 API에서 공통으로 사용되는 코드의 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;○&lt;span&gt; 단일 모듈의 한계&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;적용&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;1. 프로젝트 분리의 기준 정하기&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;2. 각 모듈의 의존성 관리 방법 결정&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;3. 모듈별 독립적인 빌드와 배포환경 구성&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;4. 모듈간의 인터페이스 명확하게 정의, 공통 기능 별도의 모듈로 분리&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;5. 멀티 모듈 구조의 적용은 지속적인 리팩토링과 개선 과정을 통해 이루어짐&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;* 도메인 관점에서 진행할 경우&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;- 팀 단위 작업 분리, 유지보수 용이, 비즈니스 로직 집중, 확장성&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;- 중복 코드의 단점 가질 수 있음&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;멀티 모듈 경계 나누기&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; - Boot (서버 모듈)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; - Infra (연동 모듈)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; - Cloud (클라우드-시스템 모듈)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; - Data (데이터 모듈 + 도메인)&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 모듈의 저주&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필요하지만 변경이 모든 모듈에 영향을 끼칠 수 있으므로 가장 엄격하게 관리되어야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리틱 아키텍처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 하나의 통합된 코드로 여러 비즈니스 기능을 수행 (전통적인 아키텍처)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 링크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://soso-hyeon.tistory.com/83&quot;&gt;[Spring Boot] 멀티 모듈 구조(Multi Module Architecture) 적용기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762754040769&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Boot] 멀티 모듈 구조(Multi Module Architecture) 적용기&quot; data-og-description=&quot;글을 시작하기 전에 최근 멀티 모듈 구조를 도입했다. 아니 정확히 말하면 도입하는 중이다.. 1차적으로 분리와 리팩토링은 완료했으나, 구조를 100%로 이해한 건 아니라서 조금씩 공부하면서 개&quot; data-og-host=&quot;soso-hyeon.tistory.com&quot; data-og-source-url=&quot;https://soso-hyeon.tistory.com/83&quot; data-og-url=&quot;https://soso-hyeon.tistory.com/83&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c8SZio/hyZNv6i3q4/J88PizwAqr0LnfSxbafLNK/img.jpg?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515,https://scrap.kakaocdn.net/dn/NnK49/hyZMtIuXAp/MhxH2pgb3e5cMWPyp2WS00/img.jpg?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515,https://scrap.kakaocdn.net/dn/AfN47/hyZNEhjK0t/V9TmnM8aIN1U5HnTdLs6y1/img.jpg?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515&quot;&gt;&lt;a href=&quot;https://soso-hyeon.tistory.com/83&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://soso-hyeon.tistory.com/83&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c8SZio/hyZNv6i3q4/J88PizwAqr0LnfSxbafLNK/img.jpg?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515,https://scrap.kakaocdn.net/dn/NnK49/hyZMtIuXAp/MhxH2pgb3e5cMWPyp2WS00/img.jpg?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515,https://scrap.kakaocdn.net/dn/AfN47/hyZNEhjK0t/V9TmnM8aIN1U5HnTdLs6y1/img.jpg?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Boot] 멀티 모듈 구조(Multi Module Architecture) 적용기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;글을 시작하기 전에 최근 멀티 모듈 구조를 도입했다. 아니 정확히 말하면 도입하는 중이다.. 1차적으로 분리와 리팩토링은 완료했으나, 구조를 100%로 이해한 건 아니라서 조금씩 공부하면서 개&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;soso-hyeon.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/127</guid>
      <comments>https://m2nseop.tistory.com/127#entry127comment</comments>
      <pubDate>Mon, 10 Nov 2025 14:52:35 +0900</pubDate>
    </item>
    <item>
      <title>DDD : 도메인 주도 설계</title>
      <link>https://m2nseop.tistory.com/126</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Domain Driven Design : DDD&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 개발의 복잡성을 해결하기 위한 설계 방법론&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 비즈니스 Domain 별로 나누어 설계하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 핵심 목표 : &quot;Loosly coupling&quot;, &quot;High cohesion&quot; - 모듈 간의 의존성 최소화 및 응집성 최대화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Strategic Design(개념 설계) / Tactical Design(구체적 설계)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도메인의 모델과 로직에 집중 (데이터 중심 &amp;rarr; 도메인 중심 접근)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Ubiquitous Language : 보편적 언어 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; : 여기서 말하는 언어는 개발자도 알고 도메인 전문가도 아는 언어 == 비즈니스 용어를 하나로 통합한 공통의 언어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; ※ 고려사항&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- Bounded Context (제한 영역) 범위 내에서 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- 용어 사전 (Glossary) 형태로 구성하는 것 권장 (: 처음부터 모든 용어를 사전처럼 정의하고 진행하는 것 불가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;- Software Entity와 Domain 간의 개념 일치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 전략적 설계 (Strategic Design)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; : 복잡한 도메인의 경계를 상황(Context)에 맞게 명확히 정의하는 과정 (개념적 설계)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 도메인 이해 관계자끼리 지식을 공유하고 이해 후 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 개념과 경계를 식별하여 바운디드 컨텍스트로 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 경계의 관계를 컨텍스트 맵으로 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 전술적 설계 (Tactical Design)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; : 전략적 설계를 통해 식별된 내부를 상세하게 설계하는 과정 (구체적 설계)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 전략적 설계에서 도출된 바운디드 컨텍스트와 도메인을 이용해 *애그리거트 패턴, 엔티티와 값 객체, 레포지토리 등 구성 및 구현&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;설계&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;전략적 설계&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;전술적 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;범위&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;전반적&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;특정 Bounded-Context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;목적&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;문제 도메인을 해결영역으로&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;풍부한 도메인 모델 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;메타포&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;전쟁에서 전략&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;전투에서 전술&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;주요 패턴&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Bounded-Context, Ubiquitous-Language&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Aggregate, Domain-Event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;수행 방식&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;접근법&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;상대적으로 방법론에 가까움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Bounded Context (바운디드 컨텍스트) : 사용자 프로세스, 정책/규정 등을 고유한 비즈니스 목적별로 그룹화한 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = 독립적으로 서비스될 때 문제가 없는 업무의 범위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Context map (컨텍스트 맵) : 바운디드 컨텍스트 간의 관계를 도식화 한 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Aggregate (애그리거트) : 도메인 영역을 구성하는 요소 중 하나로, 관련된 도메인 객체들의 집합 - 즉 관련된 객체들을 모아 하나의 단위로 취급하는 개념 (아래에 더 자세하게 설명)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Domain(도메인)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지식 활동의 영역&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자가 사용하는 것, 소프트웨어로 해결하고자 하는 문제 영역&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- (프로그래머의 관점) 애플리케이션 내의 로직들이 관여하는 정보와 활동의 영역&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- (DDD)관점 : 비즈니스 Domain&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Domain Model (도메인 모델)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 도메인을 개념적으로 표현한 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 소프트웨어 기능과 데이터 구조를 비즈니스의 실제 운영과 일치 시키기 위해 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 도메인 모델을 표현하는 다양한 방식(객체, 다이어그램, 그래프, 공식 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Domain Model Pattern (도메인 모델 패턴)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 도메인 모델을 생성하는 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구성요소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Entity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 고유의 식별자를 갖는 도메인 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 자신의 상태와 라이프 사이클 가짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Value Object&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 고유한 식별자를 가지지 않는 불변 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 식별성이 아닌 속성을 이용해 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 불변성을 위해 setter 사용 지양&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Service&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 도메인 객체에 위치시키기 어려운 연산을 캡슐화하는 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 일반적으로 상태를 갖지 않는 stateless&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 데이터를 직접 저장하거나 관리하지는 않고, 필요한 연산 수행 후 결과를 반환&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Module&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 유사 작업 및 개념을 그룹화하여 시스템 복잡도 감소시키는 설계 기법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;rarr; JAVA의 경우 package를 사용하여 모듈 구현 : 응집도를 높이고 결합도를 낮추기 위함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Aggregate&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 연관된 Entity와 Value Object(값 객체)를 하나로 묶은 군집&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 각각의 일관성을 보장하여 트랜잭션과 분산처리의 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 개별 객체가 아닌 관련 객체를 하나로 묶어 객체 군집 단위로 모델을 볼 수 있게 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; +) 애그리거트 루트 Aggregate root&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; : 포함된 객체들의 대표, 하위 개념을 표현한 모델을 하나로 묶어야 할 핵심 도메인 기능을 보유하고 있는 모델&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 애그리거트 내에서 유일하게 외부와 상호작용할 수 있는 진입점(Entry Point)역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Factory&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - Entity 객체의 생성에 관련된 복잡성을 캡슐화하는 설계 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 객체 생성의 전체 과정을 책임짐 &amp;rarr; 객체 생성의 세부 사항을 걱정하지 않고, 일관된 방식으로 복잡한 객체 생성 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Repository&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 도메인 영역과 데이터 infrastructure 계층 사이의 분리를 가능하게 하는 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 데이터 계층의 결합도를 낮추고 도메인 모델의 독립성 향상 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; 시스템의 유연성, 확장성 증가&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;도메인 주도 설계 아키텍처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층형 아키텍처 Layered Architecture&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- User Interface (표현계층) : 사용자의 요청을 하위 레이어로 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Application (응용계층) : 복잡한 비즈니스 로직 처리 (트랜잭션 처리 담당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Domain (도메인 계층): 도메인에 대한 정보, 객체 상태, 도메인의 비즈니스 로직 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Infrastructure : 영속성을 구현하거나 외부와 통신하는 기능 제공 - 리포지토리 구현체, 파일 시스템 접근 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 도메인 계층에서 정의한 리포지토리 인터페이스를 구현하여 데이터베이스 접근을 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 계층을 외부로 부터 보호하기 위해서, 도메인 계층이 인프라 계층에 의존하는 것을 막고자 &lt;b&gt;의존성 역전 원칙&lt;/b&gt; 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;b&gt;의존성 역전 원칙 (Dependency Inversion Principle)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;rarr;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 고수준 모듈은 더이상 저수준 모듈에 의존하지 않고, 저수준 모듈은 구현 추상화한 인터페이스에 의존&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;추상화한 인터페이스는 저수준 모듈이 아닌 고수준 모듈에 위치&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 상위 계층이 하위 계층에 의존&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Bounded Context&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 구분되는 경계를 갖는 컨텍스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 모델의 경계를 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 한 개의 바운디드 컨텍스트는 논리적으로 한 개의 모델을 가짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 도메인 기능을 사용자에게 제공하는데 필요한 표현 영역, 응용 서비스, 도메인, 인프라스트럭처 영역, 테이블도 모두 포함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 Context는 독립적으로 구현될 수 있으며, 통합이 필요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 간접 통합 : 각 바운디드 컨텍스트가 독립성을 유지하면서 통합&amp;nbsp; (ex. 메세지 큐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 직접 통합 : 한 바운디드 컨텍스트가 다른 바운디드 컨텍스트의 내부 구현에 직접 접근하는 방식 (bad)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상류, 하류 관계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; : 한 쪽에서 데이터 제공(상류) , 다른 한 쪽에서는 데이터 호출 (하류)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 하류에서 상류의 데이터를 받아서 사용할 때, *ACL을 만들어서 보호한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 공유 커널&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; : 두 바운디드 컨텍스트가 같은 모델을 공유하는 경우, 해당 서비스 모델을 공유함으로써 공통된 개발 막음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 독립 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; : 서로 통합하지 않고 모델을 발전시키는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; * &lt;b&gt;anti-corruption layer (ACL - 부패 방지 계층)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 레거시 시스템을 새로운 시스템으로 여러 단계에 걸처 이전할 때 사용할 수 있는 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 서로 다른 두 시스템이 망가지는 것을 방지하기 위해 사용하고 두 시스템 간의 원할한 서비스가 되도록 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;[사용되는 상황]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;○ 타 시스템 연계 (3rd party system)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;○ 서로 다른 통신 프로토콜 사용 시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;○ 서로 다른 도메인 모델 설계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;○ 서로 다른 데이터 타입 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;○ &lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;서로 다른 플랫폼 인프라 계층의 연계&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 스토밍 워크샵 - 업무 흐름을 이벤트 중심으로 매핑 (Bounded Context 식별)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트 매핑&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 각 Context가 어떻게 협력하고, 데이터나 이벤트를 교환하며, 통합되는지를 시각적으로 표현함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨텍스트 맵&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: Bounded Context 간 관계 표시 (OHS: 오픈 호스트 서비스 / ACL: 안티 코럽션 계층)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 컨텍스트 간 관계가 바뀔 경우 컨텍스트 맵도 변경됨&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junhyunny.github.io/architecture/pattern/anti-corruption-layer-pattern/&quot;&gt;Anti-Corruption Layer Pattern - Junhyunny&amp;rsquo;s Devlogs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762752997703&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Anti-Corruption Layer Pattern&quot; data-og-description=&quot;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&quot; data-og-host=&quot;junhyunny.github.io&quot; data-og-source-url=&quot;https://junhyunny.github.io/architecture/pattern/anti-corruption-layer-pattern/&quot; data-og-url=&quot;https://junhyunny.github.io/architecture/pattern/anti-corruption-layer-pattern/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cxFz6Y/hyZNEuQNJe/NXCAfMPrW5pKxu7VICuXg0/img.png?width=1197&amp;amp;height=512&amp;amp;face=0_0_1197_512,https://scrap.kakaocdn.net/dn/bZ1Gdo/hyZM5VEVmD/3uVJzYADIty8GlFCZBD7Jk/img.png?width=942&amp;amp;height=316&amp;amp;face=0_0_942_316&quot;&gt;&lt;a href=&quot;https://junhyunny.github.io/architecture/pattern/anti-corruption-layer-pattern/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://junhyunny.github.io/architecture/pattern/anti-corruption-layer-pattern/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cxFz6Y/hyZNEuQNJe/NXCAfMPrW5pKxu7VICuXg0/img.png?width=1197&amp;amp;height=512&amp;amp;face=0_0_1197_512,https://scrap.kakaocdn.net/dn/bZ1Gdo/hyZM5VEVmD/3uVJzYADIty8GlFCZBD7Jk/img.png?width=942&amp;amp;height=316&amp;amp;face=0_0_942_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Anti-Corruption Layer Pattern&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;junhyunny.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/126</guid>
      <comments>https://m2nseop.tistory.com/126#entry126comment</comments>
      <pubDate>Wed, 5 Nov 2025 16:20:51 +0900</pubDate>
    </item>
    <item>
      <title>테스트 하기 쉬운 코드 &amp;amp;&amp;amp; 테스트 범위와 종류</title>
      <link>https://m2nseop.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트가 어려운 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 하드 코딩된 경로 : 해당 경로에 파일이 반드시 위치해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 의존 객체를 직접 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 정적 메서드 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 실행 시점에 따라 달라지는 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○ LocalDate.now() 혹은 Random 사용하는 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 역할이 섞여 있는 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 메서드 중간에 소켓 통신 코드 포함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; +) 소켓 통신이나 HTTP 통신의 경우 실제를 대체할 서버를 로컬에 띄워서 처리 가능 (서버 수준의 대역)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 콘솔에서 입력을 받거나 결과를 콘솔에 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 테스트 대상이 사용하는 의존 대상 클래스나 메서드가 final : 대역으로 대체하기 어려움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 테스트 대상의 소스를 소유하고 있지 않아 수정이 어려움&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 가능한 설계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 어려운 주된 이유 = 의존하는 코드를 교체할 수 있는 수단이 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;●&amp;nbsp; 하드 코딩된 상수를 생성자나 파라미터로 받기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - [어려운 점] 테스트 환경에 따라 경로를 다르게 줄 수 있는 수단 X&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - [해결 방법] 생성자나 세터를 이용해서 경로를 전달 받음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; ex) 변수로 따로 분리하기, 메서드의 파라미터로 값을 전달받도록 하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ● 의존 대상을 주입 받기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - [해결 방법] 생성자나 세터를 통해 의존 대상을 교체 (: 실제 구현 대신에 대역을 사용할 수 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; * 만약 많은 레거시 코드에서 생성자 없는 버전 사용시 &amp;rarr; 세터를 이용해서 의존대상 교체하도록 수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; : &lt;span style=&quot;color: #666666;&quot;&gt;이렇게 함으로써 의존대상을 교체할 수 있도록 수정한 것임&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트하고 싶은 코드를 분리&lt;/span&gt;하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기능의 일부만 테스트하고 싶다면 해당 코드를 별도의 기능으로 분리하여 테스트 진행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(+) 일부 기능 자체를 대역으로 변경하고 싶다면 '의존 대상을 주입받기' 에서 설명한 것처럼 세터를 이용해서 의존 대상을 주입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;● 시간이나 임의 값 생성 기능 분리하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - [어려운 점] 테스트 대상이 시간이나 임의 값을 사용하면 테스트 시점에 따라 테스트 결과가 달라짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - [해결 방법] 테스트 대상이 사용하는 시간이나 임의 값을 제공하는 기능을 별도 분리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; * 임의 값을 제공하는 라이브러리를 직접 사용하지 말고, 별도로 분리한 타입을 사용해서 대역으로 처리해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;● 외부 라이브러리 대체 불가능한 경우 (정적 메서드 제공 시)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - [어려운 점] 정적 메소드들은 상태를 가지지 않거나 전역 상태를 사용하고, mocking이 어려움.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - [해결 방법] 외부 라이브러리를 직접 사용하지 말고 외부 라이브러리와 연동하기 위한 타입을 따로 만듬. 테스트 대상은 분리한&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;타입을 사용하도록 함 (: 외부 연동이 필요한 기능을 쉽게 대역으로 대체 가능)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 종류&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;테스트 종류.jpg&quot; data-origin-width=&quot;2594&quot; data-origin-height=&quot;1428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btNfuR/btsQ6hYnsDb/aCV4XKFRKKeZDkArRW9x40/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btNfuR/btsQ6hYnsDb/aCV4XKFRKKeZDkArRW9x40/img.jpg&quot; data-alt=&quot;테스트 범위에 따른 테스트 종류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btNfuR/btsQ6hYnsDb/aCV4XKFRKKeZDkArRW9x40/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtNfuR%2FbtsQ6hYnsDb%2FaCV4XKFRKKeZDkArRW9x40%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2594&quot; height=&quot;1428&quot; data-filename=&quot;테스트 종류.jpg&quot; data-origin-width=&quot;2594&quot; data-origin-height=&quot;1428&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 범위에 따른 테스트 종류&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;●&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &lt;b&gt;통합 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 개발 완료 후에 진행하는 최종 테스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 고객의 입장에서 요구한 기능을 올바르게 구현했는지 수행하는 테스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 소프트웨어의 코드를 직접 테스트함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 대상 (web app의 경우) : 프레임워크, 라이브러리, 데이터베이스, 구현한 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; &lt;b&gt;인수 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 고객의 입장에서 요구한 기능을 올바르게 구현했는지 수행하는 테스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 요건을 완료했는지 정의하기 위해 작성한 테스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; &lt;b&gt;기능 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; &lt;span style=&quot;color: #000000;&quot;&gt;사용자 입장&lt;/span&gt;에서 시스템이 제공하는 기능이 올바르게 동작하는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 시스템을 구동하고 사용하는데 필요한 모든 구성요소가 필요함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; E2E 테스트에 속함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&lt;b&gt;&amp;nbsp; E2E 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 모든 구성요소를 하나로 엮어서 진행 (사용자가 사용하는 웹 브라우저 부터 시작해서 외부서비스, DB에 이르기까지)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;●&lt;b&gt;&amp;nbsp; 단위 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 개별 코드나 컴포넌트가 기대한대로 동작하는 지 확인&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 한 클래스나 한 메서드와 같은 작은 범위 테스트&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; 의존 대상의 경우 스텁이나 모의 객체를 이용해서 대역으로 대체&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테스트를 자동화한다 == 코드로 작성한 테스트를 실행한다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;* 정기적인 기능테스트 = 정상적인 경우와 몇 가지 특수한 상황만 테스트 범위로 잡음&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WireMock&lt;/b&gt; = 서버 API를 스텁으로 대체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WireMock 사용방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 테스트 실행 전에 WireMockServer 시작. 실제 HTTP 서버 뜸&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 테스트에서 WireMockServer의 동작 기술&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; HTTP 연동을 수행하는 테스트 작성&lt;br /&gt;●&amp;nbsp; 테스트 실행 후에 WireMockServer를 중지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TestRestTemplate은 스프링 부트가 테스트 목적으로 제공. 내장 서버에 연결하는 RestTemplate.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/125</guid>
      <comments>https://m2nseop.tistory.com/125#entry125comment</comments>
      <pubDate>Tue, 14 Oct 2025 11:30:27 +0900</pubDate>
    </item>
    <item>
      <title>테스트 코드</title>
      <link>https://m2nseop.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;테스트 코드의 구성요소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 상황 == given&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 테스트 할 대상에 따라 상황을 설정하는 방법이 달라짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 실행 == when&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 결과 확인 == then&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조로 작성해야하는 것은 맞지만 이 구조에 너무 매몰되서도 안됨. 테스트 내용의 이해를 위해서 존재하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우연에 의해 테스트 결과가 달라질 수 없도록 해야한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 파일이 없는 경우 테스트에서 현재 존재하지 않는 파일이름으로 리턴 &amp;rarr; 명시적으로 파일이 없는 상황을 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 실제 존재하는 경우에도 삭제하는 로직 추가 &amp;rarr; 항상 파일이 존재하지 않음 보장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 상태가 테스트 결과에 영향을 주지 않음 + 테스트 코드는 항상 정상적으로 동작하는 것이 중요함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;외부 상태에 따라 테스트의 성공 여부가 바뀌지 않기 위한 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 테스트 실행 전에 외부를 원하는 상태로 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 테스트 실행 후에 외부 상태를 원래대로 되돌려 놓기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 외부 상황은 테스트 코드에서 마음대로 제어할 수 없는 경우 존재&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 대상의 상황과 결과에 외부 요인이 관여할 경우 &lt;b&gt;대역&lt;/b&gt;을 사용하면 테스트 작성이 용이해짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 대역 : 테스트 대상이 의존하는 대상의 실제 구현을 대신하는 구현 == test double&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;rarr; 이를 통해서 외부 상황이나 결과 대체 가능&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 요인이 테스트에 주로 관여하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 테스트 대상에서 파일 시스템 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 테스트 대상에서 DB로 부터 데이터를 조회하거나 데이터 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 테스트 대상에서 외부의 HTTP 서버와 통신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레포지토리의 경우 메모리를 이용해서 대역을 생성하여 테스트 작성 &amp;rarr; 실제 DB를 사용하지 않고도 로직 검증가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;대역의 종류&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 168px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;대역 종류&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 42px;&quot;&gt;스텁 (Stub)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 42px;&quot;&gt;구현을 단순한 것으로 대체. 테스트에 맞게 단순히 원하는 동작을 수행.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 42px;&quot;&gt;가짜 (Fake)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 42px;&quot;&gt;제품에는 적합하지 않지만, 실제로 동작하는 구현 제공. DB대신에 메모리 이용해서 구현한 경우 이에 해당.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;스파이 (Spy)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;호출된 내역 기록. 기록한 내용은 테스트 결과 검증 시 사용.&amp;nbsp;&lt;br /&gt;스파이는 스텁에 해당함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 42px;&quot;&gt;모의 (Mock)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 42px;&quot;&gt;기대한 대로 상호작용하는 지 행위 검증. 기대한 대로 미동작시 익셉션 발생. 모의 객체는 스텁 &amp;amp; 스파이 모두에 해당&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 스파이 대역 사용 예&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일 발송 기능 테스트 할 경우, EmailNotifier이 발송 이메일을 사용하였는지 테스트하면 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모의 객체를 작성하는 방법 중 하나에 &lt;b&gt;Mockito&lt;/b&gt; 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대역 객체가 기대하는 대로 상호작용 했는지 확인하는 것&lt;/b&gt;이 모의 객체의 주요 기능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제어하기 어려운 외부상황&lt;/b&gt;의 경우 ≫ 의존을 도출하고 이를 대역으로 대신함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존 도출 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 제어하기 힘든 외부 상황을 별도 타입으로 분리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 테스트 코드는 별도로 분리한 타입의 대역을 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 생성한 대역을 테스트 대상의 생성자 등을 이용해서 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 대역을 이용해서 상황 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면, 당장 로직을 구현하지 않않아도 테스트 코드를 통과시킬 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대역 사용의 장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 대기 시간을 줄여줌 &amp;rarr; 개발 속도 향상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실제 구현이 없어도 다양한 상황에 대한 테스트 및 실행 결과 확인 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모의 객체 남용시,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 결과 검증 코드가 길어지고 복잡해짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적으로 메서드 호출 여부를 검증하는 수단이므로 테스트 대상과 모의 객체 간의 상호작용이 조금만 바뀌어도 테스트가 깨짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;☞ 따라서, DAO나 리포지토리와 같이 저장소에 대한 대역은 모의 객체를 사용하는 것보다 메모리를 이용한 가짜 구현을 사용하는 것이 테스트 코드 관리에 유리함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/122</guid>
      <comments>https://m2nseop.tistory.com/122#entry122comment</comments>
      <pubDate>Wed, 1 Oct 2025 16:25:52 +0900</pubDate>
    </item>
    <item>
      <title>기능명세로서의 테스트코드 및 JUnit 기본</title>
      <link>https://m2nseop.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;설계 과정을 지원하는 TDD&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 만들려면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 테스트할 기능 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 클래스, 메서드, 함수이름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 파라미터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 결과를 검증&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 리턴 값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 과정에서 이름을 고민하고 파라미터 타입과 리턴 타입을 고민한다. 이것이 곧 설계 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름은 설계에서 매우 중요하며, 이름이 기대하는 것과 다르게 동작할 경우 코드 분석 시간을 중가시키므로 잘 정하는 것이 중요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 작성 시,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터와 결과 값을 정해야함 &amp;rarr; 요구사항 문서에서 기능의 입력과 결과 도출 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 시 애매한 점의 경우 구체적으로 정리 필요&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 구체적인 예를 통해서 모호함이 줄어들고, 구체적인 명세가 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의 사항 - 필요할 것으로 예측해서 미리 코드를 만들면 안됨 (설계도 마찬가지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;요구사항 분석 과정에서 설계를 진행&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;JUnit 5&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;JUnit5의 요소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; &lt;b&gt;JUnit 플랫폼&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; : 테스팅 프레임워크를 구동하기 위한 런처와 테스트 엔진을 위한 API 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; junit-platform-launcher ▷ junit-platform-engine&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp;&lt;b&gt; JUnit 주피터(Jupiter)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; : JUnit5를 위한 테스트 API와 실행엔진 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; junit-jupiter-engine&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;▷ junit-jupiter-api&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&lt;b&gt;&amp;nbsp; JUnit 빈티지(Vintage)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; : JUnit 3과 4로 작성된 테스트를 JUnit5 플랫폼에서 실행하기 위한 모듈을 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; ○&amp;nbsp; junit-vintage-engine &lt;/span&gt;▷ junit:junit&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit5 는 테스트를 위한 API로 주피터 API 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하기 위해서 &lt;span style=&quot;color: #006dd7;&quot;&gt;주피터 관련 모듈&lt;/span&gt; 의존에 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. junit-jupiter-api&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. junit-jupiter-params&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. junit-jupiter-engine&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit5를 이용해서 테스트 실행할 경우 JUnit5가 제공하는 플랫폼 런처 사용 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;maven의 경우 &amp;rarr; maven-surefire-plugin 2.22.0 버전부터 JUnit5 플랫폼 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1759283808017&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependencies&amp;gt;
	&amp;lt;dependency&amp;gt;
    	&amp;lt;groupId&amp;gt;org.junit.jupiter&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;junit-jupiter&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;5.5.0&amp;lt;/version&amp;gt;
        &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;

&amp;lt;build&amp;gt;
	&amp;lt;plugins&amp;gt;
    	&amp;lt;plugin&amp;gt;
        	&amp;lt;artifactId&amp;gt;maven-surefire-plugin&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;2.22.1&amp;lt;/version&amp;gt;
        &amp;lt;/plugin&amp;gt;
        ...
    &amp;lt;/plugins&amp;gt;
&amp;lt;/build&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;JUnit의 Assertions 클래스&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 검증하기 위한 목적의 다양한 정적 메서드 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 단언 메서드 정리&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 97px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;메서드&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;assertEquals(expected, actual)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;실제 값(actual)이 기대하는 값(expected)와 같은 지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;assertNotEquals(unexpected, actual)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 21px;&quot;&gt;실제 값(actual)이 특정 값(unexpected)과 같지 않은 지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;assertSame(Object expected, Object actual)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;두 객체가 동일한 객체인지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;assertNotSame(Object unexpected, Object actual)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;두 객체가 동일하지 않은 객체인지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;assertTrue(boolean condition)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값이 true 인지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;assertFalse(boolean condition)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값이 false인지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;assertNull(Object actual)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값이 null인지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;assertNotNull(Object actual)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;값이 null이 아닌지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;fail()&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;테스트를 실패 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fail() 메서드의 경우, 테스트에 실패했음을 알리고 싶을 때 사용 - 특히 &lt;u&gt;익셉션 발생 유무를 검증&lt;/u&gt;할 때 많이 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;익셉션 발생 유무 검사 메서드&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;메서드&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;assertThrows(Class&amp;lt;T&amp;gt; expectedType, Executable executable)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;executable을 실행한 결과로 지정한 타입의 익셉션이 발생하는지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;assertDoesNotThrow(Executable executable)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;executable을 실행한 결과로 익셉션이 발생하지 않는지 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드&lt;/p&gt;
&lt;pre id=&quot;code_1759285209779&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
    () -&amp;gt; {
    	AuthService authService = new AuthService();
        authService.authenticate(null, null);
    });
    
// 추가 검증
assertTrue(thrown.getMessage().contains(&quot;id&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 참고 - Executable 인터페이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; : execute 함수를 가진 함수형 인터페이스&lt;/p&gt;
&lt;pre id=&quot;code_1759285427427&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package org.junit.jupiter.api.function;

public interface Executable{
	void execute() throws Throwable;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;assertAll()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 모든 검증을 실행하고 그중에 실패한 것이 있는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Executable 목록을 가변인자로 받아서 각 Executable을 수행하는 것&lt;/p&gt;
&lt;pre id=&quot;code_1759285657659&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;assertAll(
    () -&amp;gt; assertEquals(3, 5/2),
    () -&amp;gt; assertEquals(4, 2*2),
    () -&amp;gt; assertEquals(6, 11/2)
);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 라이프사이클&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@BeforeEach, @AfterEach&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 테스트 메서드를 포함한 객체 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. @BeforeEach 존재 시, 해당 어노테이션이 붙은 메서드 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. @Test 어노테이션이 붙은 메서드 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. @AfterEach 존재 시, 해당 어노테이션이 붙은 메서드 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;※ @Test, @BeforeEach, @AfterEach 어노테이션을 붙인 메서드는 private이면 안됨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@BeforeAll, @AfterAll&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 클래스의 모든 테스트 메서드가 실행되기 전/후에 특정 작업을 수행해야 하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit은 테스트 메서드의 실행 순서를 지정할 순 있지만, 각 테스트 메서드는 서로 독립적으로 동작해야하는 것임. 의존성이 생길경우 유지보수에 어려움이 생김&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;++)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@DisplayName : 메서드 이름 붙이기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Disabled : 특정 테스트를 실행하지 않고 싶을 때&amp;nbsp; -- 테스트 코드가 완성되지 않았거나 잠시동안 테스트를 실행하지 말아야 할 때&lt;/p&gt;
&lt;pre id=&quot;code_1759292568127&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.junit.jupiter.api.Disabled;

public class AssertionsTest {
    @Disabled
    @Test
    void failMethod() {
    	try{
        	AuthService authService = new AuthService();
            authService.authenticate(null, null);
           	fail();
        } catch (IllegalArgumentException e) {
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 테스트를 실행하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; maven :&amp;nbsp; mvn test&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; gradle :&amp;nbsp; gradle test&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/121</guid>
      <comments>https://m2nseop.tistory.com/121#entry121comment</comments>
      <pubDate>Thu, 25 Sep 2025 11:07:31 +0900</pubDate>
    </item>
    <item>
      <title>TDD로 기능 추가해보기</title>
      <link>https://m2nseop.tistory.com/120</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;원래 어느정도 기능이 구현되어 있던 간단한 게시판 프로젝트에 TDD를 이용해서 새로운 기능을 추가해보았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 기존의 회사 컴은 java 1.8 버전을 사용중이라 이 프로젝트의 실행을 위해서는 java-sdk를 version17로 변경해준다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;File &amp;rarr; Settings &amp;rarr; Build Tools &amp;rarr; Gradle &amp;rarr; Gradle JVM&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 추가해볼 기능은 바로 &lt;span style=&quot;color: #006dd7;&quot;&gt;댓글 좋아요&lt;/span&gt; 기능이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, commentLike 도메인 정도는 먼저 작성하려고 했었는데 찾아보니, TDD의 경우 도메인 코드보다 테스트 코드가 먼저!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, CommentLike 도메인 클래스를 먼저 만드는 것이 아니라, &quot;&lt;span style=&quot;color: #006dd7;&quot;&gt;이 클래스가 어떤 동작을 해야 하는지&quot;를 테스트 코드로 먼저 정의&lt;/span&gt;하는 것이 출발점이라고 한다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;테스트 작성 (Red 단계)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 CommentLike 클래스가 없더라도, &quot;이런 기능이 있어야 한다&quot;라는 테스트를 작성한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 고민이 되었던 부분이, 나는 다른 기능들을 개발할 경우에 모두 도메인 객체 생성 제약(@NoArgsConstructor(access = PROTECTED)를 설정해두었다. 따라서, JPA엔티티나 도메인 모델을 외부에서 함부로 new하지 못하게 해서 불변성과 일관성을 보장하려고 했는데, 테스트 코드에서도 객체 생성을 해야하다보니 어떻게 할 지가 고민이 되었다. 이때 선택지는 2가지라고 한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정적 팩토리 메서드 사용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 가장 많이 사용하는 방법이라고 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티는 protected 생성자 그대로 두고, 대신 정적 팩토리 메서드를 만들어서 테스트/도메인 코드 모두 이걸 쓰게 하는 방식이다. 나의 경우 이전에 처음 시작할 때 바로 롬북을 쓰지 않고 정적 팩토리 메소드로 개발을 시작했어서 이미 다 만들어져 있었다&lt;/p&gt;
&lt;pre id=&quot;code_1758722591427&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//정적 팩토리 메서드
public static Member create(String studentNumber, String nickname, String school, String email, String password, MemberStatus active) {
	return new Member(studentNumber, nickname, school, email, password, MemberStatus.ACTIVE);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Test Fixture - 테스트 전용 헬퍼 클래스&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 패키지 안에 MemberFixture 같은 유틸을 두고 생성을 도와주는 정적 메서드로만 객체 생성&lt;/p&gt;
&lt;pre id=&quot;code_1758722686956&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MemberFixture {
	public static Member createMember(String name) {
    	return Member.create(name);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점 : 도메인 변경 시에도 테스트 코드만 수정하면 됨&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 첫 번째 테스트 코드를 작성해준다. 사용자가 댓글에 좋아요를 눌렀을 때, commentLike가 생성되는 로직을 검증하는 간단한 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1758722810736&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package efub.assignment.comment.domain;

import efub.assignment.community.board.domain.Board;
import efub.assignment.community.comment.domain.Comment;
import efub.assignment.community.comment.domain.CommentLike;
import efub.assignment.community.member.domain.Member;
import efub.assignment.community.member.domain.MemberStatus;
import efub.assignment.community.post.domain.Post;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class CommentLikeTest {

    @Test
    public void 사용자는_댓글에_좋아요를_누를수가_있다() {
        //given
        Member member = Member.create(&quot;2222222&quot;, &quot;tester&quot;, &quot;ewha&quot;, &quot;test@test.com&quot;, &quot;000000&quot;, MemberStatus.ACTIVE);
        Board board = Board.create(&quot;컴공게시판&quot;, &quot;공지사항&quot;, &quot;desc&quot;, member);
        Post post = Post.create(board, member, true, &quot;컴공댕들 안녕&quot;);
        Comment comment  = Comment.create(post, true, &quot;테스트댓글&quot;, member);
        //when
        CommentLike commentLike = CommentLike.create(member, comment);
        //then
        assertEquals(member, commentLike.getMember());
        assertEquals(comment, commentLike.getComment());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의도된 RED&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아직 commentLike 클래스가 없기 때문에 에러가 발생한다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;rarr; 의도된 RED&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;따라서, 이 에러를 해결해주기 위하여 commentLike 도메인 클래스를 생성해준다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;CommentLike 도메인 클래스 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758723001075&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package efub.assignment.community.comment.domain;

import efub.assignment.community.global.domain.BaseEntity;
import efub.assignment.community.member.domain.Member;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Entity
public class CommentLike extends BaseEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long commentLikeId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;member&quot;, nullable = false)
    private Member member;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;comment&quot;, nullable = false)
    private Comment comment;

    private CommentLike(Member member, Comment comment) {
        this.member = member;
        this.comment = comment;
    }

    public static CommentLike create(Member member, Comment comment) {
        return new CommentLike(null, member, comment);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;board &amp;rarr; post &amp;rarr; comment &amp;rarr; commentLike로 참조가 이어지기 때문에 각각 모두 임시로 생성해주어야 하위 항목을 생성할 수가 있다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이제 실행해보면, 이렇게 테스트가 통과되는 것을 확인해볼 수가 있다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.webp&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uzbPf/btsQPoaCL7N/Rzft1R7i9mIGdxTgJHKVIK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uzbPf/btsQPoaCL7N/Rzft1R7i9mIGdxTgJHKVIK/img.webp&quot; data-alt=&quot;첫 번째 테스트 코드 통과~&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uzbPf/btsQPoaCL7N/Rzft1R7i9mIGdxTgJHKVIK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuzbPf%2FbtsQPoaCL7N%2FRzft1R7i9mIGdxTgJHKVIK%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1136&quot; height=&quot;251&quot; data-filename=&quot;image.webp&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;첫 번째 테스트 코드 통과~&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 사용자가 같은 댓글에 &lt;span style=&quot;color: #006dd7;&quot;&gt;중복 좋아요를 남길 수 없도록 하는 로직&lt;/span&gt;을 구현해보도록 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 각 테스트에 앞서 필요한 객체들을 생성해주어야 헀는데, 이것의 코드 중복을 피하기 위해서 @BeforeEach를 이용해서&lt;/p&gt;
&lt;pre id=&quot;code_1758723233283&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@BeforeEach
public void generateObjects(){
    Member member = Member.create(...);
    Board board = Board.create(...);
    Post post = Post.create(board, member, ...);
    Comment comment  = Comment.create(post, ...);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성해주었더니,&amp;nbsp;&lt;span style=&quot;background-color: #000000; color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;&quot;cannot resolve symbol&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에러가 발생&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;●&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;에러 원인&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: member, board, post, comment가 generateObjects 메서드의 지역변수로만 존재하고, 실제 테스트 메서드에서 접근할 수가 없음&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;●&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 테스트 클래스의 필드로 선언하고, @BeforeEach에서 초기화 해야함&lt;/p&gt;
&lt;pre id=&quot;code_1758723509492&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CommentLikeTest {
	
    private Member member;
    private Board board;
    private Post post;
    private Comment comment;

		... 생략

	// 필요한 객체들 생성
    @BeforeEach
    public void setUp(){
        Member member = Member.create(&quot;2222222&quot;, &quot;tester&quot;, &quot;ewha&quot;, &quot;test@test.com&quot;, &quot;000000&quot;, MemberStatus.ACTIVE);
        Board board = Board.create(&quot;컴공게시판&quot;, &quot;공지사항&quot;, &quot;desc&quot;, member);
        Post post = Post.create(board, member, true, &quot;컴공댕들 안녕&quot;);
        Comment comment  = Comment.create(post, true, &quot;테스트댓글&quot;, member);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1265&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lCawl/btsQM3ZTKh9/Qu6FRqBqr5r3X2B1hEATz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lCawl/btsQM3ZTKh9/Qu6FRqBqr5r3X2B1hEATz0/img.png&quot; data-alt=&quot;수정이전 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lCawl/btsQM3ZTKh9/Qu6FRqBqr5r3X2B1hEATz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlCawl%2FbtsQM3ZTKh9%2FQu6FRqBqr5r3X2B1hEATz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1265&quot; height=&quot;358&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1265&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수정이전 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-2.png&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byK1g1/btsQL3lSoLt/isp06neKm6g7akxiNs3ae1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byK1g1/btsQL3lSoLt/isp06neKm6g7akxiNs3ae1/img.png&quot; data-alt=&quot;수정하고 나서 코드~ 깔끔해졌즁?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byK1g1/btsQL3lSoLt/isp06neKm6g7akxiNs3ae1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyK1g1%2FbtsQL3lSoLt%2Fisp06neKm6g7akxiNs3ae1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;893&quot; height=&quot;245&quot; data-filename=&quot;image-2.png&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수정하고 나서 코드~ 깔끔해졌즁?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 테스트 코드를 돌려보면~&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-3.png&quot; data-origin-width=&quot;1347&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq9jfa/btsQPKYO8Aw/2ZPgzK9MsE09uDpwrbTvOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq9jfa/btsQPKYO8Aw/2ZPgzK9MsE09uDpwrbTvOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq9jfa/btsQPKYO8Aw/2ZPgzK9MsE09uDpwrbTvOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq9jfa%2FbtsQPKYO8Aw%2F2ZPgzK9MsE09uDpwrbTvOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1347&quot; height=&quot;426&quot; data-filename=&quot;image-3.png&quot; data-origin-width=&quot;1347&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 에러가 발생함을 알 수가 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 아직 commentLikeService가 존재하지 않기 때문 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;rarr; 따라서&lt;span&gt; commentLikeService&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;를 작성해보자&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-4.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuHnbj/btsQMYEjYtv/TCcqb1Mnme3Bn6AQwaP3y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuHnbj/btsQMYEjYtv/TCcqb1Mnme3Bn6AQwaP3y0/img.png&quot; data-alt=&quot;commentLikeService 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuHnbj/btsQMYEjYtv/TCcqb1Mnme3Bn6AQwaP3y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuHnbj%2FbtsQMYEjYtv%2FTCcqb1Mnme3Bn6AQwaP3y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;914&quot; height=&quot;602&quot; data-filename=&quot;image-4.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;commentLikeService 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 코드를 작성해주고 다시 테스트를 실행해준다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 NPE가 발생해서 물어봤더니 추가적인 에러를 알 수가 있었다...&lt;/p&gt;
&lt;pre id=&quot;code_1758723838975&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 필요한 객체들 생성
    @BeforeEach
    public void setUp(){
        member = Member.create(&quot;2222222&quot;, &quot;tester&quot;, &quot;ewha&quot;, &quot;test@test.com&quot;, &quot;000000&quot;, MemberStatus.ACTIVE);
        board = Board.create(&quot;컴공게시판&quot;, &quot;공지사항&quot;, &quot;desc&quot;, member);
        post = Post.create(board, member, true, &quot;컴공댕들 안녕&quot;);
        comment  = Comment.create(post, true, &quot;테스트댓글&quot;, member);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해야된다. 원래대로 했으면, 다시 객체를 생성함으로써 앞에서 선언했던 걸 덮어씌우는 형식이 되므로 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 주입한 서비스가 여러 의존성을 가지고 있다면, &lt;span style=&quot;color: #006dd7;&quot;&gt;가지고 있는 모든 의존성을 @Mock으로 추가&lt;/span&gt;해줘야한다.!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-5.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgSTGv/btsQNbcldKz/yJHCKkQ7LHLgswmRmSCfV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgSTGv/btsQNbcldKz/yJHCKkQ7LHLgswmRmSCfV1/img.png&quot; data-alt=&quot;수정한 테스트 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgSTGv/btsQNbcldKz/yJHCKkQ7LHLgswmRmSCfV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgSTGv%2FbtsQNbcldKz%2FyJHCKkQ7LHLgswmRmSCfV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;896&quot; height=&quot;466&quot; data-filename=&quot;image-5.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;수정한 테스트 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 유효성 검사에서 에러가 났는데 이것이 DB를 사용하는 로직이라서 그런 것 같다. 따라서 Service만 테스트하려면 Mocking으로 commentValidation과 findAllByComment 동작을 가짜로 만들어주어야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, DB에서 member, comment 를 생성할 경우, PK는 자동으로 생성되도록 설정이 되어있는데 그 때문에 test 하는 도중에는 각 객체의 id값이 null이므로 에러가 발생한다. 이때, 이 값을 테스트를 위하여 직접 넣어줄 수 있는데&lt;/p&gt;
&lt;pre id=&quot;code_1758724096184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ReflectionTestUtils.setField(comment, &quot;commentId&quot;, 1L);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용하면 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;ReflectionTestUtils.setField&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- Spring에서 제공하는 유틸로, private 또는 sertter가 없는 필드에 강제로 값을 넣을 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 엔티티는 보통 id가 DB에서 자동 생성되는데, 단위 테스트에서는 DB연결이 없으니까 강제로 세팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이렇게 해서 테스트를 다시 실행해보면, 엥......?!?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-6.png&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRqphl/btsQMIaC7Mv/J8WgH6UXYMNx55qcMaLjc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRqphl/btsQMIaC7Mv/J8WgH6UXYMNx55qcMaLjc0/img.png&quot; data-alt=&quot;통과해버린 테스트...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRqphl/btsQMIaC7Mv/J8WgH6UXYMNx55qcMaLjc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRqphl%2FbtsQMIaC7Mv%2FJ8WgH6UXYMNx55qcMaLjc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1414&quot; height=&quot;364&quot; data-filename=&quot;image-6.png&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;통과해버린 테스트...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● &lt;b&gt;문제 원인&lt;/b&gt; : given(commentLikeRepository.findAllByComment(comment)).willReturn(List.of(...)) 처럼 정적(stub)반환을 설정하면, 서비스가 createCommentLike()에서 findAllByComment()를 호출해도 항상 같은(미리 만든) 리스트만 돌아옴. 따라서 mock의 상태는 변하지 않기 때문에(= 저장 효과가 반영되지 않음) 테스트 결과가 기대와 달라짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;●&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: &lt;b&gt;Mockito로 '상태변화' 흉내내기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;findAllByComment()와 save()가 동작하는 작은 in-memory 리스트를 만들어서, findAllByComment()는 그 리스트를 읽게 하고, save()는 리스트에 추가하도록 함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;수정된 CommentLikeTest&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758724403230&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package efub.assignment.comment.domain;

import efub.assignment.community.board.domain.Board;
import efub.assignment.community.comment.domain.Comment;
import efub.assignment.community.comment.domain.CommentLike;
import efub.assignment.community.comment.dto.request.CommentLikeRequestDTO;
import efub.assignment.community.comment.repository.CommentLikeRepository;
import efub.assignment.community.comment.service.CommentLikeService;
import efub.assignment.community.comment.service.CommentService;
import efub.assignment.community.member.domain.Member;
import efub.assignment.community.member.domain.MemberStatus;
import efub.assignment.community.member.service.MemberService;
import efub.assignment.community.post.domain.Post;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class CommentLikeTest {

    @InjectMocks
    CommentLikeService commentLikeService;

    @Mock
    CommentLikeRepository commentLikeRepository;

    @Mock
    MemberService memberService;

    @Mock
    CommentService commentService;

    private Member member;
    private Board board;
    private Post post;
    private Comment comment;

    private List&amp;lt;CommentLike&amp;gt; commentLikes; //테스트용 in-memory store


    @Test
    public void 사용자는_댓글에_좋아요를_누를수가_있다() {
        //given
//        Member member = Member.create(&quot;2222222&quot;, &quot;tester&quot;, &quot;ewha&quot;, &quot;test@test.com&quot;, &quot;000000&quot;, MemberStatus.ACTIVE);
//        Board board = Board.create(&quot;컴공게시판&quot;, &quot;공지사항&quot;, &quot;desc&quot;, member);
//        Post post = Post.create(board, member, true, &quot;컴공댕들 안녕&quot;);
//        Comment comment  = Comment.create(post, true, &quot;테스트댓글&quot;, member);

        //when
        CommentLike commentLike = CommentLike.create(member, comment);
        //then
        assertEquals(member, commentLike.getMember());
        assertEquals(comment, commentLike.getComment());
    }


    @Test
    public void 같은_사용자가_중복_좋아요_불가() {
        //Given

        //in-memory store 초기화
        commentLikes = new ArrayList&amp;lt;&amp;gt;();

        ReflectionTestUtils.setField(comment, &quot;commentId&quot;, 1L);
        CommentLikeRequestDTO requestDTO = CommentLikeRequestDTO.builder()
                .member(member)
                .comment(comment)
                .build();

        //commentValidation이 comment 반환하도록 설정
        given(commentService.commentValidation(comment.getCommentId())).willReturn(comment);

//        given(commentLikeRepository.findAllByComment(comment))
//                .willReturn(List.of(CommentLike.create(member, comment)));

        //findAllByComment는 현재 commentLikes의 복사본(현재 상태) 반환
        given(commentLikeRepository.findAllByComment(comment))
                .willAnswer(invocation -&amp;gt; new ArrayList&amp;lt;&amp;gt;(commentLikes));

        //save가 호출되면 commentLikes에 추가하고 저장된 객체 리턴
        when(commentLikeRepository.save(any(CommentLike.class)))
                .thenAnswer(invocation -&amp;gt; {
                    CommentLike saved = invocation.getArgument(0);
                    commentLikes.add(saved);
                    return saved;
                });

        //When
        commentLikeService.createCommentLike(requestDTO);
        commentLikeService.createCommentLike(requestDTO);     //중복 좋아요 시도

        //Then
        assertEquals(1, commentLikeService.getCommentLikesCnt(comment));

        // save는 실제로 한 번만 호출되었어야 함 (중복이면 save하지 않도록 서비스가 구현 될 필요 有)
        verify(commentLikeRepository, atMost(1)).save(any(CommentLike.class));
    }


    // 필요한 객체들 생성
    @BeforeEach
    public void setUp(){
        member = Member.create(&quot;2222222&quot;, &quot;tester&quot;, &quot;ewha&quot;, &quot;test@test.com&quot;, &quot;000000&quot;, MemberStatus.ACTIVE);
        board = Board.create(&quot;컴공게시판&quot;, &quot;공지사항&quot;, &quot;desc&quot;, member);
        post = Post.create(board, member, true, &quot;컴공댕들 안녕&quot;);
        comment  = Comment.create(post, true, &quot;테스트댓글&quot;, member);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위한 in-memory store용 리스트를 생성해주고, save가 호출되면 이 리스트에 저장을 하고 저장된 객체를 리턴해줌. 그 후, service단에서 중복 호출을 방지할 것이므로 save함수 자체가 1번만 실행되어야하므로 이를 verify를 통해서 검증한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;됐다!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-7.png&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3Jxmw/btsQMGKHcV4/MqSRRzsVTnQd3j9ULHaLEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3Jxmw/btsQMGKHcV4/MqSRRzsVTnQd3j9ULHaLEk/img.png&quot; data-alt=&quot;중복방지로직이 없으므로 2번으로 나오는 값&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3Jxmw/btsQMGKHcV4/MqSRRzsVTnQd3j9ULHaLEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3Jxmw%2FbtsQMGKHcV4%2FMqSRRzsVTnQd3j9ULHaLEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1482&quot; height=&quot;344&quot; data-filename=&quot;image-7.png&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중복방지로직이 없으므로 2번으로 나오는 값&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이 테스트를 통과시키기 위해서 중복 방지 로직을 작성한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때 set을 이용해서 구현하는 방식이랑, 일반적으로 조건문으로 분기하는 방법이 있는데, set을 사용하는 방식이 DB 조회를 덜 할 수 있는 것 같다. 하지만 일단은 DB로 갯수 조회하는 형식으로 구현을 했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;중복 로직을 구현한&lt;span&gt; CommentLikeService.java 코드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758724562459&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
    public String createCommentLike(CommentLikeRequestDTO requestDTO) {
//        //1. 유효성 검사
//        Comment comment = commentService.commentValidation(requestDTO.getComment().getCommentId());
//        Member member = memberService.findMemberByMemberId(requestDTO.getMember().getMemberId());
//        if (comment == null || member == null) {
//            throw new IllegalArgumentException(&quot;댓글 좋아요 중 에러 발생&quot;);
//        }

        Comment comment = requestDTO.getComment();
        Member member = requestDTO.getMember();

        //중복 방지 로직
        if (getCommentLikesPresent(comment, member)) {
            return &quot;이미 좋아요를 누른 댓글입니다&quot;;
        }

        //2. 좋아요 생성
        CommentLike commentLike = CommentLike.create(member, comment);
        commentLikeRepository.save(commentLike);

        return &quot;좋아요가 생성되었습니다&quot;;
    }

    public boolean getCommentLikesPresent(Comment comment, Member member) {
        //1. 유효성 검사
        Comment cmt = commentService.commentValidation(comment.getCommentId());
        Member mem = memberService.findMemberByMemberId(member.getMemberId());
        if(cmt == null) {
            throw new IllegalArgumentException(&quot;유효하지 않은 댓글입니다&quot;);
        }
        //2. 해당 댓글에 좋아요 눌른 이력이 있는 지 반환
        Optional&amp;lt;CommentLike&amp;gt; isLiked = commentLikeRepository.findByCommentAndMember(comment, member);
        if(isLiked.isEmpty())  return false;
        else return true;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;코드를 작성하다보니, 숫자를 반환하는 것이 아니라 하나라도 있으면 true, 없으면 false 를 반환해서 존재 유무를 확인하고 false일 경우에만 save 메소드를 호출하는 형식으로 변환하였다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;변환된 로직에 맞게 테스트 코드도 변경해줌&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758724610804&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package efub.assignment.comment.domain;

import efub.assignment.community.board.domain.Board;
import efub.assignment.community.comment.domain.Comment;
import efub.assignment.community.comment.domain.CommentLike;
import efub.assignment.community.comment.dto.request.CommentLikeRequestDTO;
import efub.assignment.community.comment.repository.CommentLikeRepository;
import efub.assignment.community.comment.service.CommentLikeService;
import efub.assignment.community.comment.service.CommentService;
import efub.assignment.community.member.domain.Member;
import efub.assignment.community.member.domain.MemberStatus;
import efub.assignment.community.member.service.MemberService;
import efub.assignment.community.post.domain.Post;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class CommentLikeTest {

    @InjectMocks
    CommentLikeService commentLikeService;

    @Mock
    CommentLikeRepository commentLikeRepository;

    @Mock
    MemberService memberService;

    @Mock
    CommentService commentService;

    private Member member;
    private Board board;
    private Post post;
    private Comment comment;

    private List&amp;lt;CommentLike&amp;gt; commentLikes; //테스트용 in-memory store


    @Test
    public void 사용자는_댓글에_좋아요를_누를수가_있다() {
        //given
//        Member member = Member.create(&quot;2222222&quot;, &quot;tester&quot;, &quot;ewha&quot;, &quot;test@test.com&quot;, &quot;000000&quot;, MemberStatus.ACTIVE);
//        Board board = Board.create(&quot;컴공게시판&quot;, &quot;공지사항&quot;, &quot;desc&quot;, member);
//        Post post = Post.create(board, member, true, &quot;컴공댕들 안녕&quot;);
//        Comment comment  = Comment.create(post, true, &quot;테스트댓글&quot;, member);

        //when
        CommentLike commentLike = CommentLike.create(member, comment);
        //then
        assertEquals(member, commentLike.getMember());
        assertEquals(comment, commentLike.getComment());
    }


    @Test
    public void 같은_사용자가_중복_좋아요_불가() {
        //Given

        //in-memory store 초기화
        commentLikes = new ArrayList&amp;lt;&amp;gt;();

        ReflectionTestUtils.setField(comment, &quot;commentId&quot;, 1L);
        CommentLikeRequestDTO requestDTO = CommentLikeRequestDTO.builder()
                .member(member)
                .comment(comment)
                .build();

        //commentValidation이 comment 반환하도록 설정
        given(commentService.commentValidation(comment.getCommentId())).willReturn(comment);
        given(memberService.findMemberByMemberId(member.getMemberId())).willReturn(member);

//        given(commentLikeRepository.findAllByComment(comment))
//                .willReturn(List.of(CommentLike.create(member, comment)));

        //findAllByComment는 현재 commentLikes의 복사본(현재 상태) 반환
//        given(commentLikeRepository.findAllByComment(comment))
//                .willAnswer(invocation -&amp;gt; new ArrayList&amp;lt;&amp;gt;(commentLikes));

        //findByCommentAndMember -&amp;gt; 상태에 따라서 Optional 반환
        given(commentLikeRepository.findByCommentAndMember(comment, member))
                .willAnswer(invocation -&amp;gt; commentLikes.stream()
                        .filter(cL -&amp;gt; cL.getComment().equals(comment) &amp;amp;&amp;amp; cL.getMember().equals(member))
                        .findFirst());

        //save가 호출되면 commentLikes에 추가하고 저장된 객체 리턴
        when(commentLikeRepository.save(any(CommentLike.class)))
                .thenAnswer(invocation -&amp;gt; {
                    CommentLike saved = invocation.getArgument(0);
                    commentLikes.add(saved);
                    return saved;
                });

        //When
        String msg1 = commentLikeService.createCommentLike(requestDTO);
        String msg2 = commentLikeService.createCommentLike(requestDTO);     //중복 좋아요 시도

        //Then
        assertEquals(&quot;좋아요가 생성되었습니다&quot;, msg1);
        assertEquals(&quot;이미 좋아요를 누른 댓글입니다&quot;, msg2);
        assertEquals(1, commentLikes.size());

//        // save는 실제로 한 번만 호출되었어야 함 (중복이면 save하지 않도록 서비스가 구현 될 필요 有)
//        verify(commentLikeRepository, atMost(1)).save(any(CommentLike.class));
    }


    // 필요한 객체들 생성
    @BeforeEach
    public void setUp(){
        member = Member.create(&quot;2222222&quot;, &quot;tester&quot;, &quot;ewha&quot;, &quot;test@test.com&quot;, &quot;000000&quot;, MemberStatus.ACTIVE);
        board = Board.create(&quot;컴공게시판&quot;, &quot;공지사항&quot;, &quot;desc&quot;, member);
        post = Post.create(board, member, true, &quot;컴공댕들 안녕&quot;);
        comment  = Comment.create(post, true, &quot;테스트댓글&quot;, member);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;통과한 것을 확인할 수가 있다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-8.png&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J2G4F/btsQLMYUO2t/UUc5WUGAE0S4ujKoNOvZ51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J2G4F/btsQLMYUO2t/UUc5WUGAE0S4ujKoNOvZ51/img.png&quot; data-alt=&quot;통과!~&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J2G4F/btsQLMYUO2t/UUc5WUGAE0S4ujKoNOvZ51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ2G4F%2FbtsQLMYUO2t%2FUUc5WUGAE0S4ujKoNOvZ51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1373&quot; height=&quot;366&quot; data-filename=&quot;image-8.png&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;통과!~&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;사용된 개념 정리&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;JUnit 5 관련&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7; text-align: start;&quot; data-token-index=&quot;0&quot;&gt;@ExtendWith(MockitoExtension.class)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot; data-token-index=&quot;0&quot;&gt;▷ JUnit5 테스트 확장 기능(Extension)을 추가하는 어노테이션&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷&lt;span&gt; MockitoExtension을 등록하면, 테스트 실행 시 @Mock, @InjectMocks 등을 자동 초기화&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  Mockito 관련&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;@Mock&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷ Mock 객체(가짜 객체)를 생성해주는 어노테이션&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷ 여기서는 CommentLikeRepository, CommentService를 Mock으로 만들어서 진짜 DB 접근을 막고, 원하는 반환값을 직접 설정함&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7; text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;@InjectMocks&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷ Mock이 아닌 실제 객체를 생성하면서, 필요한 의존성(@Mock으로 만든 객체들)을 주입해줌&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷ 여기서는 commentLikeService가 실제로 만들어지고, 그 안의 commentLikeRepository, commentService가 Mock으로 채워짐&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7; text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;given(...)willReturn(...)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷ Mockito BDD 스타일 문법&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷ 특정 메서드 호출이 있을 때 정해진 값을 반환하도록 설정&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758725059596&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;given(commentService.commentValidation(comment.getCommentId()))
    .willReturn(comment);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;given(...)willAnser(...)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;willReturn은 정해진 값만 주지만, willAnswer는 호출될 때마다 동적으로 로직을 실행시킴&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷&lt;span&gt; 여기서는 in-memory likeStore를 매번 반환하도록 했음&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758725152129&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;given(commentLikeRepository.findAllByComment(comment))
    .willAnswer(invocation -&amp;gt; new ArrayList&amp;lt;&amp;gt;(likeStore));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;when(...)thenAnswer(...)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷ given과 같은 기능인데, BDD 스타일이 아닌 전통적인 Mockito 문법&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷ 여기서는 save() 흉내내기 위해 사용&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758725212512&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;when(commentLikeRepository.save(any(CommentLike.class)))
    .thenAnswer(invocation -&amp;gt; {
        CommentLike saved = invocation.getArgument(0);
        likeStore.add(saved);
        return saved;
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; save가 호출되면 전달된 객체를 likeStore에 추가하고 그대로 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;verify(mock, times(n))&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷&lt;span&gt; Mock 객체의 메서드가 몇 번 호출됐는지 검증&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;▷&lt;span&gt; 예시 - save()가 최대 1번 호출되었는지 체크&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758725291881&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;verify(commentLikeRepository, atMost(1)).save(any(CommentLike.class));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코드 리뷰 반영&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문자열을 그대로 비교하는 것은 권장 X &amp;nbsp; &amp;rarr;&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;.contains(&quot;좋아요&quot;)&lt;/span&gt;와 같이 부분 검증으로 변환&lt;/p&gt;
&lt;pre id=&quot;code_1758940755189&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//수정 전        
        assertEquals(&quot;좋아요가 생성되었습니다&quot;, msg1);
        assertEquals(&quot;이미 좋아요를 누른 댓글입니다&quot;, msg2);

//코드리뷰 반영
        assertTrue(msg1.contains(&quot;생성되었습니다&quot;));
        assertTrue(msg2.contains(&quot;이미 좋아요&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/120</guid>
      <comments>https://m2nseop.tistory.com/120#entry120comment</comments>
      <pubDate>Wed, 24 Sep 2025 23:48:38 +0900</pubDate>
    </item>
    <item>
      <title>mongodb/redis connect with springBoot</title>
      <link>https://m2nseop.tistory.com/118</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 추가해주기 - build.gradle&lt;/p&gt;
&lt;pre id=&quot;code_1758581852698&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	// redis
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'


	// Mongo DB
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis application.yml - local 연결용&lt;/p&gt;
&lt;pre id=&quot;code_1758581816316&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  data:
    redis:
      host: localhost
      port: 6379&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;mongodb 연결 시에 host/port 가 아니라 uri 로 한 줄만 쓰는 것이 표준이라고 한다!&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758581780078&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data:
	mongodb:
    	uri: &amp;lt;mongodb-connection-string&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, mongodb 서비스를 시작하는 명령어는 mac의 경우&lt;/p&gt;
&lt;pre id=&quot;code_1758610320893&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew services start mongodb/brew/mongodb-community&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redisConfig도 작성해준다&lt;/p&gt;
&lt;pre id=&quot;code_1758581925949&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.practice.blog.global.redis;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching // 캐싱 기능 활성화
public class RedisConfig {

    @Value(&quot;${spring.data.redis.host}&quot;)
    private String host;

    @Value(&quot;${spring.data.redis.port}&quot;)
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory () {
        return new LettuceConnectionFactory(host, port);    //Redis와 통신하기 위한 클라이언트 라이브러리
    }

    //redis template을 사용하여 redis에 직접 데이터를 저장하고 조회
    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate&amp;lt;String, Object&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 다 작성했으면 이제 실습코드 실행을 위한 account관련 코드들도 추가해주면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodb의 경우 repository에서 mongodb에서 제공하는 repository를 사용하면 된다&lt;/p&gt;
&lt;pre id=&quot;code_1758582055097&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.data.mongodb.repository.MongoRepository;

// AccountDocument의 Repository
public interface AccountDocumentRepository extends MongoRepository&amp;lt;AccountDocument, String&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 key에 2개 이상의 필드를 가진 객체를 저장하기 위해 &lt;span style=&quot;color: #006dd7;&quot;&gt;Hash 구조&lt;/span&gt; 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; HSET : Hash의 특정 필드에 특정 값 할당. 이미 값이 존재할 경우 덮어씀&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; HGET : 특정 필드의 값 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; HGETALL: 특정 키에 대해 저장된 모든 필드-값 페어 조회 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; HMGET : 특정 필드들의 값들 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; HINCRBY : 특정 필드의 숫자 값을 증가 혹은 감소시킴&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 결과 - redis&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjRP8H/btsQH3MY2np/Ebd8fbpAxSWbNzFUyQLJZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjRP8H/btsQH3MY2np/Ebd8fbpAxSWbNzFUyQLJZ1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;982&quot; data-filename=&quot;스크린샷 2025-09-23 오전 12.35.48.png&quot; data-widthpercent=&quot;42.4&quot; style=&quot;width: 41.905572%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjRP8H/btsQH3MY2np/Ebd8fbpAxSWbNzFUyQLJZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjRP8H%2FbtsQH3MY2np%2FEbd8fbpAxSWbNzFUyQLJZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UzMSf/btsQI4LoVyh/GWHkz6khXwHDfOFk9XAsD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UzMSf/btsQI4LoVyh/GWHkz6khXwHDfOFk9XAsD1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;716&quot; data-filename=&quot;스크린샷 2025-09-23 오전 12.36.24.png&quot; data-widthpercent=&quot;57.6&quot; style=&quot;width: 56.931637%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UzMSf/btsQI4LoVyh/GWHkz6khXwHDfOFk9XAsD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUzMSf%2FbtsQI4LoVyh%2FGWHkz6khXwHDfOFk9XAsD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;redis create/read&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cL9zvd/btsQK6VJgLj/Iqpkpoa46kXbdKKbdvmqq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cL9zvd/btsQK6VJgLj/Iqpkpoa46kXbdKKbdvmqq1/img.png&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;932&quot; data-filename=&quot;스크린샷 2025-09-23 오전 12.41.14.png&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;45.39&quot; style=&quot;width: 44.85798%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cL9zvd/btsQK6VJgLj/Iqpkpoa46kXbdKKbdvmqq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcL9zvd%2FbtsQK6VJgLj%2FIqpkpoa46kXbdKKbdvmqq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1042&quot; height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sn4hr/btsQJ7gw9UF/kbK9XoN8FSxVKkBfY3MkN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sn4hr/btsQJ7gw9UF/kbK9XoN8FSxVKkBfY3MkN1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;776&quot; data-filename=&quot;스크린샷 2025-09-23 오전 12.43.15.png&quot; data-widthpercent=&quot;54.61&quot; style=&quot;width: 53.97923%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sn4hr/btsQJ7gw9UF/kbK9XoN8FSxVKkBfY3MkN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSn4hr%2FbtsQJ7gw9UF%2FkbK9XoN8FSxVKkBfY3MkN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1044&quot; height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;redis update/delete&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis-cli 캡쳐를 깜빡했다 ㅎㅎ;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습결과 - mongodb&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhSX2w/btsQJzq1pvG/qFKSDx1PHUtI8QVrkAAS9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhSX2w/btsQJzq1pvG/qFKSDx1PHUtI8QVrkAAS9K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;988&quot; data-filename=&quot;스크린샷 2025-09-23 오전 7.40.12.png&quot; data-widthpercent=&quot;18.96&quot; style=&quot;width: 18.739535%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhSX2w/btsQJzq1pvG/qFKSDx1PHUtI8QVrkAAS9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhSX2w%2FbtsQJzq1pvG%2FqFKSDx1PHUtI8QVrkAAS9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1046&quot; height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DAYvy/btsQIUWrSMs/eZOxjA1oRXEtpulSZLv9r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DAYvy/btsQIUWrSMs/eZOxjA1oRXEtpulSZLv9r1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;278&quot; data-filename=&quot;스크린샷 2025-09-23 오전 7.40.44.png&quot; data-widthpercent=&quot;81.04&quot; style=&quot;width: 80.097675%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DAYvy/btsQIUWrSMs/eZOxjA1oRXEtpulSZLv9r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDAYvy%2FbtsQIUWrSMs%2FeZOxjA1oRXEtpulSZLv9r1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1258&quot; height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;mongodb create&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오전 7.41.30.png&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JldEv/btsQIBv4zYt/lYIHGez5PySketj9cEimzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JldEv/btsQIBv4zYt/lYIHGez5PySketj9cEimzk/img.png&quot; data-alt=&quot;mongodb - read&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JldEv/btsQIBv4zYt/lYIHGez5PySketj9cEimzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJldEv%2FbtsQIBv4zYt%2FlYIHGez5PySketj9cEimzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1040&quot; height=&quot;820&quot; data-filename=&quot;스크린샷 2025-09-23 오전 7.41.30.png&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;mongodb - read&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drwuk8/btsQITiUpaP/NXRkJAUa4zHTRRTkCmMjLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drwuk8/btsQITiUpaP/NXRkJAUa4zHTRRTkCmMjLk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;934&quot; data-filename=&quot;스크린샷 2025-09-23 오전 7.42.14.png&quot; data-widthpercent=&quot;22.51&quot; style=&quot;width: 22.249148%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drwuk8/btsQITiUpaP/NXRkJAUa4zHTRRTkCmMjLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdrwuk8%2FbtsQITiUpaP%2FNXRkJAUa4zHTRRTkCmMjLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1028&quot; height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dK1jFs/btsQKCHvwbn/WaQ1qCo6j6UuRlrnkAUAIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dK1jFs/btsQKCHvwbn/WaQ1qCo6j6UuRlrnkAUAIK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;284&quot; data-filename=&quot;스크린샷 2025-09-23 오전 7.42.22.png&quot; data-widthpercent=&quot;77.49&quot; style=&quot;width: 76.588061%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dK1jFs/btsQKCHvwbn/WaQ1qCo6j6UuRlrnkAUAIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdK1jFs%2FbtsQKCHvwbn%2FWaQ1qCo6j6UuRlrnkAUAIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1076&quot; height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;mongodb - update&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyjp5B/btsQKB9FN9F/wScblK9KjtM7HlCsK09i71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyjp5B/btsQKB9FN9F/wScblK9KjtM7HlCsK09i71/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;836&quot; data-filename=&quot;스크린샷 2025-09-23 오전 7.42.44.png&quot; data-widthpercent=&quot;39.52&quot; style=&quot;width: 39.060901%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyjp5B/btsQKB9FN9F/wScblK9KjtM7HlCsK09i71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcyjp5B%2FbtsQKB9FN9F%2FwScblK9KjtM7HlCsK09i71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1052&quot; height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMZV6L/btsQIOB0WZd/BfXZLZkgP2p8vP9QB0OvI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMZV6L/btsQIOB0WZd/BfXZLZkgP2p8vP9QB0OvI1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;2230&quot; data-origin-height=&quot;1158&quot; data-filename=&quot;스크린샷 2025-09-23 오전 7.42.56.png&quot; data-widthpercent=&quot;60.48&quot; style=&quot;width: 59.776309%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMZV6L/btsQIOB0WZd/BfXZLZkgP2p8vP9QB0OvI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMZV6L%2FbtsQIOB0WZd%2FBfXZLZkgP2p8vP9QB0OvI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2230&quot; height=&quot;1158&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;mongodb - delete&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/118</guid>
      <comments>https://m2nseop.tistory.com/118#entry118comment</comments>
      <pubDate>Tue, 23 Sep 2025 08:01:37 +0900</pubDate>
    </item>
    <item>
      <title>테스트 코드 작성 순서</title>
      <link>https://m2nseop.tistory.com/117</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 쉬운 경우에서 어려운 경우로 진행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 예외적인 경우에서 정상인 경우로 진행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 정해진 값을 리턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 값 비교를 이용해서 정해진 값을 리턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 다양한 테스트를 추가하면서 구현을 일반화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 코드 중복, 메서드 추출 등의 방법을 통해서 지속적인 리팩토링 작업 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot; data-huuid=&quot;4001804280974700029&quot;&gt;&lt;span&gt;java.time.LocalDate&lt;span&gt;&amp;nbsp;&lt;/span&gt;클래스의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;plusMonths()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot; data-huuid=&quot;4001804280974700029&quot;&gt;&lt;span&gt;: 특정&lt;span&gt;&amp;nbsp;&lt;/span&gt;LocalDate&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체에 지정된 개월 수를 더한 새로운&lt;span&gt;&amp;nbsp;&lt;/span&gt;LocalDate&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체를 반환하는 메서드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot; data-huuid=&quot;4001804280974697660&quot;&gt;&lt;span&gt;이 함수는 원래의 날짜를 변경하지 않고 새로운 날짜 객체를 생성하며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;LocalDateTime에서도 동일한 방식으로 사용가능&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;span style=&quot;color: #001d35;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;* 이전 달과 이번 달의 말일이 같지 않아도 알아서 처리해줌&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터 개수는 적을 수록 코드 가독성과 유지보수에 유리함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 파라미터의 개수가 3개 이상이 될 때 객체로 바꿔 1개로 줄이는 것을 고려해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD를 시작할 때 테스트할 목록을 미리 정하면 좋음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이때 지라/트렐로 같은 시스템을 이용하면 테스트 사례를 하위 작업으로 등록해서 테스트 통과 여부 추적 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 성공시켜나가는 형식으로 짧은 개발 리듬을 잡고 가는 것이 더 효율적인 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 구현에서 막혔을 경우 단언/검증 코드부터 하나씩 만들어가보는 것이 좋음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/117</guid>
      <comments>https://m2nseop.tistory.com/117#entry117comment</comments>
      <pubDate>Fri, 12 Sep 2025 12:52:59 +0900</pubDate>
    </item>
    <item>
      <title>TDD 시작하기</title>
      <link>https://m2nseop.tistory.com/116</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;나는 &lt;b&gt;IntelliJ&lt;/b&gt;와 &lt;b&gt;Maven&lt;/b&gt;을 이용해서 할 예정이다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 먼저 JUnit 설정을 해야하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 하나를 새로 생성해주고, JUnit5 라이브러리를 추가해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;라이브러리 추가의 경우,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;[File] &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; [Project Structrue ...] 메뉴를 통해서 생성해주면 된다&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;org.juni.jupiter:junit-jupiter:5.5.0 을 입력하라고 하는데, 이렇게 하면 &lt;span style=&quot;color: #ee2323;&quot;&gt;Found:0&lt;/span&gt; 으로 검색이 안된다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 아래와 같이 검색해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tdd캡처1.PNG&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jl8dW/btsQoOvunre/t9kx9Zvty0c76FdOqMk6xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jl8dW/btsQoOvunre/t9kx9Zvty0c76FdOqMk6xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jl8dW/btsQoOvunre/t9kx9Zvty0c76FdOqMk6xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJl8dW%2FbtsQoOvunre%2Ft9kx9Zvty0c76FdOqMk6xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1033&quot; height=&quot;872&quot; data-filename=&quot;tdd캡처1.PNG&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 클래스를 추가해서 테스트 코드를 실행하면, 잘 실행이 되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tdd캡처2.PNG&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPn2Hk/btsQoQ7VtQ8/EfI44almYI31sr6ATMGx01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPn2Hk/btsQoQ7VtQ8/EfI44almYI31sr6ATMGx01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPn2Hk/btsQoQ7VtQ8/EfI44almYI31sr6ATMGx01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPn2Hk%2FbtsQoQ7VtQ8%2FEfI44almYI31sr6ATMGx01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;252&quot; data-filename=&quot;tdd캡처2.PNG&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 메이븐 프로젝트에서 JUnit을 설정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서 pom.xml 파일에 JUnit5를 위한 의존성을 추가해주어야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; TDD : Test-Driven Development (테스트 주도 개발)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능을 검증하는 테스트 코드를 먼저 작성하고 테스트를 통과시키기 위해 개발을 진행하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ example1_계산기 예제 ]&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assertEquals() : 인자로 받은 두 값이 동일한지 비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용 방법 : assertEquals(기대값, 실제값)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 일치하지 않는 경우 : AssertionFailedError 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;인스턴스 메소드 VS 정적 메소드&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 인스턴스 메소드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 객체를 생성해야 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;●&amp;nbsp; 정적 메소드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+) &lt;span style=&quot;color: #666666;&quot;&gt;src/test/java/&lt;/span&gt; 소스 폴더는 배포 대상에서 제외됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 &amp;rarr; 코딩 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; 리팩토링&amp;nbsp; &amp;nbsp; :&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;/span&gt;이 과정을 계속 반복한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD의 이점 : 코드 수정에 대한 피드백이 빠름 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; 잘못된 코드가 배포 되는 것 방지 가능&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 쉽거나 가장 예외적인 상황을 선택해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; : 모든 조건을 충족하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 패스워드 문자열의 길이가 8글자 미만, 나머지 조건은 충족 == 암호 강도: 보통&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 숫자를 포함하지 않고 나머지 조건은 충족하는 암호 == 암호강도: 보통&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;네 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 값이 없는 경우 NPE(Null Pointer Exception) 발생&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 해결하기 위해서는&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - IllegalArgumentException 발생&amp;nbsp; &amp;nbsp; /&amp;nbsp; &amp;nbsp;PasswordStrength.INVALID 설정&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다섯 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 대문자 포함하지 않고 나머지 조건을 충족하는 경우&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이렇게 까지 하면 한 가지 조건만 충족하거나 모든 조건을 충족하지 않는 경우만 남음&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;여섯 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 길이가 8글자 이상인 조건만 충족&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일곱 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 숫자 포함 조건만 만족함&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여덟 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 대문자 포함 조건만 충족&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일일이 테스트마다 로직을 따로 if문으로 분기한다면, 코드 길이도 길어지고 가독성도 떨어짐&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 공통적인 부분이 뭘까?? =&amp;gt; 조건 충족&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;개수&lt;/span&gt;로 묶으면 어떨까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아홉 번째 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 아무 조건도 충족하지 않은 경우&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pujaA/btsQt6JBXjy/qjtwExflouJNkowFETb8vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pujaA/btsQt6JBXjy/qjtwExflouJNkowFETb8vK/img.png&quot; data-alt=&quot;완성!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pujaA/btsQt6JBXjy/qjtwExflouJNkowFETb8vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpujaA%2FbtsQt6JBXjy%2FqjtwExflouJNkowFETb8vK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1005&quot; height=&quot;428&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/116</guid>
      <comments>https://m2nseop.tistory.com/116#entry116comment</comments>
      <pubDate>Tue, 9 Sep 2025 12:42:21 +0900</pubDate>
    </item>
    <item>
      <title>스프링 데이터 JPA</title>
      <link>https://m2nseop.tistory.com/115</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 데이터 JPA = 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CRUD를 처리하기 위한 공통 인터페이스 제공&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1755439967269&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.springframework.data.jpa.repository.JpaRepository&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리포지토리 개발 시, 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 데이터 접근 계층을 개발 시, 구현 클래스 없이 인터페이스만 작성해도 개발 완료 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 데이터 JPA를 사용하기 위해서는 '&lt;span style=&quot;color: #006dd7;&quot;&gt;spring-data-jpa&lt;/span&gt;' 라이브러리가 필요함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링 데이터 프로젝트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트들 중 하나. 이는 다양한 데이터 저장소에 대한 접근을 추상화하여 개발자의 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;공통 인터페이스 기능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 &lt;span style=&quot;color: #006dd7;&quot;&gt;JpaRepository&lt;/span&gt; 인터페이스 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용방법&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JpaRepository 인터페이스를 상속받은 후 제네릭에 엔티티 클래스와 엔티티 클래스가 사용하는 식별자 타입을 지정하면 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 메소드&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 수정함&lt;/li&gt;
&lt;li&gt;delete(T) : 엔티티 하나를 삭제함. 내부에서 EntityManager.remove() 호출&lt;/li&gt;
&lt;li&gt;findOne(ID) : 엔티티 하나를 조회함. 내부에서 EntityManager.find() 호출&lt;/li&gt;
&lt;li&gt;getOne(ID) : 엔티티를 프록시로 조회함. 내부에서 EntityManager.getReference() 호출&lt;/li&gt;
&lt;li&gt;findAll(...) : 모든 엔티티 조회. 정렬(Sort)이나 페이징(Pageable) 조건을 파라미터로 제공가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;쿼리 메소드 기능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 데이터 JPA가 제공하는 마법 같은 기능&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메소드 이름으로 쿼리 생성&lt;/li&gt;
&lt;li&gt;메소드 이름으로 JPA NamedQuery 호출&lt;/li&gt;
&lt;li&gt;@Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. 메소드 이름으로 쿼리 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 인터페이스에 'findByEmailAndName(...)' 메소드를 실행하면 스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL 생성 후 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. JPA Named Query&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드 이름으로 JPA Named 쿼리를 호출하는 기능 제공. JPA Named Query = 쿼리에 이름을 부여해서 사용하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@NamedQuery 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. @Query 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리포지토리 메소드에 직접 쿼리를 정의하는 것 (org.springframework.data.jpa.repository.Query 사용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름없는 Named 쿼리 = 실행할 메소드에 정적 쿼리를 직접 작성, 애플리케이션 실행 시점에 문법 오류 발견 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;파라미터 바인딩&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위치 기반 : select m from Member m where m.username = ?1&lt;/li&gt;
&lt;li&gt;이름 기반 : select m from Member m where m.username= :name&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값은 위치 기반인데 파라미터 순서로 바인딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+++++&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 : org.springframework.data.domain.Sort&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이징 : org.springframework.data.domain.Pageable (내부에 sort가 포함되어 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;사용자 정의 리포지토리 구현&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정의 인터페이스 작성 &amp;rarr; 사용자 정의 인터페이스를 구현한 클래스 작성 (리포지토리 인터페이스 이름 + Impl) &amp;rarr; 리포지토리 인터페이스에서 사용자 정의 인터페이스 상속받기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>codingtori</author>
      <guid isPermaLink="true">https://m2nseop.tistory.com/115</guid>
      <comments>https://m2nseop.tistory.com/115#entry115comment</comments>
      <pubDate>Sun, 17 Aug 2025 23:35:17 +0900</pubDate>
    </item>
  </channel>
</rss>