<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>young_209.log</title>
        <link>https://velog.io/</link>
        <description>고우고우~</description>
        <lastBuildDate>Wed, 09 Feb 2022 02:03:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>young_209.log</title>
            <url>https://images.velog.io/images/young_209/profile/5bed26eb-576f-4519-8575-90fb688d02b0/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. young_209.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/young_209" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[스프링 핵심 원리 이해1]]></title>
            <link>https://velog.io/@young_209/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EC%9D%B4%ED%95%B41</link>
            <guid>https://velog.io/@young_209/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EC%9D%B4%ED%95%B41</guid>
            <pubDate>Wed, 09 Feb 2022 02:03:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>본 포스팅은 스프링 핵심 원리 - 기본편 강의를 보고 작성하였음</strong></p>
</blockquote>
<h1 id="비즈니스-요구사항과-설계">비즈니스 요구사항과 설계</h1>
<h3 id="요구사항">요구사항</h3>
<ul>
<li>회원<ul>
<li>회원을 가입하고 조회할 수 있다.</li>
<li>회원은 일반과 VIP 두 가지 등급이 있다.</li>
<li>회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)</li>
</ul>
</li>
<li>주문과 할인 정책<ul>
<li>회원은 상품을 주문할 수 있다.</li>
<li>회원 등급에 따라 할인 정책을 적용할 수 있다.</li>
<li>할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있다.)</li>
<li>할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)</li>
</ul>
</li>
</ul>
<h3 id="회원-도메인-설계">회원 도메인 설계</h3>
<h4 id="회원-도메인-협력-관계">회원 도메인 협력 관계</h4>
<p><img src="https://images.velog.io/images/young_209/post/2db6254e-4fd7-4252-a41d-62798beff8e5/image.png" alt=""></p>
<h4 id="회원-클래스-다이어그램">회원 클래스 다이어그램</h4>
<p><img src="https://images.velog.io/images/young_209/post/94ec70a8-3edf-4b53-93fb-aec4c59f6f02/image.png" alt=""></p>
<h4 id="회원-객체-다이어그램">회원 객체 다이어그램</h4>
<ul>
<li>회원 서비스 : MemberServiceImpl
<img src="https://images.velog.io/images/young_209/post/720a5df9-1f76-45e8-99c9-e287151bfb73/image.png" alt=""></li>
</ul>
<h3 id="주문과-할인-도메인-설계">주문과 할인 도메인 설계</h3>
<h4 id="주문-도메인-전체">주문 도메인 전체</h4>
<p><img src="https://images.velog.io/images/young_209/post/a1eac91c-1066-4772-884a-cf001d2b6dda/image.png" alt=""></p>
<ol>
<li>주문 생성 : 클라이언트는 주문 서비스에 주문 생성을 요청함</li>
<li>회원 조회 : 할인을 위해서는 회원 등급이 필요하므로 회원 저장소에서 회원을 조회함</li>
<li>할인 적용 : 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임함</li>
<li>주문 결과 반환 : 주문 서비스는 할인 결과를 반환함</li>
</ol>
<h4 id="주문-도메인-클래스-다이어그램">주문 도메인 클래스 다이어그램</h4>
<p><img src="https://images.velog.io/images/young_209/post/439dd904-c913-4a0c-8777-70e0881362a6/image.png" alt=""></p>
<h4 id="주문-도메인-객체-다이어그램-1">주문 도메인 객체 다이어그램 1</h4>
<p><img src="https://images.velog.io/images/young_209/post/11db7659-19c7-4548-a1d1-0b9765ac9977/image.png" alt=""></p>
<ul>
<li>회원을 메모리에서 조회하고, 정액 할인 정책(고정 금액)을 지원해도 주문 서비스를 변경하지 않아도 됨</li>
<li>역활들의 협력 관계를 그래도 재사용 할 수 있음</li>
</ul>
<h4 id="주문-도메인-객체-다이어그램-2">주문 도메인 객체 다이어그램 2</h4>
<p><img src="https://images.velog.io/images/young_209/post/c2253ac2-acb5-48ca-b97d-cf7d850c0b17/image.png" alt=""></p>
<ul>
<li>회원을 실제 DB에서 조회하고, 정률 할인 정책(주문 금액에 따라 % 할인)을 지원해도 주문 서비스를 변경하지 않아도 됨</li>
<li>역활들의 협력 관계를 그래도 재사용 할 수 있음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 설계와 스프링]]></title>
            <link>https://velog.io/@young_209/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81</link>
            <guid>https://velog.io/@young_209/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81</guid>
            <pubDate>Tue, 08 Feb 2022 06:20:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>본 포스팅은 스프링 핵심 원리 - 기본편 강의를 보고 작성하였음</strong></p>
</blockquote>
<h1 id="1-스프링이란">1. 스프링이란?</h1>
<ul>
<li>자바 언어 기바의 프레임 워크<ul>
<li>자바 언어의 가장 큰 특징 - <strong>객체 지향 언어</strong></li>
</ul>
</li>
<li>객체 지향 언어가 가진 강력한 특징을 살려내느 프레임워크</li>
<li><strong>좋은 객체 지향</strong> 애플리케이션을 개발할 수 있게 도와 주는 프레임워트</li>
</ul>
<h1 id="2-좋은-객체-지향-프로그래밍">2. 좋은 객체 지향 프로그래밍?</h1>
<h2 id="21-객체-지향-특징">2.1 객체 지향 특징</h2>
<ul>
<li>추상화</li>
<li>캡슐화</li>
<li>상속</li>
<li><strong>다형성</strong></li>
</ul>
<h2 id="22-객체-지향-프로그래밍">2.2 객체 지향 프로그래밍</h2>
<ul>
<li>프로그램을 <strong>유연</strong>하고 <strong>변경</strong>이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용됨</li>
</ul>
<h4 id="✓-유연하고-변경이-용이">✓ 유연하고, 변경이 용이?</h4>
<ul>
<li>레고 플럭 조립하듯</li>
<li>키보드, 카우스 갈아 끼우듯이</li>
<li>컴퓨터 부품 갈아 끼우듯이</li>
<li>컴포넌트를 쉽고 유연하게 변경하며서 개발할 수 있는 방법</li>
</ul>
<h3 id="221-다형성polymorphism">2.2.1 다형성(Polymorphism)?</h3>
<h4 id="다형성의-실세계-비유">다형성의 실세계 비유?</h4>
<ul>
<li>실세계와 객체 지향을 1:1로 매칭 X</li>
<li>그래도 실세계의 비유로 이해하기에는 좋음</li>
<li><strong>역활</strong>과 <strong>구현</strong>으로 세상을 구분<h4 id="운전자---자동차">운전자 - 자동차</h4>
<img src="https://images.velog.io/images/young_209/post/707a1fb3-aba4-4f83-9791-ebaf1817d813/image.png" alt=""><h4 id="공연-무대">공연 무대</h4>
<img src="https://images.velog.io/images/young_209/post/b3c1255e-49b8-4e2d-a65d-3fb50f01b6ca/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-08%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2012.56.45.png" alt=""><h4 id="역활과-구현을-분리">역활과 구현을 분리</h4>
<ul>
<li><strong>역활</strong>과 <strong>구현</strong>으로 구분하면 세상이 <strong>단순</strong>해지고, <strong>유현</strong>해지며 <strong>변경</strong>도 편리해짐</li>
<li>장점<ul>
<li>대상의 역활(인터페이스)만 알면 됨</li>
<li>구현 대상의 <strong>내부 구조를 몰라도</strong> 됨</li>
<li>구현 대상의 <strong>내부 구조가 변경</strong>되어도 영향을 받지 않음</li>
<li>구현 <strong>대상 자체를 변경</strong>해도 영향을 받지 않음</li>
</ul>
</li>
<li>자바 언어의 다형성을 활용    <ul>
<li>역활 = 인터페이스</li>
<li>구현 = 이너페이스를 구현한 클래스, 구현 객체</li>
<li>객체를 설계 시 <strong>역활</strong>과 <strong>구현</strong>을 명확히 분리<h4 id="자바에서의-다형성">자바에서의 다형성</h4>
</li>
<li><strong>오버라이딩</strong>!<ul>
<li>부모 클래스로부터 상속받은 메소드를 자식 클래스에서 재정의하여 사용하는 것</li>
</ul>
</li>
<li>다형성으로 인터페이스를 구현한 객체를 실행 시점에 유현하게 변경할 수 있음</li>
<li>클래스 상속 관계도 다형성, 오버라이딩 적용 가능
<img src="https://images.velog.io/images/young_209/post/2c9093b9-c3f0-4e9e-b201-8708231d0101/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="222-스프링과-객체-지향">2.2.2 스프링과 객체 지향</h3>
<ul>
<li>다형성이 가장 중요</li>
<li>스프링은 다형성을 극대화해서 이용할 수 있게 도와줌</li>
<li>스프링에서 이야기하는 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역활과 구현을 편리하게 다룰 수 있도록 지원함</li>
<li>스프링을 사용하면 구현을 편리하게 변경할 수 있음</li>
</ul>
<h1 id="3-좋은-객체-지향-설계의-5가지-원칙-solid">3. 좋은 객체 지향 설계의 5가지 원칙 (SOLID)</h1>
<ul>
<li>SRP : 단일 책임 원칙(single responsibility principle)</li>
<li>OCP: 개방-폐쇄 원칙 (Open/closed principle)</li>
<li>LSP: 리스코프 치환 원칙 (Liskov substitution principle) </li>
<li>ISP: 인터페이스 분리 원칙 (Interface segregation principle)</li>
<li>DIP: 의존관계 역전 원칙 (Dependency inversion principle)</li>
</ul>
<h3 id="srp-단일-책임-원칙">SRP 단일 책임 원칙</h3>
<ul>
<li>한 클래스는 하나의 책임만 가져야 한다. </li>
<li>하나의 책임이라는 것은 모호하다.<ul>
<li>클 수도 있고, 작을 수도 있다.</li>
<li>문맥과 상황에 따라 다르다.</li>
</ul>
</li>
<li><strong>중요한 기준으 변경</strong><ul>
<li>변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 지킨것</li>
</ul>
</li>
<li>예) UI 변경, 객체의 생성과 사용을 분리</li>
</ul>
<h3 id="ocp-개방-폐쇄-원칙">OCP 개방-폐쇄 원칙</h3>
<ul>
<li><p><strong>확장에는 열려</strong> 있으나 <strong>변경에는 닫혀</strong> 있어야함</p>
</li>
<li><p><strong>다형성</strong>을 활용할 필요</p>
</li>
<li><p>인터페이스를 구현한 새로운 클래스를 하나 만들어 새로운 기능을 구현</p>
</li>
<li><p>문제점</p>
<ul>
<li>구현 객체를 변경하려면 클라이언트 코드를 변경해야함 </li>
<li><blockquote>
<p>객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요함</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h3 id="lsp-리스코프-치환-원칙">LSP 리스코프 치환 원칙</h3>
<ul>
<li>프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야함</li>
<li>예) 자동차 인터페이스의 엑셀은 앞으로 가라는 기능, 뒤로 가게 구현하면 LSP 위반, 느리 더라도 앞으로 가야함</li>
</ul>
<h3 id="isp-인터페이스-분리-원칙">ISP 인터페이스 분리 원칙</h3>
<ul>
<li>특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나 보다 낫다</li>
<li>자동차 인터페이스 -&gt; 운전 / 정비 인터페이스 분리</li>
<li>사용자 클라이언트 -&gt; 운전자 / 정비사 클라이언트 분리</li>
</ul>
<h3 id="dip-의존관계-역전-원칙">DIP 의존관계 역전 원칙</h3>
<ul>
<li>구현 클래스에 의존하지 X, 인터페이스에 의존하라는 뜻</li>
<li><strong>역활(Role)에 의존하게 해야 한다는 것과 같음</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[운영 이슈 테스트 : Chaos Monkey]]></title>
            <link>https://velog.io/@young_209/%EC%9A%B4%EC%98%81-%EC%9D%B4%EC%8A%88-%ED%85%8C%EC%8A%A4%ED%8A%B8-Chaos-Monkey</link>
            <guid>https://velog.io/@young_209/%EC%9A%B4%EC%98%81-%EC%9D%B4%EC%8A%88-%ED%85%8C%EC%8A%A4%ED%8A%B8-Chaos-Monkey</guid>
            <pubDate>Mon, 10 Jan 2022 05:48:38 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-소개">1. 소개</h1>
<ul>
<li><p>카오스 엔지니어링(<a href="http://channy.creation.net/blog/1173">http://channy.creation.net/blog/1173</a>) 툴</p>
<ul>
<li>프로덕션 환경, 특히 분산 시스템 환경에서 불확실성을 파악하고 해결 방안을 모색하는데 사용하는 툴</li>
</ul>
</li>
<li><p>운영 환경 불확실성의 예</p>
<ul>
<li>네트워크 지연</li>
<li>서버 장애</li>
<li>디스크 오작동</li>
<li>메모리 누수</li>
<li>등등</li>
</ul>
</li>
<li><p>Chaos Monkey 스프링 부트 (<a href="https://codecentric.github.io/chaos-monkey-spring-boot/latest/">https://codecentric.github.io/chaos-monkey-spring-boot/latest/</a>)</p>
<ul>
<li>스프링 부트 애플리케이션에 카오스 멍키를 손쉽게 적용해 볼 수 있는 툴</li>
<li>즉, 스프링 부트 애플리케이션을 망가트릴 수 있는 툴</li>
</ul>
</li>
<li><p>카오스 멍키 스프링 부트 주요 개념</p>
<ul>
<li>공격 대상 (Watcher)<ul>
<li>@RestController</li>
<li>@Controller</li>
<li>@Service</li>
<li>@Repository</li>
<li>@Component</li>
</ul>
</li>
<li>공격 유형 (Assaults)<ul>
<li>응답 지연 (Latency Assault)</li>
<li>예외 발생 (Exception Assault)</li>
<li>애플리케이션 종료 (AppKiller Assault)</li>
<li>메모리 누수 (Memory Assault)</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="2-설치-cm4sb">2. 설치 (CM4SB)</h1>
<h3 id="의존성-추가">의존성 추가</h3>
<pre><code>    implementation &#39;org.springframework.boot:spring-boot-starter-actuator&#39;
    implementation &#39;de.codecentric:chaos-monkey-spring-boot:2.5.4&#39;</code></pre><ul>
<li>de.codecentric:chaos-monkey-spring-boot<ul>
<li><a href="https://codecentric.github.io/chaos-monkey-spring-boot/latest/">https://codecentric.github.io/chaos-monkey-spring-boot/latest/</a></li>
<li>스프링 부트용 카오스 멍키</li>
</ul>
</li>
<li>Spring-boot-starter-actuator<ul>
<li>스프링 부트 운영 툴로, 런타임 중에 카오스 멍키 설정을 변경할 수 있다.</li>
<li>그밖에도 헬스 체크, 로그 레벨 변경, 매트릭스 데이터 조회 등 다양한 운영 툴로 사용 가능.</li>
<li>/actuator</li>
</ul>
</li>
</ul>
<h3 id="applicationproperties에-추가">application.properties에 추가</h3>
<pre><code># profile이 chaos-monkey로 되어 있어야 테스트 가능
spring.profiles.active=chaos-monkey

# chaosmonkey endpoints 활성화
management.endpoint.chaosmonkey.enabled=true
management.endpoints.web.exposure.include=health,info,chaosmonkey</code></pre><h1 id="3-테스트">3. 테스트</h1>
<h3 id="테스트를-위한-사용-툴">테스트를 위한 사용 툴</h3>
<ul>
<li>HTTPies    <ul>
<li>카오스 멍키 설정 변경</li>
</ul>
</li>
<li>JMeter    <ul>
<li>카오스 멍키 설정 변경 후 Assault 확인</li>
</ul>
</li>
</ul>
<h3 id="응답-지연-테스트">응답 지연 테스트</h3>
<ul>
<li><a href="https://codecentric.github.io/chaos-monkey-spring-boot/latest/#_latency_assault">https://codecentric.github.io/chaos-monkey-spring-boot/latest/#_latency_assault</a></li>
<li>Repository Watcher 활성화    <ul>
<li>chaos.monkey.watcher.repository=true</li>
</ul>
</li>
<li>카오스 멍키 활성화    <ul>
<li>http post localhost:8080/actuator/chaosmonkey/enable</li>
</ul>
</li>
<li>카오스 멍키 활성화 확인    <ul>
<li>http localhost:8080/actuator/chaosmonkey/status</li>
</ul>
</li>
<li>카오스 멍키 와처 확인    <ul>
<li>http localhost:8080/actuator/chaosmonkey/watchers</li>
</ul>
</li>
<li>카오스 멍키 지연 공격 설정<ul>
<li>http POST localhost:8080/actuator/chaosmonkey/assaults level=3 latencyRangeStart=2000 latencyRangeEnd=5000 latencyActive=true</li>
</ul>
</li>
<li>테스트    <ul>
<li>JMeter 확인
<img src="https://images.velog.io/images/young_209/post/3f576b0e-7e36-403e-9711-0b70ac601670/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="에러-테스트">에러 테스트</h3>
<ul>
<li><a href="https://codecentric.github.io/chaos-monkey-spring-boot/latest/#_exception_assault">https://codecentric.github.io/chaos-monkey-spring-boot/latest/#_exception_assault</a></li>
<li>카오스 멍키 활성화    <ul>
<li>http post localhost:8080/actuator/chaosmonkey/enable</li>
</ul>
</li>
<li>카오스 멍키 활성화 확인    <ul>
<li>http localhost:8080/actuator/chaosmonkey/status</li>
</ul>
</li>
<li>카오스 멍키 와처 확인    <ul>
<li>http localhost:8080/actuator/chaosmonkey/watchers</li>
</ul>
</li>
<li>카오스 멍키 지연 공격 설정<ul>
<li>http POST localhost:8080/actuator/chaosmonkey/assaults level=3 latencyActive=false exceptionsActive=true exception.type=java.lang.RuntimeException
<img src="https://images.velog.io/images/young_209/post/1c4f8d79-527f-4001-91d6-8e5c1323d387/image.png" alt=""></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[성능 테스트 : JMeter]]></title>
            <link>https://velog.io/@young_209/%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8-JMeter</link>
            <guid>https://velog.io/@young_209/%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8-JMeter</guid>
            <pubDate>Mon, 10 Jan 2022 02:23:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-소개">1. 소개</h1>
<ul>
<li>성능 측정 및 부하(load) 테스트 기능을 제공하는 오픈 소스 자바 어플리케이션 (<a href="https://jmeter.apache.org/">https://jmeter.apache.org/</a>)</li>
<li>다양한 형태의 어플리케이션 테스트 지원    <ul>
<li>웹 - HTTP, HTTPS</li>
<li>SOAP / REST 웹 서비스</li>
<li>FTP</li>
<li>데이터베이스 (JDBC 사용)</li>
<li>Mail (SMTP, POP3, IMAP)</li>
<li>등등    </li>
</ul>
</li>
<li>CLI 지원    <ul>
<li>CI 또는 CD 툴과 연동할 때 편리함.    </li>
<li>UI 사용하는 것보다 메모리 등 시스템 리소스를 적게 사용.</li>
</ul>
</li>
<li>주요 개념<ul>
<li>Thread Group: 한 쓰레드 당 유저 한명</li>
<li>Sampler: 어떤 유저가 해야 하는 액션</li>
<li>Listener: 응답을 받았을 할 일 (리포팅, 검증, 그래프 그리기 등)</li>
<li>Configuration: Sampler 또는 Listener가 사용할 설정 값 (쿠키, JDBC 커넥션 등)</li>
<li>Assertion: 응답이 성공적인지 확인하는 방법 (응답 코드, 본문 내용 등)</li>
</ul>
</li>
<li>대체제<ul>
<li>Gatling</li>
<li>nGrinder</li>
</ul>
</li>
</ul>
<h1 id="2-설치">2. 설치</h1>
<ul>
<li><a href="https://jmeter.apache.org/download_jmeter.cgi">https://jmeter.apache.org/download_jmeter.cgi</a></li>
<li>압축 파일 받고 압축 파일 풀기. 원한다면 PATH에 bin 디렉토리를 추가.</li>
<li>bin/jmeter 실행하기</li>
</ul>
<h1 id="3-사용하기">3. 사용하기</h1>
<ul>
<li><p>Thread Group 만들기</p>
<ul>
<li><p>Number of Threads: 쓰레드 개수</p>
</li>
<li><p>Ramp-up period: 쓰레드 개수를 만드는데 소요할 시간</p>
</li>
<li><p>Loop Count</p>
<ul>
<li>infinite 체크 하면 위에서 정한 쓰레드 개수로 계속 요청 보냄 </li>
<li>값을 입력하면 해당 쓰레드 개수 X 루프 개수 만큼 요청 보냄</li>
</ul>
</li>
</ul>
</li>
<li><p>Sampler 만들기</p>
<ul>
<li>여러 종류의 샘플러가 있으나 그 중에 사용할 샘플러는 HTTP Request 샘플러</li>
<li>HTTP Sampler<ul>
<li>요청을 보낼 호스트, 포트, URI, 요청 본문 등을 설정</li>
</ul>
</li>
<li>여러 샘플러를 순차적으로 등록하는 것도 가능</li>
</ul>
</li>
<li><p>Listener 만들기</p>
<ul>
<li>View Results Tree</li>
<li>View Resulrts in Table</li>
<li>Summary Report</li>
<li>Aggregate Report</li>
<li>Response Time Graph</li>
<li>Graph Results</li>
<li>등등</li>
</ul>
</li>
<li><p>Assertion 만들기</p>
<ul>
<li>응답 코드 확인</li>
<li>응답 본문 확인</li>
</ul>
</li>
<li><p>CLI 사용하기    </p>
<ul>
<li>jmeter -n -t 설정 파일 -l 리포트 파일</li>
</ul>
</li>
</ul>
<p><img src="https://images.velog.io/images/young_209/post/cd6c8578-005c-4b5a-88d6-54da62f5741b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커와 테스트 : Testcontainers 소개 / 설치 / 기능]]></title>
            <link>https://velog.io/@young_209/%EB%8F%84%EC%BB%A4%EC%99%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-Testcontainers-%EC%86%8C%EA%B0%9C-%EC%84%A4%EC%B9%98-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@young_209/%EB%8F%84%EC%BB%A4%EC%99%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-Testcontainers-%EC%86%8C%EA%B0%9C-%EC%84%A4%EC%B9%98-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Wed, 05 Jan 2022 02:30:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-testcontainers-소개">1. Testcontainers 소개</h1>
<ul>
<li>테스트에서 도커 컨테이너를 실행할 수 있는 라이브러리 (<a href="https://www.testcontainers.org/">https://www.testcontainers.org/</a>)</li>
<li>테스트 실행시 DB를 설정하거나 별도의 프로그램 또는 스크립트를 실행할 필요 없음</li>
<li>보다 Production에 가까운 테스트를 만들 수 있음</li>
<li>테스트가 느려짐</li>
</ul>
<h3 id="전제-조건">전제 조건</h3>
<ul>
<li>로컬 PC에 Docker가 설치되어 있어야 함</li>
</ul>
<h1 id="2-testcontainers-설치">2. Testcontainers 설치</h1>
<h3 id="gradle-설정-추가">Gradle 설정 추가</h3>
<pre><code>dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    implementation &#39;org.projectlombok:lombok:1.18.20&#39;
    annotationProcessor &#39;org.projectlombok:lombok&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
    testImplementation &#39;org.postgresql:postgresql:42.3.1&#39;

    /* testcontainers */
    testImplementation &#39;org.testcontainers:postgresql:1.16.2&#39;
    testImplementation &#39;org.testcontainers:junit-jupiter&#39;
}

dependencyManagement {
    imports {
        mavenBom(&quot;org.testcontainers:testcontainers-bom:1.16.2&quot;)
    }
}</code></pre><h3 id="application-testproperties-추가">application-test.properties 추가</h3>
<ul>
<li>src/test/resources/application-test.properties</li>
</ul>
<pre><code>spring.datasource.url=jdbc:tc:postgresql:///postgres
// Spring Boot version 2.3.0 이면 추가 해야함
// spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.jpa.hibernate.ddl-auto=create-drop</code></pre><h3 id="test-소스">Test 소스</h3>
<pre><code>@SpringBootTest
@ExtendWith(MockitoExtension.class)
@ActiveProfiles(&quot;test&quot;)
@Testcontainers
class TestcontainersTest {

    @Mock
    MemberService memberService;

    @Autowired
    StudyRepository studyRepository;

    @Container
    static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer(&quot;postgres&quot;).withDatabaseName(&quot;study-db&quot;);

    @BeforeAll
    static void beforeAll() {
        String jdbcUrl = postgreSQLContainer.getJdbcUrl();
        String databaseName = postgreSQLContainer.getDatabaseName();

        System.out.println(&quot;databaseName : &quot; + databaseName + &quot; / jdbcUrl : &quot; + jdbcUrl);
    }

    @BeforeEach
    void beforeEach() {
        studyRepository.deleteAll();
    }

    @Test
    void createNewStudy() {
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

        Member member = new Member();
        member.setId(1L);
        member.setEmail(&quot;lee@gamil.com&quot;);

        Study study = new Study(10, &quot;테스트&quot;);

        when(memberService.findById(1L)).thenReturn(Optional.of(member));

        studyService.createNewStudy(1L, study);

        assertNotNull(study.getOwnerId());
        assertEquals(member.getId(), study.getOwnerId());

        verify(memberService, times(1)).notify(study);

        verifyNoMoreInteractions(memberService);
    }

    @DisplayName(&quot;다른 사용자가 볼 수 있도록 스터디를 공개한다.&quot;)
    @Test
    void openStudy() {
        // Given
        StudyService studyService = new StudyService(memberService, studyRepository);
        Study study = new Study(10, &quot;the Java, Test&quot;);
//        given(studyRepository.save(study)).willReturn(study);

        // When
        studyService.openStudy(study);

        // Then
        assertEquals(study.getStatus(),StudyStatus.OPENED);
        assertNotNull(study.getOpenedDateTime());
        then(memberService).should().notify(study);
    }
}</code></pre><h1 id="3-testcontainers-기능">3. Testcontainers 기능</h1>
<h3 id="어노테이션">어노테이션</h3>
<ul>
<li>@Testcontainers    <ul>
<li>JUnit 5 확장팩으로 테스트 클래스에 @Container를 사용한 필드를 찾아서 컨테이너 라이프사이클 관련 메소드를 실행</li>
</ul>
</li>
<li>@Container    <ul>
<li>인스턴스 필드에 사용하면 모든 테스트 마다 컨테이너를 재시작 하고, 스태틱 필드에 사용하면 클래스 내부 모든 테스트에서 동일한 컨테이너를 재사용<h3 id="컨테이너-만들기">컨테이너 만들기</h3>
New GenericContainer(String imageName)</li>
</ul>
</li>
</ul>
<h3 id="네트워크">네트워크</h3>
<ul>
<li>withExposedPorts(int...)    <ul>
<li>DB port가 같다면 테스트 DB 및 운영서버 DB 중 랜덤하게 사용(충돌하지 않는 포트 우선사용)</li>
</ul>
</li>
<li>getMappedPort(int)    <ul>
<li>내 호스트가 어떤 포트를 사용했는지 확인</li>
</ul>
</li>
</ul>
<h3 id="환경-변수-설정">환경 변수 설정</h3>
<p>withEnv(key, value)</p>
<h3 id="명령어-실행">명령어 실행</h3>
<p>withCommand(String cmd...)</p>
<h3 id="사용할-준비가-됐는지-확인하기">사용할 준비가 됐는지 확인하기</h3>
<ul>
<li>waitingFor(Wait)    <ul>
<li>언제 서버가 가용되는데 시간이 걸리는지</li>
</ul>
</li>
<li>Wait.forHttp(String url)    <ul>
<li>Http요청으로 응답을 확인 및 해당 컨테이너가 가용한지 확인 후 테스트 진행</li>
</ul>
</li>
<li>Wait.forLogMessage(String message)    <ul>
<li>특정한 로그 메세지가 출력이 되었는지  확인 후 테스트 진행</li>
</ul>
</li>
</ul>
<h3 id="로그-살펴보기">로그 살펴보기</h3>
<ul>
<li>getLogs()    <ul>
<li>현재까지의 컨테이너의 로그들을 모두 가져오기</li>
</ul>
</li>
<li>followOutput()    <ul>
<li>스트리밍으로 로그를 출력</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mockito : Mock 객체 확인 및 BDD 스타일 Mockito API]]></title>
            <link>https://velog.io/@young_209/Mockito-Mock-%EA%B0%9D%EC%B2%B4-%ED%99%95%EC%9D%B8-%EB%B0%8F-BDD-%EC%8A%A4%ED%83%80%EC%9D%BC-Mockito-API</link>
            <guid>https://velog.io/@young_209/Mockito-Mock-%EA%B0%9D%EC%B2%B4-%ED%99%95%EC%9D%B8-%EB%B0%8F-BDD-%EC%8A%A4%ED%83%80%EC%9D%BC-Mockito-API</guid>
            <pubDate>Tue, 04 Jan 2022 08:05:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-mock-객체-확인">1. Mock 객체 확인</h1>
<ul>
<li>Mock 객체가 어떻게 사용이 됐는지 확인할 수 있음    <ul>
<li>1) 특정 메소드가 특정 매개변수로 몇번 호출 되었는지, 최소 한번은 호출 됐는지, 전혀 호출되지 않았는지 확인<ul>
<li><a href="https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#exact_verification">https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#exact_verification</a></li>
</ul>
</li>
<li>2) 어떤 순서대로 호출했는지 확인<ul>
<li><a href="https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#in_order_verification">https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#in_order_verification</a></li>
</ul>
</li>
<li>3) 특정 시간 이내에 호출됐는지 확인<ul>
<li><a href="https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#verification_timeout">https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#verification_timeout</a></li>
</ul>
</li>
<li>4) 특정 시점 이후에 아무 일도 벌어지지 않았는지 확인<ul>
<li><a href="https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#finding_redundant_invocations">https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#finding_redundant_invocations</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>// 1,2 만 확인함
    @Test
    void createNewStudy() {
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

        Member member = new Member();
        member.setId(1L);
        member.setEmail(&quot;lee@gamil.com&quot;);

        Study study = new Study(10, &quot;테스트&quot;);

        when(memberService.findById(1L)).thenReturn(Optional.of(member));
        when(studyRepository.save(study)).thenReturn(study);

        studyService.createNewStudy(1L, study);

        assertNotNull(study.getOwner());
        assertEquals(member, study.getOwner());

        verify(memberService, times(1)).notify(study);

        verifyNoMoreInteractions(memberService);
//
//        verify(memberService, times(1)).notify(member);
//        verify(memberService, never()).validate(any());
//
//        InOrder inOrder = inOrder(memberService);
//        inOrder.verify(memberService).notify(study);
//
//        inOrder.verify(memberService).notify(member);
    }</code></pre><h1 id="2-bdd-스타일-mockito-api">2. BDD 스타일 Mockito API</h1>
<h2 id="bdd란">BDD란?</h2>
<ul>
<li>애플리케이션이 어떻게 “행동”해야 하는지에 대한 공통된 이해를 구성하는 방법으로, TDD에서 창안</li>
</ul>
<h3 id="행동에-대한-스팩">행동에 대한 스팩</h3>
<ul>
<li>Title</li>
<li>Narrative    <ul>
<li>As a  / I want / so that    </li>
</ul>
</li>
<li>Acceptance criteria    <ul>
<li>Given / When / Then</li>
</ul>
</li>
</ul>
<h2 id="mockito-bdd-스타일의-api">Mockito BDD 스타일의 API</h2>
<ul>
<li>BddMockito라는 클래스 존재</li>
</ul>
<pre><code>    @Test
    void bddStyle() {
        // Given
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

        Member member = new Member();
        member.setId(1L);
        member.setEmail(&quot;lee@gamil.com&quot;);

        Study study = new Study(10, &quot;테스트&quot;);

//        when(memberService.findById(1L)).thenReturn(Optional.of(member));
//        when(studyRepository.save(study)).thenReturn(study);
        given(memberService.findById(1L)).willReturn(Optional.of(member));
        given(studyRepository.save(study)).willReturn(study);

        // When
        studyService.createNewStudy(1L, study);

        // Then
        assertEquals(member, study.getOwner());

//        verify(memberService, times(1)).notify(study);
        then(memberService).should(times(1)).notify(study);

//        verifyNoMoreInteractions(memberService);
        then(memberService).shouldHaveNoMoreInteractions();
    }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Mockito : Mock 객체 Stubbing]]></title>
            <link>https://velog.io/@young_209/Mockito-Mock-%EA%B0%9D%EC%B2%B4-Stubbing</link>
            <guid>https://velog.io/@young_209/Mockito-Mock-%EA%B0%9D%EC%B2%B4-Stubbing</guid>
            <pubDate>Tue, 28 Dec 2021 01:50:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-mock-객체의-행동">1. Mock 객체의 행동?</h1>
<ol>
<li>Null을 리턴한다. (Optional 타입은 Optional.empty 리턴)</li>
<li>Primitive 타입은 기본 Primitive 값.</li>
<li>콜렉션은 비어있는 콜렉션.</li>
<li>Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않는다.</li>
</ol>
<h1 id="2-stubbing-이란">2. Stubbing 이란?</h1>
<ul>
<li>Mock 객체의 행동을 조작하는 것<ul>
<li>특정한 매개변수를 받은 경우 특정한 값을 리턴하거나 예뢰를 던지도록 만들 수 있음    <ul>
<li>Mock 객체 메서드의 파라미터 값을 하드 코딩하고 싶지 않다면 Mockito의 Argument Matchers를 이용 (ex. any() 등)</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>```
@Test
void stubbing1Test() {
    StudyService studyService = new StudyService(memberService, studyRepository);
    assertNotNull(studyService);

    Member member = new Member();
    member.setId(1L);
    member.setEmail(&quot;lee@gamil.com&quot;);

    when(memberService.findById(any())).thenReturn(Optional.of(member));

    Study study = new Study(10, &quot;java&quot;);

    studyService.createNewStudy(1L, study);
}
```
- Void 메소드 특정 매개변수를 받거나 호출된 경우 예외를 발생 시킬 수 있음
```

@Test
void stubbing2Test() {
    StudyService studyService = new StudyService(memberService, studyRepository);
    assertNotNull(studyService);

    Member member = new Member();
    member.setId(1L);
    member.setEmail(&quot;lee@gamil.com&quot;);

    when(memberService.findById(any())).thenReturn(Optional.of(member));

    assertEquals(&quot;lee@gamil.com&quot;, memberService.findById(1L).get().getEmail());
    assertEquals(&quot;lee@gamil.com&quot;, memberService.findById(2L).get().getEmail());

    doThrow(new IllegalArgumentException()).when(memberService).validate(1L);

    assertThrows(IllegalArgumentException.class, () -&gt; {
       memberService.findById(1L);
    });

    memberService.validate(2L);
}
```

- 메소드가 동일한 매개변수로 여러번 호출될 때 각기 다르게 행동하도록 조작할 수도 있음
```
@Test
void stubbing3Test() {
    StudyService studyService = new StudyService(memberService, studyRepository);
    assertNotNull(studyService);

    Member member = new Member();
    member.setId(1L);
    member.setEmail(&quot;lee@gamil.com&quot;);

    when(memberService.findById(any()))
            .thenReturn(Optional.of(member))
            .thenThrow(new RuntimeException())
            .thenReturn(Optional.empty());

    Optional&lt;Member&gt; byId = memberService.findById(1L);
    assertEquals(&quot;lee@gamil.com&quot;, byId.get().getEmail());

    assertThrows(RuntimeException.class, () -&gt; {
       memberService.findById(2L);
    });

    assertEquals(Optional.empty(), memberService.findById(3L));
}
```</code></pre><h1 id="3-연습-문제">3. 연습 문제</h1>
<pre><code>    @Test
    void stubbing4Test() {
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

        Member member = new Member();
        member.setId(1L);
        member.setEmail(&quot;lee@gamil.com&quot;);

        Study study = new Study(10, &quot;테스트&quot;);

        // TODO memberService 객체에 findById 메소드를 1L 값으로 호출하면 Optional.of(member) 객체를 리턴하도록 Stubbing
        when(memberService.findById(1L)).thenReturn(Optional.of(member));
        // TODO studyRepository 객체에 save 메소드를 study 객체로 호출하면 study 객체 그대로 리턴하도록 Stubbing
        when(studyRepository.save(study)).thenReturn(study);

        studyService.createNewStudy(1L, study);

        assertNotNull(study.getOwner());
        assertEquals(member, study.getOwner());
    }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Mockito : 소개 및 시작]]></title>
            <link>https://velog.io/@young_209/Mockito-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@young_209/Mockito-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Fri, 24 Dec 2021 07:51:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-mockito란">1. Mockito란?</h1>
<ul>
<li>Mock을 지원하는 Framework</li>
<li>Mock은 진짜 객체와 비슷하게 동작하지만 프로그래머가 직접 그 객체의 행동을 관리하는 객체, <strong>가짜 객체임</strong></li>
<li>Mockito는 Mock 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법을 제공</li>
</ul>
<h1 id="2-mockito-시작하기">2. Mockito 시작하기</h1>
<ul>
<li><p>스프링 부트 2.2+ 프로젝트 생성시 spring-boot-starter-test에서 자동으로 Mockito 추가해 줌</p>
</li>
<li><p>스프링 부트 쓰지 않는다면, 의존성 직접 추가</p>
<ul>
<li>Maven 기준<pre><code>  &lt;dependency&gt;
      &lt;groupId&gt;org.mockito&lt;/groupId&gt;
      &lt;artifactId&gt;mockito-core&lt;/artifactId&gt;
      &lt;version&gt;3.1.0&lt;/version&gt;
      &lt;scope&gt;test&lt;/scope&gt;
  &lt;/dependency&gt;

</code></pre></li>
</ul>
</li>
</ul>
<pre><code>    &lt;dependency&gt;
        &lt;groupId&gt;org.mockito&lt;/groupId&gt;
        &lt;artifactId&gt;mockito-junit-jupiter&lt;/artifactId&gt;
        &lt;version&gt;3.1.0&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;</code></pre><pre><code>
# 3. Mock 객체 만들기
### Mockito.mock() 메소드로 만드는 방법</code></pre><p>class StudyServiceTest {</p>
<pre><code>@Test
void create_study_service() {
    MemberService memberService = mock(MemberService.class);
    StudyRepository studyRepository = mock(StudyRepository.class);

    StudyService studyService = new StudyService(memberService, studyRepository);

    assertNotNull(studyService);
}</code></pre><p>}</p>
<pre><code>
### @Mock 애노테이션으로 만드는 방법
- JUnit 5 extension으로 MockitoExtension을 사용해야 한다.

#### 1) 필드</code></pre><p>@ExtendWith(MockitoExtension.class)
class StudyServiceTest {</p>
<pre><code>@Mock
MemberService memberService;

@Mock
StudyRepository studyRepository;

@Test
void create_study_service() {

    StudyService studyService = new StudyService(memberService, studyRepository);

    assertNotNull(studyService);
}</code></pre><p>}</p>
<pre><code>
#### 2) 메소드 매개변수</code></pre><p>@ExtendWith(MockitoExtension.class)
class StudyServiceTest {</p>
<pre><code>@Test
void createStudyService(@Mock MemberService memberService,
                        @Mock StudyRepository studyRepository) {
    StudyService studyService = new StudyService(memberService, studyRepository);
    assertNotNull(studyService);
}</code></pre><p>}</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit 5 : 확장 모델 & JUnit 설정 파일]]></title>
            <link>https://velog.io/@young_209/JUnit-5-%ED%99%95%EC%9E%A5-%EB%AA%A8%EB%8D%B8-JUnit-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC</link>
            <guid>https://velog.io/@young_209/JUnit-5-%ED%99%95%EC%9E%A5-%EB%AA%A8%EB%8D%B8-JUnit-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC</guid>
            <pubDate>Fri, 24 Dec 2021 06:09:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-확장-모델">1. 확장 모델</h1>
<ul>
<li>JUnit 4의 확장 모델은 @RunWith(Runner), TestRule, MethodRule.</li>
<li>JUnit 5의 확장 모델은 단 하나, Extension.</li>
</ul>
<h2 id="확장-등록-방법">확장 등록 방법</h2>
<ul>
<li><p>선언적인 등록 @ExtendWith</p>
<ul>
<li>Extendtion 뒤에 인자로 확장할 Extension을 추가하여 사용</li>
</ul>
</li>
</ul>
<pre><code>@ExtendWith(FindSlowTestExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class StudyTest {
}</code></pre><ul>
<li><p>프로그래밍 등록 @RegisterExtension</p>
<ul>
<li>생성자(Constructor)를 통해 확장기능에 의존성을 주입하거나, 빌더등을 통해서 프로그램을 통한 설정이 가능</li>
<li>생명주기에 맞춰 static으로 선언</li>
<li>@RegisterExtension으로 등록하고자 하는 필드는 null이거나 private으로 지정되면 안됨</li>
</ul>
</li>
</ul>
<pre><code>@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class StudyTest {

    @RegisterExtension
    static FindSlowTestExtension findSlowTestExtension = new FindSlowTestExtension(1000L);
}</code></pre><h1 id="2-junit-설정-파일">2. JUnit 설정 파일</h1>
<ul>
<li><p>JUnit 설정 파일로, 클래스패스 루트 (src/test/resources/)에 넣어두면 적용된다.</p>
</li>
<li><p>junit-platform.properties</p>
<pre><code>#테스트 인스턴스 라이프사이클 설정
junit.jupiter.testinstance.lifecycle.default = per_class
</code></pre></li>
</ul>
<p>#@Disabled 무시하고 실행하기
junit.jupiter.conditions.deactivate = org.junit.*DisabledCondition</p>
<p>#테스트 이름 표기 전략 설정
junit.jupiter.displayname.generator.default = org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit 5 : 테스트 인스턴스 & 순서]]></title>
            <link>https://velog.io/@young_209/JUnit-5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%88%9C%EC%84%9C</link>
            <guid>https://velog.io/@young_209/JUnit-5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%88%9C%EC%84%9C</guid>
            <pubDate>Fri, 24 Dec 2021 02:33:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-테스트-인스턴스">1. 테스트 인스턴스</h1>
<h3 id="junit은-테스트-메소드-마다-테스트-인스턴스를-새로-만든다">JUnit은 테스트 메소드 마다 테스트 인스턴스를 새로 만든다.</h3>
<ul>
<li>테스트 메소드를 독립적으로 실행하여 예상치 못한 부작용을 방지하기 위함이다.<h3 id="이-전략을-junit-5에서-변경할-수-있음">이 전략을 JUnit 5에서 변경할 수 있음</h3>
</li>
<li>@TestInstance(Lifecycle.PER_CLASS)<ul>
<li>테스트 클래스당 인스턴스를 하나만 만들어 사용함</li>
<li>경우에 따라, 테스트 간에 공유하는 모든 상태를 @BeforeEach 또는 @AfterEach에서 초기화 할 필요가 있음</li>
<li>@BeforeAll과 @AfterAll을 인스턴스 메소드 또는 인터페이스에 정의한 default 메소드로 정의할 수도 있음</li>
</ul>
</li>
</ul>
<pre><code>@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StudyTest {

    int value = 1;
    @BeforeAll
    void beforeAll() {
        System.out.println(&quot;BeforeAll!&quot;);
    }

    @AfterAll
    void afterAll() {
        System.out.println(&quot;AfterAll!&quot;);
    }

    @BeforeEach
    void setUp() {
        System.out.println(&quot;BeforeEach!&quot;);
    }

    @AfterEach
    void tearDown() {
        System.out.println(&quot;AfterEach!&quot;);
    }

    @Test
    void instenceCheck1() {
        System.out.println(this);
        System.out.println(value++);
    }

    @Test
    void instenceCheck2() {
        System.out.println(this);
        System.out.println(value++);
    }
}</code></pre><h1 id="2-테스트-순서">2. 테스트 순서</h1>
<p>실행할 테스트 메소드 특정한 순서에 의해 실행되지만 어떻게 그 순서를 정하는지는 의도적으로 분명히 하지 않는다. (테스트 인스턴스를 테스트 마다 새로 만드는 것과 같은 이유)</p>
<p>경우에 따라, 특정 순서대로 테스트를 실행하고 싶을 때도 있다. 그 경우에는 테스트 메소드를 원하는 순서에 따라 실행하도록 @TestInstance(Lifecycle.PER_CLASS)와 함께 @TestMethodOrder를 사용할 수 있다.</p>
<ul>
<li>MethodOrderer 구현체<ul>
<li>기본 구현체    <ul>
<li>MethodName : 메소드명으로 오름차순 정렬 (a~ㄱ), 메소드명이 같다면 파라미터 타입명으로 오름차순 정렬<ul>
<li>@TestMethodOrder(MethodOrderer.MethodName.class)<ul>
<li>DisplayName : @DisplayName의 값으로 오름차순 정렬</li>
<li>@TestMethodOrder(value = MethodOrderer.DisplayName.class)</li>
<li>Random : 랜덤으로 순서를 정하는 방법</li>
<li>@TestMethodOrder(MethodOrderer.Random.class)</li>
<li>OrderAnnoation : @Order(n) 어노테이션으로 순서를 정하는 방법</li>
<li>@TestMethodOrder(value = MethodOrderer.OrderAnnotation.class)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit 5 : 테스트 반복하기]]></title>
            <link>https://velog.io/@young_209/JUnit-5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%98%EB%B3%B5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@young_209/JUnit-5-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%B0%98%EB%B3%B5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 24 Dec 2021 01:26:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-repeatedtest">1. @RepeatedTest</h1>
<ul>
<li>반복 횟수와 반복 테스트 이름을 설정할 수 있다.<ul>
<li>{displayName}  <ul>
<li>@DisplayName로 선언된 이름을 가지고 옴</li>
</ul>
</li>
<li>{currentRepetition}<ul>
<li>현재 반복되어 지는 번호</li>
</ul>
</li>
<li>{totalRepetitions}<ul>
<li>전체 반복하는 횟수</li>
</ul>
</li>
</ul>
</li>
<li>RepetitionInfo 타입의 인자를 받을 수 있다.</li>
</ul>
<pre><code>    @DisplayName(&quot;반복 테스트&quot;)
    @RepeatedTest(value = 10, name = &quot;{displayName}, {currentRepetition} / {totalRepetitions}&quot;)
    void repeatTest(RepetitionInfo info) {
        System.out.println(&quot;test &quot; + info.getCurrentRepetition() + &quot;/&quot; + info.getTotalRepetitions());
    }</code></pre><h1 id="2-parameterizedtest">2. @ParameterizedTest</h1>
<ul>
<li>테스트에 여러 다른 매개변수를 대입해가며 반복 실행한다.<ul>
<li>{displayName}<ul>
<li>@DisplayName로 선언된 이름을 가지고 옴</li>
</ul>
</li>
<li>{index}<ul>
<li>현재 반복되어 지는 번호</li>
</ul>
</li>
<li>{arguments}<ul>
<li>전달되는 파라미터 값</li>
</ul>
</li>
<li>{0}, {1}, ...<ul>
<li>전달되는 파라미터 값의 인덱스 번호</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>    @DisplayName(&quot;Parameter 반복 테스트&quot;)
    @ParameterizedTest(name = &quot;{index} {displayName} stringVal = {0}&quot;)
    @ValueSource(strings = {&quot;11111111&quot;, &quot;22222222222&quot;, &quot;3333333333&quot;, &quot;444444444444&quot;})
    void stringValueSourceTest(String s) {
        System.out.println(s);

    }</code></pre><h1 id="3-인자parameter-값에-관련된-annotation">3. 인자(Parameter) 값에 관련된 Annotation</h1>
<ul>
<li>@ValueSource    <ul>
<li>지정한 배열을 파라미터 값으로 순서대로 넘겨줌</li>
<li>테스트 메소드 실행 당 하나의 인수(argument)만을 전달할 때 사용됨</li>
<li>리터럴 값의 배열을 테스트 메소드에 전달<ul>
<li>리터럴 값(literal vales)이란?<ul>
<li>변수에 넣는 변하지 않는 데이터를 의미</li>
<li>보통은 기본형의 데이터를 의미하지만, 특정 객체(Immutable class , VO class)에 한에서는 리터럴이 될 수 있음</li>
<li>short, byte, int, long, float, double, char, java.lang.String, java.lang.Class</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>@NullSource, @EmptySource, @NullAndEmptySource    <ul>
<li>@NullSource    <ul>
<li>null을 인자로 추가    </li>
</ul>
</li>
<li>@EmptySource    <ul>
<li>빈 문자열을 인자로 추가</li>
</ul>
</li>
<li>@NullAndEmptySource    <ul>
<li>null과 빈 문자열 모두를 인자로 추가</li>
</ul>
</li>
</ul>
</li>
<li>@CsvSource    <ul>
<li>기본 구분자로는 콤마(&#39;,&#39;)</li>
<li>delimiter 을 직접 정의함으로써 구분자를 지정</li>
</ul>
</li>
</ul>
<h1 id="4-인자-값-타입-변환">4. 인자 값 타입 변환</h1>
<ul>
<li><p>암묵적인 타입 변환    </p>
<ul>
<li>레퍼런스 참고 (<a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-argument-conversion-implicit">https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-argument-conversion-implicit</a>)</li>
</ul>
</li>
<li><p>명시적인 타입 변환    </p>
<ul>
<li><p>SimpleArgumentConverter 상속 받은 구현체 제공</p>
<ul>
<li><p>하나의 인자값만 가능</p>
</li>
<li><p>반드시 static inner class 거나 public class 여야 함</p>
</li>
<li><p>@ConvertWith 활용하여 구현한 class를 입력</p>
<pre><code>  @DisplayName(&quot;Custom(domain 객체) Parameter 반복 테스트&quot;)
  @ParameterizedTest(name = &quot;{index} {displayName} stringVal = {0}&quot;)
  @ValueSource(ints = {10, 20, 30})
  void convertValueSourceTest(@ConvertWith(StudyConverter.class) Study study) {
      System.out.println(study.getLimit());

  }

  // public 클래스 혹은 static 인너클래스로 작성해야함
  static class StudyConverter extends SimpleArgumentConverter {

      @Override
      protected Object convert(Object source, Class&lt;?&gt; targetType) throws ArgumentConversionException {
          assertEquals(Study.class, targetType, &quot;Can only convert to Study&quot;);
          return new Study(Integer.parseInt(source.toString()));
      }
  }</code></pre></li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="5-인자-값-조합">5. 인자 값 조합</h1>
<ul>
<li><p>ArgumentsAccessor    </p>
<ul>
<li><p>두개 이상의 인자값을 받을 수 있음</p>
<pre><code>@DisplayName(&quot;Parameter 반복 테스트&quot;)
@ParameterizedTest(name = &quot;{index} {displayName} stringVal = {0}&quot;)
@CsvSource({&quot;10, &#39;자바 스터디&#39;&quot;, &quot;20, &#39;스프링&#39;&quot;})
void convertCvsTest(ArgumentsAccessor argumentsAccessor) {
  Study study = new Study(argumentsAccessor.getInteger(0),
          argumentsAccessor.getString(1));
  System.out.println(study);

}</code></pre></li>
</ul>
</li>
<li><p>커스텀 Accessor    </p>
<ul>
<li><p>AgumentsAggregator 인터페이스 구현</p>
<ul>
<li><p>반드시 static inner class 거나 public class 여야 함</p>
</li>
<li><p>@AggregateWith 활용하여 구현한 class를 입력</p>
<pre><code>  @DisplayName(&quot;Parameter 반복 테스트&quot;)
  @ParameterizedTest(name = &quot;{index} {displayName} stringVal = {0}&quot;)
  @CsvSource({&quot;10, &#39;자바 스터디&#39;&quot;, &quot;20, &#39;스프링&#39;&quot;})
  void convertCvsTest(@AggregateWith(StudyArgregator.class) Study study) {
      System.out.println(study);

  }

  // public 클래스 혹은 static 인너클래스로 작성해야함
  static class StudyArgregator implements ArgumentsAggregator {
      @Override
      public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException {
          Study study = new Study(accessor.getInteger(0),
                  accessor.getString(1));
          return study;
      }
  }</code></pre></li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit 5 : @Tag 및 커스텀 태그]]></title>
            <link>https://velog.io/@young_209/JUnit-5-Tag-%EB%B0%8F-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%83%9C%EA%B7%B8</link>
            <guid>https://velog.io/@young_209/JUnit-5-Tag-%EB%B0%8F-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%83%9C%EA%B7%B8</guid>
            <pubDate>Thu, 23 Dec 2021 08:21:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-tag">1. @Tag</h1>
<ul>
<li><p>테스트 클래스, 메소드에 테스트 구분을 태깅하기 위해 사용</p>
<h2 id="소스">소스</h2>
<pre><code>  @DisplayName(&quot;스터디 만들기 fast&quot;)
  @Tag(&quot;fast&quot;)
  @Test
  void testTaging() {
      Study study = new Study(1);

      assertAll(
              () -&gt; assertNotNull(study),
              () -&gt; assertEquals(StudyStatus.DRAFT, study.getStatus(),
                      () -&gt; &quot;스터디를 처음 만들면 상태값이 &quot; + StudyStatus.DRAFT + &quot;이어야 한다.&quot;),
              () -&gt; assertTrue(study.getLimit() &gt; 0, &quot;스터디 최대 참석 가능 인원은 0보다 커야한다.&quot;)
      );
  }

  @DisplayName(&quot;스터디 만들기 slow&quot;)
  @Tag(&quot;slow&quot;)
  @Test
  void testTaging2() {
      Study study = new Study(1);

      assertAll(
              () -&gt; assertNotNull(study),
              () -&gt; assertEquals(StudyStatus.DRAFT, study.getStatus(),
                      () -&gt; &quot;스터디를 처음 만들면 상태값이 &quot; + StudyStatus.DRAFT + &quot;이어야 한다.&quot;),
              () -&gt; assertTrue(study.getLimit() &gt; 0, &quot;스터디 최대 참석 가능 인원은 0보다 커야한다.&quot;)
      );
  }</code></pre><h2 id="실행">실행</h2>
</li>
<li><p>원하는 @Tag만 테스트할려면 별도 설정 필요</p>
</li>
<li><p>Edit Configurations 클릭
<img src="https://images.velog.io/images/young_209/post/ba1d48ba-d75e-4bf1-8e20-18ffc1b81e58/image.png" alt=""></p>
</li>
<li><p>노란 영역부분 Tags로 변경 및 실행할 태그명 입력
<img src="https://images.velog.io/images/young_209/post/767e95c3-4a22-433b-82bf-baa512553bfc/image.png" alt=""></p>
</li>
</ul>
<h1 id="2-커스텀-태그">2. 커스텀 태그</h1>
<ul>
<li>JUnit 5 애노테이션을 조합하여 커스텀 태그를 만들 수 있다.<h2 id="커스텀-annotation-작성">커스텀 Annotation 작성</h2>
<img src="https://images.velog.io/images/young_209/post/ff17c12f-c56c-4876-8f00-b446758a469a/image.png" alt=""><pre><code>@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@Tag(&quot;fast&quot;)
public @interface FastTest {
}</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit 5 : 조건에 따라 테스트 실행하기]]></title>
            <link>https://velog.io/@young_209/JUnit-5-%EC%A1%B0%EA%B1%B4%EC%97%90-%EB%94%B0%EB%9D%BC-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@young_209/JUnit-5-%EC%A1%B0%EA%B1%B4%EC%97%90-%EB%94%B0%EB%9D%BC-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 23 Dec 2021 05:31:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-assumption">1. Assumption</h1>
<ul>
<li><p>org.junit.jupiter.api.Assumptions.*    </p>
<ul>
<li><p>assumeTrue(조건)</p>
<ul>
<li>파라미터의 값이 true일 때 테스트를 계속 진행하고, false일 때 <strong>테스트를 생략</strong>하는 것이지 실패하는 것은 아님 </li>
</ul>
</li>
<li><p>assumingThat(조건, 테스트)</p>
<ul>
<li><p>지정한 가정을 충족한 경우 지정한 검증을 수행</p>
<pre><code>@Test
void assumeTrueTest() {
String test_env = System.getenv(&quot;TEST_ENV&quot;);
System.out.println(test_env);
assumeTrue(&quot;LOCAL&quot;.equalsIgnoreCase(test_env));

Study study = new Study(10);
assertThat(study.getLimit()).isGreaterThan(0);
}
</code></pre></li>
</ul>
<p>@Test
void assumeThatTest() {
  String test_env = System.getenv(&quot;TEST_ENV&quot;);</p>
<p>  assumingThat(&quot;LOCAL&quot;.equalsIgnoreCase(test_env),</p>
<pre><code>      () -&gt; {
          System.out.println(&quot;local&quot;);
          Study study = new Study(10);
          assertThat(study.getLimit()).isGreaterThan(0);
      });</code></pre><p>  assumingThat(&quot;DEV&quot;.equalsIgnoreCase(test_env),</p>
<pre><code>      () -&gt; {
          System.out.println(&quot;dev&quot;);
          Study study = new Study(20);
          assertThat(study.getLimit()).isGreaterThan(0);
      });</code></pre><p>}</p>
<pre><code></code></pre></li>
</ul>
</li>
</ul>
<h1 id="2-enabled____-와-disabled____">2. @Enabled____ 와 @Disabled____</h1>
<ul>
<li>OS 따라 테스트 실행    <ul>
<li>@EnabledOnOs(OS조건) / @DisabledOnOs(OS조건)</li>
</ul>
</li>
<li>자바 환경변수에 따라 실행    <ul>
<li>@EnabledOnJre(JRE.JAVA_8) / @EnabledOnJre(JRE.JAVA_8)</li>
</ul>
</li>
</ul>
<pre><code>    @Test
    @EnabledOnOs(OS.WINDOWS)
    void enabledOnOsTest() {
        System.out.println(&quot;EnabledOnOs&quot;);
    }

    @Test
    @DisabledOnOs(OS.WINDOWS)
    void disabledOnOsTest() {
        System.out.println(&quot;DisabledOnOs&quot;);
    }

    @Test
    @EnabledOnJre(JRE.JAVA_8)
    void enabledOnJreTest() {
        System.out.println(&quot;JAVA_8&quot;);
    }

    @Test
    @EnabledOnJre(JRE.OTHER)
    void disabledOnJreTest() {
        System.out.println(&quot;JRE_OTHER&quot;);
    }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit 5 : Assertion]]></title>
            <link>https://velog.io/@young_209/JUnit-5-Assertion</link>
            <guid>https://velog.io/@young_209/JUnit-5-Assertion</guid>
            <pubDate>Thu, 23 Dec 2021 02:18:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-assertion">1. Assertion</h1>
<ul>
<li>값 검증을 위한 assert로 시작하는 static(정적) 메서드를 제공하고 있음</li>
<li>JUnit4로부터 온 assertion 메소드와 새롭게 자바 8 람다 표현식으로 추가된 메소드들이 있음</li>
<li>assertEqulas(expected, actual)<ul>
<li>실제 값이 기대한 값과 같은지 확인</li>
</ul>
</li>
<li>assertNotNull(actual)<ul>
<li>값이 null이 아닌지 확인</li>
</ul>
</li>
<li>assertTrue(boolean)<ul>
<li>다음 조건이 참(true)인지 확인</li>
</ul>
</li>
<li>assertAll(executables...)<ul>
<li>모든 확인 구문 확인</li>
<li>함수형인터페이스인 Executable 목록을 파라미터로 갖음<ul>
<li>람다식을 사용해서 여러개의 검증을 목록으로 전달 할 수 있음</li>
</ul>
</li>
</ul>
</li>
<li>assertThrows(expectedType, executable)<ul>
<li>예외 발생 확인</li>
</ul>
</li>
<li>assertTimeout(duration, executable)<ul>
<li>특정 시간 안에 실행이 완료되는지 확인</li>
</ul>
</li>
</ul>
<pre><code>@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @DisplayName(&quot;스터디 만들기&quot;)
    @Test
    void assertAllTest() {
        Study study = new Study(-1);

        assertAll(
                () -&gt; assertNotNull(study),
                () -&gt; assertEquals(StudyStatus.DRAFT, study.getStatus(),
                        () -&gt; &quot;스터디를 처음 만들면 상태값이 &quot; + StudyStatus.DRAFT + &quot;이어야 한다.&quot;),
                () -&gt; assertTrue(study.getLimit() &gt; 0, &quot;스터디 최대 참석 가능 인원은 0보다 커야한다.&quot;)
        );
    }

    @Test
    void assertThrowsTest() {
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -&gt; new Study(-10));
        String message = exception.getMessage();
        assertEquals(&quot;limit은 0보다 커야 한다.&quot;, message);
    }

    @Test
    void assertTimeoutTest() {
//        assertTimeout(Duration.ofMillis(10), () -&gt; new Study(-10));
        assertTimeout(Duration.ofMillis(10), () -&gt; {
            new Study(10);
            Thread.sleep(100);
        });

//        정해진 시간이 됬을 때 바로 끝남
//        Thead관련 문제가 발생 할 수 있으므로 사용에 주의의
//       assertTimeoutPreemptively(Duration.ofMillis(100), () -&gt; {
//            new Study(10);
//            Thread.sleep(200);
//        });
    }

    @BeforeAll
    static void beforeAll() {
        System.out.println(&quot;BeforeAll!&quot;);
    }

    @AfterAll
    static void afterAll() {
        System.out.println(&quot;AfterAll!&quot;);
    }

    @BeforeEach
    void setUp() {
        System.out.println(&quot;BeforeEach!&quot;);
    }

    @AfterEach
    void tearDown() {
        System.out.println(&quot;AfterEach!&quot;);
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JUnit 5 : 소개 및 시작]]></title>
            <link>https://velog.io/@young_209/JUnit-5-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@young_209/JUnit-5-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Thu, 23 Dec 2021 02:09:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>인프런의 &quot;더 자바, 애플리케이션을 테스트하는 다양한 방법”을 보고 정리한 것임</strong></p>
</blockquote>
<h1 id="1-소개-및-시작">1. 소개 및 시작</h1>
<h2 id="11-소개">1.1. 소개</h2>
<ul>
<li>테스팅 프레임워크</li>
<li>자바 8 이상을 필요함</li>
<li>이전 JUnit 버전과 다르게, JUnt5는 세개의 서브 프로젝트로 이루어져 있음    <ul>
<li>JUnit5은 JUnit Platform + JUnit Jupiter + JUnit Vintage</li>
<li>JUnit Platform<ul>
<li>JVM에서 테스트 프레임워크를 실행하는데 기초를 제공</li>
<li>TestEngine API를 제공해 테스트 프레임워크를 개발할 수 있음</li>
</ul>
</li>
<li>JUnit Jupiter<ul>
<li>JUnit 5에서 테스트를 작성하고 확장을 하기 위한 새로운 프로그래밍 모델과 확장 모델의 조합</li>
</ul>
</li>
<li>JUnit Vintage<ul>
<li>하위 호환성을 위해 JUnit3과 JUnt4를 기반으로 돌아가는 플랫폼에 테스트 엔진을 제공</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="12-시작">1.2. 시작</h2>
<h3 id="의존성-추가">의존성 추가</h3>
<ul>
<li>2.2+ 버전의 스프링 부트 프로젝트를 만든다면 기본으로 의존성 추가되어 있음 </li>
<li>maven<pre><code>  &lt;dependency&gt;
      &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;
      &lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;
      &lt;version&gt;5.5.2&lt;/version&gt;
      &lt;scope&gt;test&lt;/scope&gt;
  &lt;/dependency&gt;</code></pre></li>
</ul>
<h3 id="기본-애노테이션">기본 애노테이션</h3>
<ul>
<li><p>@Test</p>
<ul>
<li>테스트 클래스나 테스트 메소드에 이름을 붙여줄 때 사용</li>
</ul>
</li>
<li><p>@BeforeAll</p>
<ul>
<li>테스트가 시작하기 전 딱 한 번만 실행</li>
</ul>
</li>
<li><p>@AfterAll</p>
<ul>
<li>테스트가 완전히 끝난 후 딱 한 번만 실행</li>
</ul>
</li>
<li><p>@BeforeEach </p>
<ul>
<li>각각 테스트 메소드가 실행되기전에 실행되어야 하는 메소드를 명시해 줌</li>
<li>목업 데이터를 미리 세팅해주기 위해 주로 사용</li>
</ul>
</li>
<li><p>@AfterEach</p>
<ul>
<li>@Test , @RepeatedTest , @ParameterizedTest , @TestFactory 가 붙은 테스트 메소드가 실행되고 난 후 실행</li>
</ul>
</li>
<li><p>@Disabled</p>
<ul>
<li>테스트 클래스나, 메소드의 테스트를 비활성화 함</li>
</ul>
</li>
<li><p>@DisplayNameGeneration     </p>
<ul>
<li>Method와 Class 레퍼런스를 사용해서 테스트 이름을 표기하는 방법 설정</li>
<li>기본 구현체로 ReplaceUnderscores 제공</li>
</ul>
</li>
</ul>
<ul>
<li>@DisplayName     <ul>
<li>어떤 테스트인지 테스트 이름을 보다 쉽게 표현할 수 있는 방법을 제공하는 애노테이션</li>
<li>@DisplayNameGeneration 보다 우선 순위가 높다.</li>
</ul>
</li>
</ul>
<pre><code>@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class TestStudyTest {

    @DisplayName(&quot;스터디 만들기&quot;)
    @Test
    void create_new_study() {
        TestStudy testStudy = new TestStudy();
        assertNotNull(testStudy);
        System.out.println(&quot;create!&quot;);
    }

    @Test
    void create_new_study_again() {
        TestStudy testStudy = new TestStudy();
        assertNotNull(testStudy);
        System.out.println(&quot;create2!&quot;);
    }

    @BeforeAll
    static void beforeAll() {
        System.out.println(&quot;BeforeAll!&quot;);
    }

    @AfterAll
    static void afterAll() {
        System.out.println(&quot;AfterAll!&quot;);
    }

    @BeforeEach
    void setUp() {
        System.out.println(&quot;BeforeEach!&quot;);
    }

    @AfterEach
    void tearDown() {
        System.out.println(&quot;AfterEach!&quot;);
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 로그인 연동]]></title>
            <link>https://velog.io/@young_209/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@young_209/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Fri, 03 Dec 2021 14:32:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>본 포스팅은 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 보고 작성하였음</strong></p>
</blockquote>
<h1 id="1-네이버-로그인-연동">1. 네이버 로그인 연동</h1>
<h2 id="11-네이버-api-등록">1.1 네이버 API 등록</h2>
<ul>
<li><a href="https://developers.naver.com/apps/#/register?api=nvlogin">https://developers.naver.com/apps/#/register?api=nvlogin</a>
<img src="https://images.velog.io/images/young_209/post/6b110531-3080-44d9-9413-a09dbfe6b305/image.png" alt="">
<img src="https://images.velog.io/images/young_209/post/b3ddee6e-7e6a-4b3f-83a4-5a88f5aeea95/image.png" alt=""></li>
<li>등록 완료하면 Client ID와 Client Secret가 생성됨</li>
</ul>
<h2 id="12-네이버">1.2 네이버</h2>
<blockquote>
<p>네이버에서는 스프링 시큐리티를 공식 지원하지 않기 때문에 수동 입력이 필요</p>
</blockquote>
<h3 id="121-application-oauthproperties에-등록">1.2.1 application-oauth.properties에 등록</h3>
<pre><code># registration
spring.security.oauth2.client.registration.naver.client-id=클라이언트ID
spring.security.oauth2.client.registration.naver.client-secret=클라이언트 PW
spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.scope=name,email,profile_image
spring.security.oauth2.client.registration.naver.client-name=Naver

# provider
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response ⓐ</code></pre><ul>
<li>ⓐ user-name-attribute=response    <ul>
<li>기준이 되는 user_name의 이름을 네이버에서는 response로 해야함</li>
<li>네이버의 회원 조회 시 반환되는 JSON 형태 때문임</li>
</ul>
</li>
</ul>
<h3 id="122-스프링-시큐리티-설정-등록">1.2.2 스프링 시큐리티 설정 등록</h3>
<h4 id="oauthattributes에-네이버-설정-추가">OAuthAttributes에 네이버 설정 추가</h4>
<pre><code>@Getter
public class OAuthAttributes {

    ---

    public static OAuthAttributes of(String registrationId,
                                     String userNameAttributeName,
                                     Map&lt;String, Object&gt; attributes) {

        if (&quot;naver&quot;.equals(registrationId)) {
            return ofNaver(&quot;id&quot;, attributes);
        }
        return ofGoogle(userNameAttributeName, attributes);

    }

    ---

    private static OAuthAttributes ofNaver(String userNameAttributeName,
                                            Map&lt;String, Object&gt; attributes) {
        return OAuthAttributes.builder()
                .name((String) attributes.get(&quot;name&quot;))
                .email((String) attributes.get(&quot;email&quot;))
                .picture((String) attributes.get(&quot;profileImage&quot;))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build()
                ;
    }

    ---

}</code></pre><h4 id="indexmustache에-네이버-로그인-버튼-추가">index.mustache에 네이버 로그인 버튼 추가</h4>
<pre><code>    ---

            {{^userName}}
                &lt;a href=&quot;/oauth2/authorization/google&quot; class=&quot;btn btn-info active&quot; role=&quot;button&quot;&gt;Google Login&lt;/a&gt;
                &lt;a href=&quot;/oauth2/authorization/naver&quot; class=&quot;btn btn-info active&quot; role=&quot;button&quot;&gt;Naver Login&lt;/a&gt; ⓐ
            {{/userName}}
    ---</code></pre><ul>
<li>ⓐ /oauth2/authorization/naver    <ul>
<li>네이버 로그인 URL은 application-oauth.properties에 등록한 redirect-uri 값에 맞춰 자동으로 등록됨</li>
<li>/oauth2/authorization/ 까지는 고정, 마지막 Path만 각 소셜 로그인 코드를 사용하면 됨</li>
</ul>
</li>
</ul>
<h4 id="네이버-로그인-확인">네이버 로그인 확인</h4>
<p><img src="https://images.velog.io/images/young_209/post/be010865-7add-4d18-b4b3-a3b6f7301f28/image.png" alt="">
<img src="https://images.velog.io/images/young_209/post/5e63cec0-5339-4307-8018-162990d78301/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개선 하기(어노테이션 기반 & 세션 저장소로 DB 사용)]]></title>
            <link>https://velog.io/@young_209/%EA%B0%9C%EC%84%A0-%ED%95%98%EA%B8%B0%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EA%B8%B0%EB%B0%98-%EC%84%B8%EC%85%98-%EC%A0%80%EC%9E%A5%EC%86%8C%EB%A1%9C-DB-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@young_209/%EA%B0%9C%EC%84%A0-%ED%95%98%EA%B8%B0%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EA%B8%B0%EB%B0%98-%EC%84%B8%EC%85%98-%EC%A0%80%EC%9E%A5%EC%86%8C%EB%A1%9C-DB-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Thu, 02 Dec 2021 05:17:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>본 포스팅은 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 보고 작성하였음</strong></p>
</blockquote>
<h1 id="1-어노테이션-기반">1. 어노테이션 기반</h1>
<blockquote>
<p>같은 코드가 계속해서 반복되는 부분 -&gt; 어노테이션 기반으로 변경</p>
<ul>
<li>ex) IndexController에서 세션 값을 가져오는 부분을 메소드 인자로 세션값을 바로 받을 수 있도록 변경</li>
</ul>
</blockquote>
<h2 id="11-loginuser-어노테이션-생성">1.1 @LoginUser 어노테이션 생성</h2>
<pre><code>@Target(ElementType.PARAMETER) ⓐ
@Retention(RetentionPolicy.RUNTIME) ⓑ
public @interface LoginUser { ⓒ
}</code></pre><ul>
<li><p>ⓐ @Target(ElementType.PARAMETER)     </p>
<ul>
<li>해당 어노테이션이 생성될 수 있는 위치를 지정함</li>
<li>PARAMETER로 지정했으니 메소드의 파라미터로 선언된 객체에서만 사용 할 수 있음<ul>
<li>ElementType.PACKAGE : 패키지 선언<ul>
<li>ElementType.TYPE : 타입 선언</li>
<li>ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언</li>
<li>ElementType.CONSTRUCTOR : 생성자 선언</li>
<li>ElementType.FIELD : 멤버 변수 선언</li>
<li>ElementType.LOCAL_VARIABLE : 지역 변수 선언</li>
<li>ElementType.METHOD : 메서드 선언</li>
<li>ElementType.PARAMETER : 전달인자 선언</li>
<li>ElementType.TYPE_PARAMETER : 전달인자 타입 선언</li>
<li>ElementType.TYPE_USE : 타입 선언</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>ⓑ @Retention(RetentionPolicy.RUNTIME) </p>
<ul>
<li>Annotation 이 실제로 적용되고 유지되는 범위를 의미</li>
<li>RetentionPolicy.RUNTIME<ul>
<li>컴파일 이후에도 JVM 에 의해서 계속 참조가 가능<ul>
<li>주로 리플렉션이나 로깅에 많이 사용됨</li>
</ul>
</li>
</ul>
</li>
<li>RetentionPolicy.CLASS <ul>
<li>컴파일러가 클래스를 참조할 때가지 유효함</li>
</ul>
</li>
<li>RetentionPolicy.SOURCE <ul>
<li>컴파일 전까지만 유효함<ul>
<li>즉, 컴파일 이후에는 사라지게 됨</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>ⓒ @interface </p>
<ul>
<li>해당 파일을 어노테이션 클래스로 지정(생성)</li>
</ul>
</li>
</ul>
<h2 id="12-loginuserargumentresolver">1.2 LoginUserArgumentResolver</h2>
<pre><code>@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

    private final HttpSession httpSession;

    @Override
    public boolean supportsParameter(MethodParameter parameter) { ⓐ
        boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
        boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());

        return isLoginUserAnnotation &amp;&amp; isUserClass;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { ⓑ
        return httpSession.getAttribute(&quot;user&quot;);
    }
}</code></pre><ul>
<li>ⓐ supportsParameter()<ul>
<li>컨트롤러 메서드의 특정 파라미터를 지원하는지 판단함</li>
<li>상기 매서드에서는 파라미터에 @LoginUser 어노테이션이 붙어 있고, 파라미터 클래스 타입이 SessionUser.class인 경우 true를 반환함</li>
</ul>
</li>
<li>ⓑ resolveArgument()<ul>
<li>파라미터에 전달할 객체를 생성함</li>
<li>상기 소스에서는 세션에서 객체를 가져옴</li>
</ul>
</li>
</ul>
<h2 id="13-indexcontroller-개선">1.3 IndexController 개선</h2>
<pre><code>@RequiredArgsConstructor
@Controller
public class IndexController {

    private final PostsService postsService;

    @GetMapping(&quot;/&quot;)
    public String index(Model model, @LoginUser SessionUser user) { ⓐ
        model.addAttribute(&quot;posts&quot;, postsService.findAllDesc());

        if (user != null) {
            model.addAttribute(&quot;userName&quot;, user.getName());
        }
        return &quot;index&quot;;
    }
}</code></pre><ul>
<li>ⓐ @LoginUser SessionUser user<ul>
<li>기존 SessionUser user = (SessionUser) httpSession.getAttribute(&quot;user&quot;);로 가져오던 세션 정보 값이 개선 됨</li>
<li>해당 커스텀 어노테션을 사용하면 세션 정보를 가져올 수 있게 됨</li>
</ul>
</li>
</ul>
<h1 id="2-세션-저장소로-데이터베이스-사용하기">2. 세션 저장소로 데이터베이스 사용하기</h1>
<h2 id="21-세션session">2.1 세션(Session)?</h2>
<ul>
<li>클라이언트와 웹서버 간 네트워크 연결이 지속 유지되고 있는 상태를 말함</li>
<li>즉, 사용자가 브라우저를 열어 서버에 접속한 뒤 접속을 종료할 때가지의 시점을 이야기함</li>
<li>HTTP 프로토콜은 비접속형 프로토콜이므로, 매 접속마다 새로운 네트워크 연결이 이루어는데 세션이 연결 유지를 가능하게 함</li>
<li>정보들이 서버단에 저장되기 때문에 보안 면에서 쿠키보다 우수함</li>
</ul>
<h2 id="22-다중-서버-환경에서-세션관리-방법">2.2 다중 서버 환경에서 세션관리 방법</h2>
<p>(참조 : <a href="https://hyuntaeknote.tistory.com/6">https://hyuntaeknote.tistory.com/6</a>)</p>
<blockquote>
<ul>
<li>Sticky Session (Load Balancer)</li>
<li>Session Clustering (TOMCAT 세션)</li>
<li>Session Storage<ul>
<li>Disk Based Database (MySQL, Oracle, MS-SQL 등과 같은 관계형 데이터베이스)</li>
<li>In-Memory Database (Redis, Memcached 등)</li>
</ul>
</li>
</ul>
</blockquote>
<h3 id="221-sticky-session">2.2.1 Sticky Session</h3>
<ul>
<li>고정된 세션</li>
<li>Load Balance가 기본적으로 라운드 로빈 방식으로 트래픽을 분산</li>
<li>특정 사용자가 접속을 시도했을 때 처음 접속된 서버로 계속해서 접속되도록 트래픽을 처리하는 방식</li>
<li>장점    <ul>
<li>사용자는 세션이 유지되는 동안 동일한 서버만을 사용</li>
<li>정합성 이슈에서 자유로움</li>
</ul>
</li>
<li>단점    <ul>
<li>사용자가 접속해야하는 서버가 정해져 있기 때문에 특정 서버에 트래픽이 집중될 위험이 있음</li>
<li>사용자가 자신의 세션이 없는 다른서버 이용불가</li>
<li>가용성이 떨어짐</li>
</ul>
</li>
</ul>
<p><img src="https://images.velog.io/images/young_209/post/551c4ed4-3e6f-453b-b35e-441b24196577/img.png" alt=""></p>
<h3 id="222-session-clustering세션-클러스터링">2.2.2 Session Clustering(세션 클러스터링)</h3>
<ul>
<li>여러 대의 컴퓨터들이 하나의 시스템 처럼 동작하도록 만드는 것</li>
<li>WAS가 2대 이상 설치 형태일 때 동일한 세션으로 관리하는 것을 의미</li>
<li>장점<ul>
<li>세션을 복제하여 사용자가 어떤 서버에 접속하더라도 <strong>데이터가 세션에 복제됨으로써 정합성 이슈 해결</strong></li>
<li>서버 하나에 장애가 발생하더라도 서비스는 중단되지 않고 운영 가능</li>
</ul>
</li>
<li>단점<ul>
<li>모든 서버가 동일한 세션 객체를 가져야하기 때문에 많은 메모리가 필요</li>
<li>세션 저장소에 데이터가 저장될 때마다 모든 서버에 값을 입력해야함</li>
<li>서버 수에 비례하여 네트워크 트래픽이 증가하는 등 성능저하가 발생
<img src="https://images.velog.io/images/young_209/post/4197aaeb-f07f-4a1d-a000-d0c30b72feb4/img%20(1).png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="223-session-storage세션-저장소">2.2.3 Session Storage(세션 저장소)</h3>
<ul>
<li>서버가 아무리 늘어나도 세션 스토리지에 대한 정보만 각각의 서버에 입력해주면 세션을 공유할 수 있게됨</li>
<li>트래픽이 비정상적으로 몰리는 현상을 고려하지 않아도 됨</li>
<li>서버가 하나 장애가 발생하더라도 별도의 세션 저장소가 존재하기 때문에 서비스를 계속해서 제공할 수 있음 (<strong>가용성을 확보할 수 있음</strong>)</li>
<li>여러대의 서버가 하나의 세션을 사용하기 때문에 데이터 불일치가 발생하지 않음 (<strong>정합성 문제 해결</strong>)</li>
<li>별도의 세션 복제를 할 필요 없기에 성능적인 문제도 해결이 가능</li>
<li>But, 세션 저장소 <strong>서버 장애를 방지</strong>하기 위해 동일한 세션 저장소 하나를 더 구성하여 <strong>복제</strong>하는것이 좋음</li>
</ul>
<ol>
<li>Disk Based Database<ul>
<li>디스크에 저장 및 사용</li>
<li>MySQL, Oracle, MS-SQL 등과 같은 관계형 데이터베이스</li>
<li>세션 저장소를 할 수 있는 가장 쉬운방법</li>
<li>많은 설정 필요 없음</li>
<li>처리 속도가 오래걸림, DB I/O가 발생하여 성능상 이슈가 발생할 수 있음</li>
<li>빈번한 Read/Write가 이루어지는 세션 저장소로써 Disk 기반의 데이터베이스는 상대적으로 I/O 속도가 느리기 때문에 적합하지 않음</li>
<li>보통 로그인 요청이 많이 없는 백오피스, 사내 시스템 용도에서 많이 사용</li>
</ul>
</li>
</ol>
<p><img src="https://images.velog.io/images/young_209/post/2f78e31a-5ffe-4256-b2c4-8f1f03c230ac/img%20(2).png" alt=""></p>
<ol start="2">
<li>In-Memory Database<ul>
<li>메모리에 저장 및 사용</li>
<li>Redis, Memcached 등이 있음</li>
<li>세션에 저장하는 데이터들은 영구적으로 저장하는 데이터가 아님</li>
<li>In-Memory 데이터베이스는 전원 공급이 중단되면 데이터를 잃어버리지만, 세션 저장소에 저장되는 데이터는 상대적으로 피해가 적기 때문에 In-Memory 데이터베이스 사용이 적합합니다. (일부 데이터베이스는 Replication을 지원하기 때문에 가용성을 확보할 수 있습니다.)</li>
<li>데이터를 메모리에서 Read/Write 할 수 있다는 점에서 빠른 속도로 데이터를 처리할 수 있기 때문에 세션 저장소로써 적합</li>
</ul>
</li>
</ol>
<p><img src="https://images.velog.io/images/young_209/post/7bf298c6-c523-480f-910f-b1ea3f70bad4/img%20(3).png" alt="">    </p>
<h2 id="23-세션-저장소로-데이터베이스disk-based-database-설정">2.3 세션 저장소로 데이터베이스(Disk Based Database) 설정</h2>
<ul>
<li>gradle에 의존성 추가    <ul>
<li>implementation &#39;org.springframework.session:spring-session-jdbc&#39;</li>
</ul>
</li>
<li>application.properties에 추가    <ul>
<li>spring.session.store-type=jdbc</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[구글 로그인 연동 및 스프링 시큐리티 설정]]></title>
            <link>https://velog.io/@young_209/%EA%B5%AC%EA%B8%80-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@young_209/%EA%B5%AC%EA%B8%80-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%B0%EB%8F%99-%EB%B0%8F-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sat, 27 Nov 2021 14:19:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>본 포스팅은 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 보고 작성하였음</strong></p>
</blockquote>
<h1 id="1-도메인-user-클래스-생성">1. 도메인 User 클래스 생성</h1>
<h3 id="user">User</h3>
<pre><code>import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@NoArgsConstructor
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    @Column
    private String picture;

    @Enumerated(EnumType.STRING) ⓐ
    @Column(nullable = false)
    private Role role;

    @Builder
    public User(String name, String email, String picture, Role role) {
        this.name = name;
        this.email = email;
        this.picture = picture;
        this.role = role;
    }

    public User update(String name, String picture) {
        this.name = name;
        this.picture = picture;

        return this;
    }

    public String getRoleKey() {
        return this.role.getKey();
    }
}</code></pre><ul>
<li><p>ⓐ @Enumerated(EnumType.STRING)</p>
<ul>
<li>JPA로 데이터베이스로 저장할 때 Enum 값을 어떤 형대로 저장할지를 결정함</li>
<li>기본적으로 int로 된 숫자가 저장됨</li>
<li>숫자로 저장되면 데이터베이스로 확인할 때 그 값이 무슨 코드를 의미하는지 알 수가 없음, 그래서 <strong><em>문자열(EnumType.STRING)로 저장될 수 있도록 선언</em></strong>함</li>
</ul>
</li>
</ul>
<h3 id="role">Role</h3>
<ul>
<li>사용자의 권한을 관리할 Enum 클래스<pre><code>import lombok.Getter;
import lombok.RequiredArgsConstructor;
</code></pre></li>
</ul>
<p>@Getter
@RequiredArgsConstructor
public enum Role {</p>
<pre><code>GUEST(&quot;ROLE_GUEST&quot;, &quot;손님&quot;),
USER(&quot;ROLE_USER&quot;, &quot;일반 사용자&quot;);

private final String key;
private final String title;</code></pre><p>}</p>
<pre><code>- 스프링 시큐리티에서는 권한 코드에 ***항상 ROLE_이 앞에 있어야만*** 함

### UserRepository</code></pre><p>import org.springframework.data.jpa.repository.JpaRepository;</p>
<p>import java.util.Optional;</p>
<p>public interface UserRepository extends JpaRepository&lt;User, Long&gt; {</p>
<pre><code>Optional&lt;User&gt; findByEmail(String email);</code></pre><p>}</p>
<pre><code>
# 2. 스프링 시큐리티 설정
## 2.1 build.gradle 의존성 추가</code></pre><p>implementation &#39;org.springframework.boot:spring-boot-starter-oauth2-client&#39;</p>
<pre><code>- spring-boot-starter-oauth2-client

    - 소셜 로그인 등 클라이언트 입장에서 소셜 기능 구현 시 필요한 의존성임
    - spring-security-oauth2-client와 spring-security-oauth2-jose를 기본으로 관리해줌

## 2.2 config.auth 패키지 생성
- 시큐리티 관련 클래스
### 2.2.1 SecurithConfig</code></pre><p>import com.study.aws.studyspringbootaws.domain.user.Role;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;</p>
<p>@RequiredArgsConstructor
@EnableWebSecurity ⓐ
public class SecurityConfig extends WebSecurityConfigurerAdapter {</p>
<pre><code>private final CustomOAuth2UserService customOAuth2UserService;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .headers().frameOptions().disable() ⓑ
            .and()
                .authorizeRequests() ⓒ
                .antMatchers(&quot;/&quot;, &quot;/css/**&quot;, &quot;/images/**&quot;, &quot;/js/**&quot;, &quot;/h2-console/**&quot;)
                    .permitAll()
                .antMatchers(&quot;/api/v1/**&quot;) ⓓ
                    .hasRole(Role.USER.name())
                .anyRequest() ⓔ
                    .authenticated()
            .and()
                .logout()
                    .logoutSuccessUrl(&quot;/&quot;) ⓕ
            .and()
                .oauth2Login() ⓖ
                    .userInfoEndpoint() ⓗ
                        .userService(customOAuth2UserService) ⓘ
            ;
}</code></pre><p>}</p>
<pre><code>- ⓐ @EnableWebSecurity     
    - Spring Security 설정들을 활성화시켜 줌
- ⓑ csrf().disable().headers().frameOptions().disable()     
    - h2-console 화면을 사용하기 위해 해당 옵션들을 disable 처리함
- ⓒ authorizeRequests()      
    - URL별 권한 관리를 설정하는 옵션의 시작점
    - authorizeRequests가 선언되어야만 antMatchers 옵션을 사용할 수 있음
- ⓓ antMatchers(&quot;/api/v1/**&quot;)     
    - 권한 관리 대상을 지정하는 옵션임
    - URL, HTTP 메소드별로 관리가 가능함
    - permitAll() 옵션을 통해 전체 권한을 줌
    - hasRole() 옵션을 통해 해당 권한이 있는 사용자에게 접근 가능하도록 함
- ⓔ anyRequest()      
    - 설정된 값들 이외의 나머지 URL들을 나타냄
    - authenticated()를 추가하여 나머지 URL들을 모두 인증된 사용자들에게 허용하게 함
    - 인증된 사용자 즉, 로그인한 사용자들을 이야기함
- ⓕ logout().logoutSuccessUrl(&quot;/&quot;) 
    - 로그아웃 기능에 대한 여러 설정 진입점임
    - 로그아웃 성공시 &quot;/&quot; 주소로 이동한다는 의미
- ⓖ oauth2Login() 
    - OAuth 2 로그인 기능에 대한 여려 설정의 진입점임
- ⓗ userInfoEndpoint()  
    - OAuth 2 로그인 성공 이후 사용자 정보를 가져올 때의 설정들을 담당함
- ⓘ userService(특정 UserService) 
    - 소셜 로그인 성공 시 후속 조치를 진행할 UserService 인터페이스의 구현체를 등록함
    - 리소스 서버(즉, 소셜 서비스들)에서 사용자 정보를 가져온 상태에서 추가로 진행하고자 하는 기능을 명시 할 수 있음

### 2.2.2 CustomOAuth2UserService
- 구글 로그인 이후 가져온 사용자의 정보들을 기반으로 가입 및 정보 수정, 세션 저장 등의 기능을 지원함
</code></pre><p>@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService&lt;OAuth2UserRequest, OAuth2User&gt; {</p>
<pre><code>private final UserRepository userRepository;
private final HttpSession httpSession;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
    OAuth2UserService delegate = new DefaultOAuth2UserService();
    OAuth2User oAuth2User = delegate.loadUser(userRequest);

    String registrationId = userRequest.getClientRegistration().getRegistrationId(); ⓐ
    String userNameAttributeName =
            userRequest.getClientRegistration()
                    .getProviderDetails()
                    .getUserInfoEndpoint()
                    .getUserNameAttributeName(); ⓑ

    OAuthAttributes attributes = OAuthAttributes.of(registrationId,
            userNameAttributeName,
            oAuth2User.getAttributes()); ⓒ

    User user = saveOrUpdate(attributes);

    httpSession.setAttribute(&quot;user&quot;, new SessionUser(user)); ⓓ

    return new DefaultOAuth2User(
            Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
            attributes.getAttributes(),
            attributes.getNameAttributeKey()
    );
}

private User saveOrUpdate(OAuthAttributes attributes) {
    User user = userRepository.findByEmail(attributes.getEmail())
            .map(entity -&gt; entity.update(attributes.getName(),
                    attributes.getPicture()))
            .orElse(attributes.toEntity());

    return userRepository.save(user);
}</code></pre><p>}</p>
<pre><code>- ⓐ registrationId     
    - 현재 로그인 진행 중인 서비스를 구분하는 코드
    - 현재 상기 코드에서는 구글만 사용하므로 불필요한 값이지만, 기타 다른 로그인 연동 시 다른 서비스인지를 구분하기위해 사용함(ex. 네이버 로그인인지, 구글인지, 카카오인지 확인하기 위한 ID 값)
- ⓑ userNameAttributeName     
    - OAuth2 로그인 진행 시 키가 되는 필드값을 이야기 함 (Primary Key와 같은 의미라 보면 됨)
    - 구글의 경우 기본적으로 코드를 지원하지만, 네이버 카카오 등은 기본으로 지원하지 않음! 구글의 기본 코드는 &quot;sub&quot;임
    - 이후 네이버 로그인과 구글 로그인을 동시 지원할 때 사용할 예정
- ⓒ OAuthAttributes      
    - OAuth2UserService를 통해 가져온 OAuth2User의 attribute를 담을 클래스임
    - 이후 네이버 등 다른 소셜 로그인도 이 클래스를 사용함
- ⓓ SessionUser
    - 세션에 사용자 정보를 저장하기 위한 DTO 클래스임

### 2.2.3 OAuthAttributes
- DTO 패키지를 만들어 클래스 생성</code></pre><p>@Getter
public class OAuthAttributes {</p>
<pre><code>private Map&lt;String, Object&gt; attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;

@Builder
public OAuthAttributes(Map&lt;String, Object&gt; attributes,
                       String nameAttributeKey, String name,
                       String email, String picture) {
    this.attributes = attributes;
    this.nameAttributeKey = nameAttributeKey;
    this.name = name;
    this.email = email;
    this.picture = picture;
}

public static OAuthAttributes of(String registrationId,
                                 String userNameAttributeName,
                                 Map&lt;String, Object&gt; attributes) { ⓐ
    return ofGoogle(userNameAttributeName, attributes);

}

private static OAuthAttributes ofGoogle(String userNameAttributeName,
                                        Map&lt;String, Object&gt; attributes) { 
    return OAuthAttributes.builder()
            .name((String) attributes.get(&quot;name&quot;))
            .email((String) attributes.get(&quot;email&quot;))
            .picture((String) attributes.get(&quot;picture&quot;))
            .attributes(attributes)
            .nameAttributeKey(userNameAttributeName)
            .build()
            ;
}

public User toEntity() { ⓑ
    return User.builder()
            .name(name)
            .email(email)
            .picture(picture)
            .role(Role.GUEST)
            .build();
}</code></pre><p>}</p>
<pre><code>- ⓐ of()     
    - OAuth2User에서 반환하는 사용자 정보는 Map이기 때문에 값 하나하나를 변환해야함 함
- ⓑ toEntity()     
    - User 엔티티를 생성함
    - OAuthAttributes에서 엔티티를 생성하는 시점은 처음 가입할 때임
    - 가입할 때의 기본 권한을 GUEST로 주기 위해서 role 빌더값에는 Role.GUEST 값을 주었음
    - OAuthAttributes 클래스 생성이 끝났으면 같은 패키지에 SessionUser 클래스를 생성함

### 2.2.4 SessionUser</code></pre><p>@Getter
public class SessionUser implements Serializable {</p>
<pre><code>private String name;
private String email;
private String picture;

public SessionUser(User user) {
    this.name = user.getName();
    this.email = user.getEmail();
    this.picture = user.getPicture();
}</code></pre><p>}</p>
<pre><code>### 2.2.5 왜 User 클래스를 사용하지 않고 SessionUser를 따로 만들었나?

- 세션에 저장을 위해 클래스의 직렬화가 필요함

    - **User 클래스는 엔티티**이기 때문에 직렬화 하기 힘듬
        - 엔티티 클래스는 언제 다른 엔티티와 관계가 형성될지 모름
       - ex) @OneToMany or @ManytoMany 등 자식 엔티티를 갖고 있다면 직렬화 대상에 해당 엔티티도 포함되므로 **성능이슈, 부수효과**가 발생할 가능성이 높음

# 3. 로그인 테스트
## 3.1 화면에 로그인 기능 추가
### 3.1.1 index.mustache</code></pre><h1>스프링 부트로 시작하는 웹 서비스</h1>
<div class="col-md-12">
    <div class="row">
        <div class="col-md-6">
            <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
            {{#userName}} ⓐ
                Logged in as : <span id="user">{{userName}}</span>
                <a href="/logout" class="btn btn-info active" role="button">Logout</a> ⓑ
            {{/userName}}
            {{^userName}} ⓒ
                <a href="/oauth2/authorization/google" class="btn btn-info active" role="button">Google Login</a> ⓓ
            {{/userName}}
        </div>
    </div>
</div>
```
- ⓐ {{#userName}}    
    - 머스테치는  true/false 여부만 판단함, if문 없음
    - 항상 최종값을 넘겨줘야 함
- ⓑ a href="/logout"
    - 스프링 시큐리티에서 기본적으로 제공하는 로그아웃 URL
    - 즉, 개발자가 별도로 저 URL에 해당하는 컨트롤러를 만들 필요가 없음
    - SecurityConfig 클래스에서 URL 변경 가능
- ⓒ {{^userName}}     
    - 머스테치에서는 해당 값이 존재하지 않을 경우에는 ^를 사용함
- ⓓ a href="/oauth2/authorization/google"     
    - 스프링 시큐리티에서 기본적으로 제공하는 구글 로그인 URL
### 3.1.2 indexController
```

<pre><code>private final HttpSession httpSession;

@GetMapping(&quot;/&quot;)
public String index(Model model) {
    model.addAttribute(&quot;posts&quot;, postsService.findAllDesc());

    SessionUser user = (SessionUser) httpSession.getAttribute(&quot;user&quot;); 

    if (user != null) { 
        model.addAttribute(&quot;userName&quot;, user.getName());
    }
    return &quot;index&quot;;
}</code></pre><pre><code>## 3.2 프로젝트 실행하여 테스트
1. 로그인버튼화면
![](https://images.velog.io/images/young_209/post/eac8c3a7-037d-414b-9c46-9778fcc5937d/image.png)
2. 구글 로그인
![](https://images.velog.io/images/young_209/post/3a8121ae-7fe4-4b60-a64c-c03b0d6cd218/image.png)
3. 로그인 정보 DB 확인
![](https://images.velog.io/images/young_209/post/d22d4efd-ed9d-40a4-b899-7bac0cfedea0/image.png)
4. 게시글 등록 (GUEST 상태일때)
![](https://images.velog.io/images/young_209/post/67fa9fc5-b6dc-4a65-8bba-0717c67aaee7/image.png)
![](https://images.velog.io/images/young_209/post/d91d6939-e9a5-42b9-8efd-6edf332bd059/image.png)
5. 권한 변경 후 등록
![](https://images.velog.io/images/young_209/post/e6916064-fefc-4f43-acfd-4f7e7f6ba44d/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[OAuth 2.0 및 구글 서비스 등록]]></title>
            <link>https://velog.io/@young_209/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-2.0-%EA%B8%B0%EB%B0%98%EC%9D%98-OAuth-2.0</link>
            <guid>https://velog.io/@young_209/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-2.0-%EA%B8%B0%EB%B0%98%EC%9D%98-OAuth-2.0</guid>
            <pubDate>Sat, 27 Nov 2021 09:11:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>본 포스팅은 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 보고 작성하였음</strong></p>
</blockquote>
<h1 id="1-스프링-부트-20-기반의-oauth-20">1. 스프링 부트 2.0 기반의 OAuth 2.0</h1>
<h2 id="11-oauth-20란">1.1 OAuth 2.0란?</h2>
<ul>
<li>예시로 만약 로그인 기능을 직접 구현할 경우 로그인 및 회원가입 등등의 기능이 필요하게 됨 but, OAuth 2.0을 사용한다면 대분의 로그인, 개인정보 관리 책임을 Google, Facebook, Kakao 등에게 위임할 수 있음</li>
</ul>
<h2 id="12-스프링-부트-20으로-사용하는-이유">1.2 스프링 부트 2.0으로 사용하는 이유</h2>
<ul>
<li>스프링 팀에서 기존 1.5에서 사용되던 Spring-security-oauth 프로젝트는 유지 상태(maintenance mode)로 결정했으며 더는 신규 기능은 추가하지 않고 버그 수정 정도의 기능만 추가될 예정, 신규 기능은 새 oauth2 라이브러리에서만 지원 하겠다고 선언</li>
<li>스프링 부트용 라이브러리(starter) 출시</li>
<li>기존에 사용되던 방식은 확장 포인트가 적절하게 오픈되어 있지 않아 직접 상소가거나 오버라이딩 해야 하고 신규 라이브러리의 경우 확장 포인트를 고려해서 설계된 상태</li>
<li>CommonOAuth2Provider라는 enum이 새롭게 추가되어 구글, 깃허브, 페이스북, 옥타(Okta)의 기본 설정 값은 모두 제공해주므로 따로 설정할 필요 없음</li>
</ul>
<h1 id="2-구글-서비스-등록-책-p166--p173">2. 구글 서비스 등록 (책 P.166 ~ P.173)</h1>
<h2 id="21-구글-클라우드-플랫폼에-신규-프로젝트-생성">2.1 구글 클라우드 플랫폼에 신규 프로젝트 생성</h2>
<p><img src="https://images.velog.io/images/young_209/post/9e7da0e1-9c57-4457-ad14-8a1ab68e921d/image.png" alt=""></p>
<h2 id="22-사용자-인증-정보-등록">2.2 사용자 인증 정보 등록</h2>
<p><img src="https://images.velog.io/images/young_209/post/701d23d8-1844-4b33-8177-5663a03fdc5f/image.png" alt=""></p>
<h2 id="23-프로젝트에-properties-추가">2.3 프로젝트에 properties 추가</h2>
<ul>
<li><p>스프링 부트에서는 properties의 이름을 application-xxx.properties 로 네이밍하여 만들면 xxx라는 이름의 profile이 생성되어 관리 할 수 있음, 즉 profile=xxx라는 식으로 호출하면 <strong><em>해당 properties의 설정들을 가져</em></strong>올수 있음</p>
</li>
<li><p>해당 책에서는 스프링 부트의 기본 설정 파일인 application.properties에서 application-oauth.properties를 포함하도록 구성
<img src="https://images.velog.io/images/young_209/post/c6da2a84-c3d0-46fc-a651-9415860812c5/image.png" alt="">
<img src="https://images.velog.io/images/young_209/post/f7488307-c5b0-4c13-977f-17b958ba70d5/image.png" alt="">
<img src="https://images.velog.io/images/young_209/post/bc23f538-b3c7-4197-8a48-453d1c0ca347/image.png" alt=""></p>
</li>
<li><p>.gitignore 등록</p>
<ul>
<li>구글 로그인을 위한 client Id와 client secret pw는 보안에 중요함ㄴ</li>
<li>깃에 해당 정보가 올라가지 않도록 방지해야함
<img src="https://images.velog.io/images/young_209/post/5c4cab6c-496c-4827-9146-e4bd7b4eb0a0/image.png" alt=""></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[면접 정리]]></title>
            <link>https://velog.io/@young_209/%EB%A9%B4%EC%A0%91-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@young_209/%EB%A9%B4%EC%A0%91-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 24 Nov 2021 04:36:01 GMT</pubDate>
            <description><![CDATA[<h1 id="checkedexception--unchecked-exception">CheckedException / Unchecked Exception</h1>
<h2 id="checked-exception">Checked Exception</h2>
<ul>
<li>처리여부 : 반드시 예외 처리 해야함</li>
<li>트랜잭션 Rollback 여부 : Rollback 안됨</li>
<li>대표 Exception : IOException, SQLException<h2 id="unchecked-exception">Unchecked Exception</h2>
</li>
<li>처리여부 : 예외 처리 하지 않아도됨</li>
<li>트랜잭션 Rollback 여부 : Rollback 안됨</li>
<li>대표 Exception : NullPointerException, IllegalArgumentException</li>
</ul>
<h4 id="참조--httpscheese10yungithubiochecked-exception">참조 : <a href="https://cheese10yun.github.io/checked-exception/">https://cheese10yun.github.io/checked-exception/</a></h4>
<h1 id="jpa-n1-문제">JPA N+1 문제</h1>
<h2 id="jpa-n1란">JPA N+1란?</h2>
<ul>
<li>Entity에 대해 하나의 쿼리로 N개의 레코드를 가져왔을 때, 연관관계 Entity를 가져오기 위해 쿼리를 N번 추가적으로 수행하는 문제</li>
</ul>
<h2 id="해결-방법">해결 방법</h2>
<ol>
<li>Fetch join 사용</li>
<li>@EntityGraph 사용</li>
<li>주의 사항<ul>
<li>JoinFetch는 Inner Join, Entity Graph는 Outer Join</li>
<li>카테시안 곱(Cartesian Product)이 발생하여 Subject의 수만큼 Academy가 중복 발생        <ul>
<li>일대다 필드의 타입을 Set으로 선언</li>
<li>distinct를 사용하여 중복을 제거하는 것</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="참조--httpsjojoldutistorycom165">참조 : <a href="https://jojoldu.tistory.com/165">https://jojoldu.tistory.com/165</a></h4>
<h1 id="테이블-정규화">테이블 정규화</h1>
<h2 id="정규화란">정규화란</h2>
<ul>
<li>테이블 간에 중복된 데이터를 허용하지 않는다는 것</li>
<li>무결성(Integrity)를 유지할 수 있으며, DB의 저장 용량 역시 줄일 수 있음</li>
</ul>
<h2 id="제1정규화">제1정규화</h2>
<ul>
<li>테이블의 컬럼이 원자값(Atomic Value, 하나의 값)을 갖도록 테이블을 분해하는 것<h2 id="제2정규화">제2정규화</h2>
</li>
<li>제1 정규화를 진행한 테이블에 대해 완전 함수 종속을 만족하도록 테이블을 분해하는 것</li>
<li>기본키의 부분집합이 결정자가 되어선 안된다는 것<h2 id="제3정규화">제3정규화</h2>
</li>
<li>제2 정규화를 진행한 테이블에 대해 이행적 종속을 없애도록 테이블을 분해하는 것</li>
<li>이행적 종속이라는 것은 A -&gt; B, B -&gt; C가 성립할 때 A -&gt; C가 성립되는 것<h2 id="bcnf-정규화">BCNF 정규화</h2>
</li>
<li>제3 정규화를 진행한 테이블에 대해 모든 결정자가 후보키가 되도록 테이블을 분해하는 것</li>
</ul>
<h4 id="참조--httpsmangkyutistorycom110">참조 : <a href="https://mangkyu.tistory.com/110">https://mangkyu.tistory.com/110</a></h4>
]]></description>
        </item>
    </channel>
</rss>