<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나를 위한 개발일기</title>
    <link>https://young-bin.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 3 Aug 2026 07:39:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Youngbin Kim</managingEditor>
    <item>
      <title>JaCoCo와 SonarQube 연동하기</title>
      <link>https://young-bin.tistory.com/94</link>
      <description>&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;978&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SgYcl/btrbuAQ4gZ1/b51osTlfmzldK42km5V8Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SgYcl/btrbuAQ4gZ1/b51osTlfmzldK42km5V8Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SgYcl/btrbuAQ4gZ1/b51osTlfmzldK42km5V8Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSgYcl%2FbtrbuAQ4gZ1%2Fb51osTlfmzldK42km5V8Kk%2Fimg.png&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;978&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 탭을 누르고 build 탭으로 간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task 설정하는 부분을 다음과 같이 변경한다.&lt;/p&gt;
&lt;pre id=&quot;code_1628338071828&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;clean build sonarqube
  -Dsonar.projectKey={프로젝트 이름}
  -Dsonar.host.url={sonarQube 서버}
  -Dsonar.login={발급받은 토큰}
  -Dsonar.github.repository=${repository}
  -Dsonar.github.endpoint=https://api.github.com
  -Dsonar.github.pullRequest=${pr}
  -Dsonar.github.disableInlineComments=true 
  -Dsonar.issuesReport.console.enable=true
  -Dsonar.sourceEncoding=UTF-8
  -Dsonar.language=java
  -Dsonar.java.binaries=build/classes&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;1482&quot; data-filename=&quot;스크린샷_2021-08-07_오후_8.00.21.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbZu0z/btrbvcoO5uf/VbMVyyPQySibFT2pg58Zb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbZu0z/btrbvcoO5uf/VbMVyyPQySibFT2pg58Zb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbZu0z/btrbvcoO5uf/VbMVyyPQySibFT2pg58Zb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbZu0z%2FbtrbvcoO5uf%2FVbMVyyPQySibFT2pg58Zb1%2Fimg.png&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;1482&quot; data-filename=&quot;스크린샷_2021-08-07_오후_8.00.21.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;빌드 성공 - 코드 커버리지 0%?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀리퀘를 보내면 github의 webhook을 이용해 젠킨스에서 자동으로 sonarQube 리포트를 생성하게 된다. 정적 리포트는 생성했지만 코드커버리지는 계속 분석하지 못했던 원인은 빌드 순서 때문이다.&lt;/p&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소나큐브는 Jacoco를 통해 만들어진 xml 리포트를 바탕으로 코드 커버리지를 분석한다. 그렇기 때문에 빌드시에 만들어진 jacoco 리포트가 없다면 소나큐브를 실행해도 코드커버리지는 0%로 나오게 된다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;test&lt;/li&gt;
&lt;li&gt;jacocoTestReport&lt;/li&gt;
&lt;li&gt;jacocoTestCoverageVerification&lt;/li&gt;
&lt;li&gt;SoanrQube&lt;br /&gt;(코드 커버리지 연동 시 2번을 통해 만들어진 리포트가 필요.)&lt;/li&gt;
&lt;/ol&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;실제로 코드 커버리지 연동에 실패했던 빌드 Task순서를 보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;1728&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csNet4/btrbuBbjbhy/mUvqUXd2EK2jSvbyTf7EQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csNet4/btrbuBbjbhy/mUvqUXd2EK2jSvbyTf7EQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csNet4/btrbuBbjbhy/mUvqUXd2EK2jSvbyTf7EQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsNet4%2FbtrbuBbjbhy%2FmUvqUXd2EK2jSvbyTf7EQ0%2Fimg.png&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;1728&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;코드 커버리지 연동에 성공한 빌드 Tasks&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;1610&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyNEOR/btrbotlgXR9/HfGcgxtigmdtI8KT8jJ9k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyNEOR/btrbotlgXR9/HfGcgxtigmdtI8KT8jJ9k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyNEOR/btrbotlgXR9/HfGcgxtigmdtI8KT8jJ9k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyNEOR%2FbtrbotlgXR9%2FHfGcgxtigmdtI8KT8jJ9k1%2Fimg.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;1610&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sonarqube 서버에서 연동에 성공한 모습을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;166&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2hxDD/btrbyHV9OAM/9FZksSKd1H0yrKmMCZZxi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2hxDD/btrbyHV9OAM/9FZksSKd1H0yrKmMCZZxi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2hxDD/btrbyHV9OAM/9FZksSKd1H0yrKmMCZZxi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2hxDD%2FbtrbyHV9OAM%2F9FZksSKd1H0yrKmMCZZxi1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;166&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>공부일기</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/94</guid>
      <comments>https://young-bin.tistory.com/94#entry94comment</comments>
      <pubDate>Wed, 11 Aug 2021 19:10:20 +0900</pubDate>
    </item>
    <item>
      <title>JaCoCo(Java Code Coverage) 적용하기</title>
      <link>https://young-bin.tistory.com/93</link>
      <description>&lt;h1&gt;JaCoCo란?&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JaCoCo is a free code coverage library for Java, which has been created by the EclEmma team based on the lessons learned from using and integration existing libraries for many years - JaCoCo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jacoco (Java Code Coverage) 자바 코드 커버리지를 체크하는 데 사용되는 오픈소스 라이브러리입니다.&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;&lt;b&gt;Jacoco의 특징&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Line, Branch Coverage를 제공한다.&lt;/li&gt;
&lt;li&gt;코드 커버리지 결과를 파일 형태로 저장할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;html, xml, csv 등으로 Report를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;JaCoCo 프로젝트에 적용하기&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JaCoCo plugin 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 build.gradle 파일 plugins 블록에 id 'jacoco'를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;plugins { 
    id 'jacoco' 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;324&quot; data-filename=&quot;스크린샷_2021-08-04_오후_5.25.50.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k8hCW/btrboss8Ruy/8199ZTHcvJjq5oQKxAPK50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8hCW/btrboss8Ruy/8199ZTHcvJjq5oQKxAPK50/img.png&quot; data-alt=&quot;jacoco 위의 설정들은 개인이 진행중인 프로젝트에 맞게 설정합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8hCW/btrboss8Ruy/8199ZTHcvJjq5oQKxAPK50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8hCW%2Fbtrboss8Ruy%2F8199ZTHcvJjq5oQKxAPK50%2Fimg.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;324&quot; data-filename=&quot;스크린샷_2021-08-04_오후_5.25.50.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;jacoco 위의 설정들은 개인이 진행중인 프로젝트에 맞게 설정합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금과 같이 자바 플러그인과 같이 프로젝트에 적용되는 경우, jacocoTestReport가 기본적으로 생성되고, HTML 리포트와 XML 리포트 파일이 &lt;code&gt;$buildDir/reports/jacoco/test&lt;/code&gt; 경로에 자동으로 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradle &amp;rarr; task &amp;mdash;&amp;gt; verification 항목에 추가된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;588&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U42Yx/btrbACAgrdZ/zgdJKhquN0KVMejN3IgAc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U42Yx/btrbACAgrdZ/zgdJKhquN0KVMejN3IgAc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U42Yx/btrbACAgrdZ/zgdJKhquN0KVMejN3IgAc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU42Yx%2FbtrbACAgrdZ%2FzgdJKhquN0KVMejN3IgAc1%2Fimg.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;588&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 두 개의 task는 반드시 test가 먼저 실행된 후에 실행이 되어야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Test와 JaCoCo의 의존 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트와 코드 커버리지 보고서의 의존성을 설정하기 위해 &lt;code&gt;build.gradle test&lt;/code&gt; 블록을 아래와 같이 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;test {
        ...
    useJUnitPlatform()

    finalizedBy 'jacocoTestReport' // 테스트가 먼저 실행되고 리포트가 작동된다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;289&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRrmc8/btrbwg5zuSM/ahf8wa9LiQRt5gKLbxsOU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRrmc8/btrbwg5zuSM/ahf8wa9LiQRt5gKLbxsOU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRrmc8/btrbwg5zuSM/ahf8wa9LiQRt5gKLbxsOU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRrmc8%2Fbtrbwg5zuSM%2Fahf8wa9LiQRt5gKLbxsOU1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;289&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JaCoCo plugin 설정 후 &lt;code&gt;jacoco&lt;/code&gt; 로 이름이 붙은 &lt;code&gt;JacocoPluginExtension&lt;/code&gt; 타입의 project extentions을 사용해 빌드 파일에서 사용 될 기본적인 설정을 합니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;jacoco {
    toolVersion = '0.8.7'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;279&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgpFJN/btrbpPhiU11/p8hVqBZlWvOKdi2ukLRP5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgpFJN/btrbpPhiU11/p8hVqBZlWvOKdi2ukLRP5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgpFJN/btrbpPhiU11/p8hVqBZlWvOKdi2ukLRP5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgpFJN%2FbtrbpPhiU11%2Fp8hVqBZlWvOKdi2ukLRP5K%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;279&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JaCoCoTestReports 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 결과를 받는 부분을 설정하는 부분입니다. html, scv, xml 등 다양한 형식으로 커버리지 측정 결과를 만들 수 있습니다. 기본은 html, 추후 Sonar Qube와의 연동을 위해 csv 형식도 true로 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;puppet&quot;&gt;&lt;code&gt;jacocoTestReport {
    dependsOn test // 리포트가 만들어지기 전 테스트 실행되어야 한다.
    reports {
        html.enabled true
        csv.enabled true
        xml.enabled false
    }
        ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;343&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNmINK/btrbpPuRQSu/bqeeb4A3P5xhQtZSUw5vO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNmINK/btrbpPuRQSu/bqeeb4A3P5xhQtZSUw5vO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNmINK/btrbpPuRQSu/bqeeb4A3P5xhQtZSUw5vO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNmINK%2FbtrbpPuRQSu%2Fbqeeb4A3P5xhQtZSUw5vO1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;343&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JaCoCoTestCoverageVerification 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커버리지 검증 수준을 정의하는 부분입니다. 해당 부분에서 위반 규칙 등을 설정해 JaCoCo의 report를 검사하고, 설정한 최소 수준을 달성하지 못하면 task는 실패하게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;jacocoTestCoverageVerification { // 코드 커버리지 측정항목 시행
    def Qdomains = []

    for (qPattern in &quot;*.QA&quot;..&quot;*.QZ&quot;) {
        Qdomains.add(qPattern + &quot;*&quot;)
    }

    violationRules { // 위반 규칙
        rule {
            limit {
                counter = 'METHOD'
                value = 'COVEREDRATIO'
                minimum = 0.85
            }

            excludes = [
                    // 커버리지 제외할 클래스
                    '*.DataLoader*',
                    '*.CviApplication*',
                    '*.DummyData*',
                    '*.KakaoProfile*',
                    '*.NaverProfile*'
            ] + Qdomains
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1283&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v0UcT/btrbpQmVQbp/fcSQcyWilxW77kvRU9X5a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v0UcT/btrbpQmVQbp/fcSQcyWilxW77kvRU9X5a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v0UcT/btrbpQmVQbp/fcSQcyWilxW77kvRU9X5a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv0UcT%2FbtrbpQmVQbp%2FfcSQcyWilxW77kvRU9X5a1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1283&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;rule : 적용하지 않은 부분은 회색 글씨로 표현했습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;enable: 해당 rule의 활성화 여부를 boolean으로 나타냄. default는 true&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;element: 커버리지 측정의 큰 단위. default는 BUNDLE&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;PACKAGE, CLASS, SOURCEFILE, METHOD&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;counter: 커버리지 측정의 최소 단위. default는 INSTRUCTION
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CLASS, METHOD, LINE, BRACH 등의 값이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;value: 측정한 counter 정보를 어떤 방식으로 보여줄지 정합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TOTALCOUNT, COVEREDCOUNT, COVEREDRATIO 등의 값이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;minimum: count 값을 value에 맞게 표현했을 때 최소 백분율. 이 값으로 jacoco coverage verification의 성공여부를 판단합니다.&lt;/li&gt;
&lt;li&gt;excludes: 커버리지 검증에서 제외할 클래스를 지정합니다. 패키지 레벨의 경로를 지정합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Qdomains 같은 경우 QueryDSL을 사용하고 있기 때문에 테스트할 필요가 없지만 &lt;code&gt;'\*.Q\*'&lt;/code&gt; 와 같이 지정할 경우 추후 발생할 Question과 같은 클래스도 제외시킬 수 있어 jacocoTestCoverageVerification 블록 처음 부분에 Q domain 패턴의 경로를 모아서 추가했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;include: 해당하는 rule을 적용할 대상을 package 수준으로 정의합니다. default는 전체 package&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JaCoCoReport에서 수집되지 않도록 제외하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JaCoCo의 테스트 검증 대상에서 벗어나도록 excludes 설정은 했지만, report 대상에는 그대로 남아있기 때문에 보고서엔 프로젝트 커버리지의 비율을 떨어뜨리는 모습으로 존재하고 있음을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzlWrY/btrbuIgWKFE/4IkwYn2SpaKfebIP9NQY6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzlWrY/btrbuIgWKFE/4IkwYn2SpaKfebIP9NQY6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzlWrY/btrbuIgWKFE/4IkwYn2SpaKfebIP9NQY6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzlWrY%2FbtrbuIgWKFE%2F4IkwYn2SpaKfebIP9NQY6k%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;300&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;해결을 위해 jacocoTestRepost 블럭에서 설정한 디렉토리는 report 결과에 포함하지 않도록 합니다.&lt;/p&gt;
&lt;pre class=&quot;puppet&quot;&gt;&lt;code&gt;jacocoTestReport {
    dependsOn test // 리포트가 만들어지기 전 테스트 실행되어야 한다.
    reports {
        html.enabled true
        csv.enabled true
        xml.enabled false
    }

    def Qdomains = []
    for(qPattern in &quot;**/QA&quot; .. &quot;**/QZ&quot;) {
        Qdomains.add(qPattern+&quot;*&quot;)
    }

    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, // 리포트 결과에 제외시킬 디렉토리
                    exclude: ['**/DataLoader*',
                              '**/CviApplication*',
                              '**/DummyData*',
                              '**/KakaoProfile*',
                              '**/NaverProfile*'] + Qdomains)
        }))
    }

    finalizedBy 'jacocoTestCoverageVerification'
}&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;code&gt;디렉토리&lt;/code&gt; 경로를 잡아줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1202&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t7PhM/btrbkWnwhhG/mLydASsJf0Kah3BWiNKclk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t7PhM/btrbkWnwhhG/mLydASsJf0Kah3BWiNKclk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t7PhM/btrbkWnwhhG/mLydASsJf0Kah3BWiNKclk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft7PhM%2FbtrbkWnwhhG%2FmLydASsJf0Kah3BWiNKclk%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1202&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 마치고 다시 리포트를 확인해보면 excludes 해준 클래스들이 리포트 결과에서 제외된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;1202&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W0LlW/btrbor8NvPO/w4CjKNiXnbHCriIYUe4CG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W0LlW/btrbor8NvPO/w4CjKNiXnbHCriIYUe4CG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W0LlW/btrbor8NvPO/w4CjKNiXnbHCriIYUe4CG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW0LlW%2Fbtrbor8NvPO%2Fw4CjKNiXnbHCriIYUe4CG1%2Fimg.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;1202&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&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;현재 프로젝트에서 Lombok을 사용중이기 때문에 getter, builder와 같은 메서드가 존재합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;1108&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckX9d1/btrbpV2pDHX/lmtlGR4bobvM9lPzIZV8Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckX9d1/btrbpV2pDHX/lmtlGR4bobvM9lPzIZV8Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckX9d1/btrbpV2pDHX/lmtlGR4bobvM9lPzIZV8Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckX9d1%2FbtrbpV2pDHX%2FlmtlGR4bobvM9lPzIZV8Dk%2Fimg.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;1108&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter와 builder와 같은 메서드 등을 커버리지 영역에 포함시키고 싶지 않은 경우 &lt;code&gt;lombok.config&lt;/code&gt; 파일을 만들어 아래와 같이 설정해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628337218067&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lombok.addLombokGeneratedAnnotation = true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;635&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oaeh0/btrbyHIBSIo/HLhHtvdKMcVUMr4yMBzkok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oaeh0/btrbyHIBSIo/HLhHtvdKMcVUMr4yMBzkok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oaeh0/btrbyHIBSIo/HLhHtvdKMcVUMr4yMBzkok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foaeh0%2FbtrbyHIBSIo%2FHLhHtvdKMcVUMr4yMBzkok%2Fimg.png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;635&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 빌드하고 리포트를 확인해보면 롬복을 통해 만들어진 getter, builder 메서드는 사라진 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;772&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PzqTS/btrbAIULFrq/URRPvvP1sg0u4aIlz3uRk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PzqTS/btrbAIULFrq/URRPvvP1sg0u4aIlz3uRk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PzqTS/btrbAIULFrq/URRPvvP1sg0u4aIlz3uRk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPzqTS%2FbtrbAIULFrq%2FURRPvvP1sg0u4aIlz3uRk0%2Fimg.png&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;772&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;testCoverage Task 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 &lt;i&gt;jacocoTestReport&lt;/i&gt;, &lt;i&gt;jacocoTestCoverageVerification&lt;/i&gt; task를 지정해주면 귀찮으니 이 task를 하나로 묶는 testCoverage라는 task를 만들 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628337591166&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task testCoverage(type: Test) {
    group 'verification'
    description 'Runs the unit tests with coverage'

    dependsOn(':test',
            ':jacocoTestReport',
            ':jacocoTestCoverageVerification')

    tasks['jacocoTestReport'].mustRunAfter(tasks['test'])
    tasks['jacocoTestCoverageVerification'].mustRunAfter(tasks['jacocoTestReport'])
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;552&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhyN9v/btrbosmomMl/m80JRPW41XsjDLPVVkKT5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhyN9v/btrbosmomMl/m80JRPW41XsjDLPVVkKT5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhyN9v/btrbosmomMl/m80JRPW41XsjDLPVVkKT5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhyN9v%2FbtrbosmomMl%2Fm80JRPW41XsjDLPVVkKT5k%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;552&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;i&gt;test&lt;/i&gt;, &lt;i&gt;jacocoTestReport&lt;/i&gt;, &lt;i&gt;jacocoTestCoverageVerification&lt;/i&gt; 실행 후 &lt;i&gt;testCoverage&lt;/i&gt; task를 실행하도록 dependsOn으로 설정합니다.&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;실행 순서를 고정 시키기 위해 mustRunAfter로 순서를 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑의 두 줄을 확인해보면 다음과 같은 순서로 task가 실행됨을 확인할 수 있습니다.&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;test &amp;rarr; jacocoTestReport &amp;rarr; jacocoTestCoverageVerification&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 &lt;i&gt;--console verbose&lt;/i&gt; 옵션을 추가해 실행순서를 확인하면 설정한 순서대로 실행됨을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;386&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EuweI/btrbqvXbEpO/xaMrWcouKg2IOZY8GX7va1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EuweI/btrbqvXbEpO/xaMrWcouKg2IOZY8GX7va1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EuweI/btrbqvXbEpO/xaMrWcouKg2IOZY8GX7va1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEuweI%2FbtrbqvXbEpO%2FxaMrWcouKg2IOZY8GX7va1%2Fimg.png&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;386&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;144&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WxzoA/btrbrUJd2K6/xwvWvov6DvZJ2jxFKxIY81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WxzoA/btrbrUJd2K6/xwvWvov6DvZJ2jxFKxIY81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WxzoA/btrbrUJd2K6/xwvWvov6DvZJ2jxFKxIY81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWxzoA%2FbtrbrUJd2K6%2FxwvWvov6DvZJ2jxFKxIY81%2Fimg.png&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;144&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;만약 최소 테스트 커버리지 비율을 95로 설정하고 빌드할 경우 다음과 같이 실패하는 것도 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;398&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqJW20/btrbAA3xjlK/UKDrg8EREVMpkykd7TaG61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqJW20/btrbAA3xjlK/UKDrg8EREVMpkykd7TaG61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqJW20/btrbAA3xjlK/UKDrg8EREVMpkykd7TaG61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqJW20%2FbtrbAA3xjlK%2FUKDrg8EREVMpkykd7TaG61%2Fimg.png&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;398&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;464&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz0DZh/btrbpPImacT/FdxDQo9qW89tt06XjdrGW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz0DZh/btrbpPImacT/FdxDQo9qW89tt06XjdrGW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz0DZh/btrbpPImacT/FdxDQo9qW89tt06XjdrGW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz0DZh%2FbtrbpPImacT%2FFdxDQo9qW89tt06XjdrGW1%2Fimg.png&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;464&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&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;a href=&quot;https://docs.gradle.org/current/userguide/jacoco_plugin.html&quot;&gt;https://docs.gradle.org/current/userguide/jacoco_plugin.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2661/&quot;&gt;https://techblog.woowahan.com/2661/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bottom-to-top.tistory.com/36&quot;&gt;https://bottom-to-top.tistory.com/36&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부일기</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/93</guid>
      <comments>https://young-bin.tistory.com/93#entry93comment</comments>
      <pubDate>Sat, 7 Aug 2021 21:02:18 +0900</pubDate>
    </item>
    <item>
      <title>Spring REST Docs 적용</title>
      <link>https://young-bin.tistory.com/92</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀의 API 문서 자동화&lt;/li&gt;
&lt;li&gt;프로덕션 코드 작성 전 API 명세를 맞추기 위해 MockMvc 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring REST Docs는 RESTful 서비스의 문서화를 도와주는 도구이다. Spring REST Docs는 문서 작성 도구로 기본적으로 Asciidoctor를 사용해 HTML을 생성한다. 필요한 경우 Markdown 을 사용하도록 변경할 수 있으나 편의를 위해 Asciidoctor로 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring REST Docs는 Spring MVC의 테스트 프레임 워크 또는 Rest Assured 3로 작성된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;테스트 코드에서 생성된 Snippet을 사용&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 기반 접근 방식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;서비스 문서의 정확성을 보장&lt;/b&gt;해준다. Snippet이 올바르지 않을 경우 테스트가 실패하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring REST Docs와 마찬가지로 많이 사용되는 문서도구로는 Swagger가 있으나 아래와 같은 사항들을 비교해 사용하지 않았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Spring REST Docs&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;vs&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Swagger&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Rest Docs는 Swagger에 비해 사용법은 어렵지만, Swagger 사용시 발생할 수 있는 코드 동기화 이슈를 막을 수 있다.&lt;/li&gt;
&lt;li&gt;테스트를 성공시켜야 문서 작성되기 때문에 테스트 작성을 강제할 수 있다.&lt;/li&gt;
&lt;li&gt;프로덕션 코드를 작성하고 테스트 작성 시 협업을 하고 API 명세를 작성하는 데 늦어지기 때문에 MockMvc(@WebMvcTest)를 사용한다.&lt;span style=&quot;color: #000000;&quot;&gt;Spring REST Docs 적용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MockMvc(@WebMvcTest)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서의 요청과 응답을 의미하는 객체로 Controller 테스트를 용이하게 해주는 라이브러리이다.&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;Rest Assured의 경우 전체 컨텍스트를 로드하고 빈을 주입하기 때문에 속도가 느리다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빌드 시스템 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1626681815184&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background-color: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins { 
  	// Asciidoctor 플러그인 적용
  	id &quot;org.asciidoctor.convert&quot; version &quot;1.5.9.2&quot;
  }

  dependencies {
  	/* 
  		asciidocker 에 대한 spring-restdocs-asciidocker 의존성 추가
  		Maven 처럼 build/generated-snippets 밑에 생성된 Snippet 을 .adoc 파일이 자동으로
  		가리키도록 하는 설정 추가.
  		operation 블록 매크로 사용 가능
  	*/
  	asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor:{project-version}' 
  	// Maven 과 같이 test Scope 에 대한 mockMvc 의존성을 추가 (WebClient, Assured 사용가능) 
  	testCompile 'org.springframework.restdocs:spring-restdocs-mockmvc:{project-version}' 
  }

  ext { 
  	// Snippet 의 생성 위치를 지정
  	snippetsDir = file('build/generated-snippets')
  }

  test { 
  	// Snippets 디렉토리를 출력으로 작업하도록 설정
  	outputs.dir snippetsDir
  }

  asciidoctor { 
  	// Snippets 디렉토리를 Input 디렉토리로 설정
  	inputs.dir snippetsDir 
  	// 문서 생성 전 테스트가 실행되도록 test 에 종속 설정
  	dependsOn test 
  }

bootJar {
  	// 빌드 전 문서 생성 확인
  	dependsOn asciidoctor
  	// 생성된 문서를 static/docs 에 복사 
  	from (&quot;${asciidoctor.outputDir}/html5&quot;) { 
  		into 'static/docs'
  	}
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Snippet 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 후 생성되는 Snippet을 사용하기 위해 .adoc 파일을 생성해야 한다. 파일명은 상관없고, 생서되는 snippet을 연결해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 Snippet을 사용한 .adoc 파일의 일부와 html로 출력될 경우의 예시이다.&lt;/p&gt;
&lt;pre id=&quot;code_1626681833787&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ifndef::snippets[]
:snippets: ../../../build/generated-snippets
endif::[]
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:author: Team CVI
:email: https://github.com/woowacourse-teams/2021-cvi
// 여기까진 설정 부분

== User (유저)
=== 유저 가입
==== 성공
===== Request
include::{snippets}/user-signup/http-request.adoc[]

===== Response
include::{snippets}/user-signup/http-response.adoc[]

==== 실패
===== Request
include::{snippets}/user-signup-failure/http-request.adoc[]

===== Response
include::{snippets}/user-signup-failure/http-response.adoc[]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;2036&quot; data-filename=&quot;스크린샷 2021-07-19 오후 5.04.13.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LjCh3/btq9RepFhr6/1KPdCJeaIaPiGJoqnTC6FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LjCh3/btq9RepFhr6/1KPdCJeaIaPiGJoqnTC6FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LjCh3/btq9RepFhr6/1KPdCJeaIaPiGJoqnTC6FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLjCh3%2Fbtq9RepFhr6%2F1KPdCJeaIaPiGJoqnTC6FK%2Fimg.png&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;2036&quot; data-filename=&quot;스크린샷 2021-07-19 오후 5.04.13.png&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring REST Docs를 위해 작성된 테스트 코드 예제&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1626681887833&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AutoConfigureRestDocs
@AutoConfigureMockMvc
public class ApiDocument {

    @Autowired
    protected MockMvc mockMvc;

    protected static OperationRequestPreprocessor getDocumentRequest() {
        return preprocessRequest(
                modifyUris()
                        .scheme(&quot;http&quot;)
                        .host(&quot;localhost&quot;)
                        .removePort(),
                prettyPrint());
    }

    protected static OperationResponsePreprocessor getDocumentResponse() {
        return preprocessResponse(prettyPrint());
    }

    protected static RestDocumentationResultHandler toDocument(String title) {
        return document(title, getDocumentRequest(), getDocumentResponse());
    }

    protected static String toJson(Object object) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
            return objectMapper.writeValueAsString(object);
        } catch (Exception e) {
            throw new IllegalStateException(&quot;직렬화 오류&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1626681894448&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebMvcTest(controllers = UserController.class)
class UserControllerTest extends ApiDocument {

    @MockBean
    private UserService userService;

    private UserRequest userRequest;
    private UserResponse userResponse;

    @BeforeEach
    void init() {
        userRequest = new UserRequest(&quot;라이언&quot;, 20);
        userResponse = new UserResponse(1L, &quot;라이언&quot;, 20);
    }

    @DisplayName(&quot;유저 가입 - 성공&quot;)
    @Test
    void signup() throws Exception {
        //given
        willReturn(userResponse).given(userService).signup(any(UserRequest.class));
        //when
        ResultActions response = 유저_회원가입_요청(userRequest);
        //then
        유저_회원가입_성공함(response, userResponse);
    }

    @DisplayName(&quot;유저 가입 - 실패&quot;)
    @Test
    void signupFailure() throws Exception {
        //given
        willThrow(new InvalidInputException(&quot;중복된 닉네임이 존재합니다.&quot;)).given(userService).signup(any(UserRequest.class));
        //when
        ResultActions response = 유저_회원가입_요청(userRequest);
        //then
        유저_회원가입_실패함(response);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;perform()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부에 컨트롤러 호출 방식인 get(&quot;호출 URI&quot;), post(&quot;호출 URI&quot;), put(&quot;호출 URI&quot;), delete(&quot;호출 URI&quot;)가 들어갈 수 있다.&lt;/li&gt;
&lt;li&gt;header(), accept(), contentType(), content(), param() 등을 호출할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1626681916947&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private ResultActions 유저_회원가입_요청(UserRequest request) throws Exception {
    return mockMvc.perform(post(&quot;/api/v1/users/signup&quot;)
            .contentType(MediaType.APPLICATION_JSON)
            .content(toJson(request)));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ResultActions&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MockMvc.perform() 메서드로 리턴되는 인터페이스. 지원 메서드로는 andExpect(), andDo(), andReturn() 등이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1626681932041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void 유저_회원가입_성공함(ResultActions response, UserResponse userResponse) throws Exception {
    response.andExpect(status().isCreated())
            .andExpect(header().string(&quot;Location&quot;, &quot;/api/v1/users/&quot; + 1))
            .andExpect(content().json(toJson(userResponse)))
            .andDo(print())
            .andDo(toDocument(&quot;user-signup&quot;));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JsonPath&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 결과가 Json인 경우 jsonPath를 사용해 검증할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1626681944726&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mockMvc.perform(post(&quot;/test1&quot;)                 
       .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)     
       .content(mapper.writeValueAsString(new TestInputVO(&quot;11&quot;, &quot;22&quot;))))          
       .andExpect(status().isOk())                            
       .andExpect(header().string(&quot;ans_cd&quot;, &quot;0000&quot;))
       .andExpect(jsonPath(&quot;$.value1&quot;).value(&quot;A&quot;))
       .andExpect(jsonPath(&quot;$.value2&quot;).isNotEmpty())
       .andExpect(jsonPath(&quot;$.value3&quot;).value(&quot;0000&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;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://twofootdog.github.io/Spring-Spring-MVC에서-JUnit-활용하기2(MockMvc를-활용한-Controller-테스트)/&quot;&gt;https://twofootdog.github.io/Spring-Spring-MVC에서-JUnit-활용하기2(MockMvc를-활용한-Controller-테스트)/&lt;/a&gt;&lt;/p&gt;</description>
      <category>공부일기/스프링</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/92</guid>
      <comments>https://young-bin.tistory.com/92#entry92comment</comments>
      <pubDate>Mon, 26 Jul 2021 18:06:04 +0900</pubDate>
    </item>
    <item>
      <title>NAVER LOGIN API 연동</title>
      <link>https://young-bin.tistory.com/91</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 정보&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Client ID&lt;/li&gt;
&lt;li&gt;Client Secret&lt;/li&gt;
&lt;li&gt;서비스 URL&lt;/li&gt;
&lt;li&gt;Callback URL&lt;/li&gt;
&lt;li&gt;State&lt;/li&gt;
&lt;/ul&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-origin-width=&quot;1081&quot; data-origin-height=&quot;643&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPDWup/btq9Reb8Y1Q/V1MQTW3AK38OCycjyrzXR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPDWup/btq9Reb8Y1Q/V1MQTW3AK38OCycjyrzXR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPDWup/btq9Reb8Y1Q/V1MQTW3AK38OCycjyrzXR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPDWup%2Fbtq9Reb8Y1Q%2FV1MQTW3AK38OCycjyrzXR0%2Fimg.png&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;643&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소셜로그인의 과정은 크게 3가지로 볼 수 있다.&lt;/p&gt;
&lt;ol 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;li&gt;&lt;b&gt;토큰 받기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리소스(회원정보) 받기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인가코드 받기&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.naver.com/apps/#/register&quot;&gt;네이버 개발자센터&lt;/a&gt;를 통해 서비스를 등록하고, Client ID와 Client Secret을 발급 받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;1720&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb3X4P/btq92xUONa1/SjnriejZOScEds7AKtK0gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb3X4P/btq92xUONa1/SjnriejZOScEds7AKtK0gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb3X4P/btq92xUONa1/SjnriejZOScEds7AKtK0gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb3X4P%2Fbtq92xUONa1%2FSjnriejZOScEds7AKtK0gk%2Fimg.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;1720&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;1156&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zmtX2/btq9Y5548Bp/C41FSpf8XDb1vnlU6Dh0gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zmtX2/btq9Y5548Bp/C41FSpf8XDb1vnlU6Dh0gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zmtX2/btq9Y5548Bp/C41FSpf8XDb1vnlU6Dh0gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzmtX2%2Fbtq9Y5548Bp%2FC41FSpf8XDb1vnlU6Dh0gk%2Fimg.png&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;1156&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일단 개발환경에서 테스트할 예정이기 때문에 localhost:8080으로 지정했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1856&quot; data-origin-height=&quot;546&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buKed4/btq91o49cQ1/mpjQDkvyuXA5D5DrQWbQF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buKed4/btq91o49cQ1/mpjQDkvyuXA5D5DrQWbQF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buKed4/btq91o49cQ1/mpjQDkvyuXA5D5DrQWbQF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuKed4%2Fbtq91o49cQ1%2FmpjQDkvyuXA5D5DrQWbQF0%2Fimg.png&quot; data-origin-width=&quot;1856&quot; data-origin-height=&quot;546&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;설정 후 확인을 누르면 네이버에서 Client ID와 Client Secret을 발급해준다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Client Secret의 경우 외부에 노출되어선 안 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 Client ID, redirect_uri(Callback URL의 인코딩 값), state(상태 유지를 위한 임의의 문자열) 정보를 넣어 아래 예제와 같은 주소로 요청을 보낸다.&lt;/p&gt;
&lt;pre id=&quot;code_1626681517785&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;https://nid.naver.com/oauth2.0/authorize?response_type=code&amp;amp;client_id={}&amp;amp;redirect_uri={}&amp;amp;state={}&lt;/code&gt;&lt;/pre&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-origin-width=&quot;1072&quot; data-origin-height=&quot;742&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IaYhN/btq91NXSRw5/hPOC0VCXQhMapkLiPTeFu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IaYhN/btq91NXSRw5/hPOC0VCXQhMapkLiPTeFu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IaYhN/btq91NXSRw5/hPOC0VCXQhMapkLiPTeFu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIaYhN%2Fbtq91NXSRw5%2FhPOC0VCXQhMapkLiPTeFu0%2Fimg.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;742&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;770&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCRCq5/btq92wInb0Z/HcaIyc3kFtK5qO6vWX3Uz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCRCq5/btq92wInb0Z/HcaIyc3kFtK5qO6vWX3Uz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCRCq5/btq92wInb0Z/HcaIyc3kFtK5qO6vWX3Uz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCRCq5%2Fbtq92wInb0Z%2FHcaIyc3kFtK5qO6vWX3Uz1%2Fimg.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;770&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;&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;&quot;&gt;동의하기를 누르면 사진처럼 설정해놓은 Callback URL로 이동한다. 이 때 네이버에서 url 파라미터로 code를 제공해주는 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;96&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAQDul/btq9Y5E0pA3/kfEptiqG5y76aVyYfWOWhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAQDul/btq9Y5E0pA3/kfEptiqG5y76aVyYfWOWhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAQDul/btq9Y5E0pA3/kfEptiqG5y76aVyYfWOWhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAQDul%2Fbtq9Y5E0pA3%2FkfEptiqG5y76aVyYfWOWhk%2Fimg.png&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;96&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;b&gt;2. 토큰 받기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제공 받은 인가코드(code)를 백엔드쪽에 넘기고, Resource Server(NAVER)에게 토큰을 받아와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발급 받은 code와 status 값을 이용해 네이버에게 토큰 발급을 요청한다.&lt;/p&gt;
&lt;pre id=&quot;code_1626681609878&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/auth/naver/login/callback&quot;)
public ResponseEntity naverCallback(String code, String state) throws JsonProcessingException {
    MultiValueMap&amp;lt;String, String&amp;gt; params = new LinkedMultiValueMap&amp;lt;&amp;gt;();
    params.add(&quot;grant_type&quot;,&quot;authorization_code&quot;);
    params.add(&quot;client_id&quot;,CLIENT_ID);
    params.add(&quot;client_secret&quot;, CLIENT_SECRET);
    params.add(&quot;code&quot;, code);
    params.add(&quot;state&quot;, state);
		// Parameter로 전달할 속성들 추가

    HttpEntity&amp;lt;MultiValueMap&amp;lt;String, String&amp;gt;&amp;gt; naverTokenRequest = makeTokenRequest(params);
		// Http 메시지 생성

    RestTemplate rt = new RestTemplate();
    ResponseEntity&amp;lt;String&amp;gt; tokenResponse = rt.exchange(
            TOKEN_REQUEST_URL,
            HttpMethod.POST,
            naverTokenRequest,
            String.class
    );
		// TOKEN_REQUEST_URL로 Http 요청 전송

		ObjectMapper objectMapper = new ObjectMapper();
    NaverOAuthToken naverToken = objectMapper.readValue(tokenResponse.getBody(), NaverOAuthToken.class);
		// ObjectMapper를 통해 NaverOAuthToken 객체로 매핑
}

private HttpEntity&amp;lt;MultiValueMap&amp;lt;String, String&amp;gt;&amp;gt; makeTokenRequest(MultiValueMap&amp;lt;String, String&amp;gt; params) {
    HttpHeaders headers = new HttpHeaders();
    headers.add(&quot;Content-type&quot;, &quot;application/x-www-form-urlencoded;charset=utf-8&quot;);
    HttpEntity&amp;lt;MultiValueMap&amp;lt;String, String&amp;gt;&amp;gt; naverTokenRequest = new HttpEntity&amp;lt;&amp;gt;(params, headers);
    return naverTokenRequest;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1626681618089&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NaverOAuthToken {
    private String access_token;
    private String refresh_token;
    private String token_type;
    private String expires_in;

    public NaverOAuthToken() {
    }

    public String getAccess_token() {
        return access_token;
    }

		...

    public void setExpires_in(String expires_in) {
        this.expires_in = expires_in;
    }
}&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;3. 리소스 받기(회원정보 받기)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버에서 받아온 토큰 정보를 활용해 회원 프로필 조회 API를 호출한다.&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;pre id=&quot;code_1626681631614&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/auth/naver/login/callback&quot;)
public ResponseEntity naverCallback(String code, String state) throws JsonProcessingException {

    ...

    HttpEntity&amp;lt;MultiValueMap&amp;lt;String, String&amp;gt;&amp;gt; naverProfileRequest = makeProfileRequest(naverToken);

    ResponseEntity&amp;lt;String&amp;gt; profileResponse = rt.exchange(
    PROFILE_REQUEST_URL,
    HttpMethod.POST,
    naverProfileRequest,
    String.class
    );

    NaverProfileResponse naverProfileResponse = objectMapper.readValue(profileResponse.getBody(), NaverProfileResponse.class);
}
private HttpEntity&amp;lt;MultiValueMap&amp;lt;String, String&amp;gt;&amp;gt; makeProfileRequest(NaverOAuthToken naverToken) {
    HttpHeaders headers = new HttpHeaders();
    headers.add(&quot;Authorization&quot;, &quot;Bearer &quot;+ naverToken.getAccess_token());
    headers.add(&quot;Content-type&quot;, &quot;application/x-www-form-urlencoded;charset=utf-8&quot;);
    HttpEntity&amp;lt;MultiValueMap&amp;lt;String, String&amp;gt;&amp;gt; naverProfileRequest = new HttpEntity&amp;lt;&amp;gt;(headers);
    return naverProfileRequest;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1626681665398&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NaverProfileResponse {
    private String resultcode;
    private String message;
    private NaverProfile response;

    public NaverProfileResponse() {
    }

    public String getResultcode() {
        return resultcode;
    }

    ...

    public void setResponse(NaverProfile response) {
        this.response = response;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1626681671844&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NaverProfile {
    private String email;
    private String nickname;
    private String profile_image;
    private String age;
    private String gender;
    private String id;
    private String name;
    private String birthday;
    private String birthyear;
    private String mobile;

    public NaverProfile() {
    }

    public String getEmail() {
        return email;
    }

    ...

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}&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;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;레이어 분리(컨틀로러-서비스-레파지토리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등으로 이뤄질 것 같다.&lt;/p&gt;</description>
      <category>공부일기</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/91</guid>
      <comments>https://young-bin.tistory.com/91#entry91comment</comments>
      <pubDate>Mon, 19 Jul 2021 17:01:29 +0900</pubDate>
    </item>
    <item>
      <title>좋은 객체 지향 설계의 5가지 원칙(SOLID)</title>
      <link>https://young-bin.tistory.com/90</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;&lt;span&gt;&lt;b&gt;&lt;span&gt;1. SRP(Single Responsibility Principle) 단일 책임 원칙&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;한 클래스는 하나의 책임만 가져야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;하나의 책임이라는 것이 모호한데, 문맥과 상황에 따라 다르다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;중요한 기준은 변경&lt;/span&gt;&lt;/b&gt;&lt;span&gt;이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;ex) UI 변경, 객체의 생성과 사용을 분리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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&gt;&lt;b&gt;&lt;span&gt;2. OCP(Open/Closed Principle) 개방-폐쇄 원칙&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;다형성을 활용한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;역할과 구현의 분리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; 문제점&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;분명 다형성을 사용했지만 OCP를 지킬 수 없다. -&amp;gt; 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요 -&amp;gt; 스프링 컨테이너의 역할&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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&gt;&lt;b&gt;&lt;span&gt;3. LSP(Liskov Substitution Principle) 리스코프 치환 원칙&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;프로그램의 객체는 정확성을 깨지 않으며 하위 타입의 인스턴스로 바꿀 수 있어야 한다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;역할이 있으면 역할에 맞는 행동이 보장되어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이 원칙은 자식 클래스의 잘못된 상속 구현으로 인한 문제에 대해 좀 더 포괄적인 의미를 내포하고 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;개발자 관점으로 더 자세히 얘기하면,&amp;nbsp;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;'부모 클래스 타입인 A를 사용하는 기존의 프로그램 코드가 자식 클래스 B로 대입 시켰을 때도 문제 없이 작동하도록 하기 위해서, 자식 클래스는 부모 클래스가 따르던 계약 사항을 자식도 따라야한다&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;.'이다. ex) 냉장고의 기능은 내부의 기온을 낮추는 것이지 올리는 것이 아니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&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&gt;&lt;b&gt;&lt;span&gt;4. ISP(Interface Segregation Principle) 인터페이스 분리 원칙&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;인터페이스가 명확해지고, 대체 가능성이 높아진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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&gt;&lt;b&gt;&lt;span&gt;5. DIP(Dependency Inversion Principle) 의존관계 역전 원칙&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;추상화에 의존해야지, 구체화에 의존하면 안된다. 의존성 주입이 이 원칙을 따르는 방법 중 하나이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;간단히 말해 구현 클래스가 아닌, 인터페이스에 의존하라!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;인터페이스에 의존한다는 것은? &lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;역할에 의존&lt;/span&gt;&lt;/b&gt;&lt;span&gt;한다는 것이다. -&amp;gt; 변경에 유연하게 대응할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부일기/Java 8</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/90</guid>
      <comments>https://young-bin.tistory.com/90#entry90comment</comments>
      <pubDate>Mon, 21 Jun 2021 19:15:16 +0900</pubDate>
    </item>
    <item>
      <title>토비의 스프링 2. 테스트</title>
      <link>https://young-bin.tistory.com/89</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;UserDaoTest 다시보기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트의 유용성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트란 내가 예상하고 의도했던 대로 코드가 정확하게 동작하는지 확인해 만든 코드를 확신할 수 있게 해주는 작업이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작은 단위의 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트하고자 하는 대상이 명확하다면 그 대상에만 집중해서 테스트하는 것이 바람직하다. 테스트는 가능하면 작은 단위로 쪼개서 해야 정확한 원인을 찾기 쉽다.(테스트의 관심사 분리)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단위 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트에서 단위는 그 크기와 범위가 어느 정도인지 정확하게 정해진 건 아니다. 크게봐서 사용자 관리 기능을 하나의 단위로 볼 수도 있고, 작게 보면 add()와 같은 메소드 하나를 단위라고 생각할 수도 있다. &lt;b&gt;충분히 하나의 관심에 집중해 효율적으로 테스트할 만한 범위의 테스트를 단위 테스트라 할 수 있다.&lt;/b&gt;&lt;br /&gt;일반적으로 단위는 작을수록 좋다. 작은 단위로 나눠서 하는 테스트는 개발자가 설계하고 만든 코드가 의도대로 동작하는지 개발자 스스로 빨리 확인할 수 있게 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DI와 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 두고 DI를 적용해야 하는 이유 3가지&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다. 당장 클래스를 바꿔서 사용할 계획이 전혀 없더라도, 언젠가 변경이 필요한 상황에서 수정에 들어가는 시간과 비용의 부담을 줄일 수 있다.&lt;/li&gt;
&lt;li&gt;클래스의 구현 방식은 바뀌지 않아도 인터페이스를 두고 DI를 적용하게 하면 다른 차원의 서비스 기능을 도입할 수 있다. 새로운 기능을 넣기 위해 기존 코드를 수정할 필요가 없다.&lt;/li&gt;
&lt;li&gt;효율적인 테스트를 손쉽게 만들기 위해서이다. DI는 테스트가 작은 단위의 대상에 대해 독립적으로 만들어지고 실행되게 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DI를 이용한 테스트 방법 선택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 가장 우선적으로 고려한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 테스트의 장점&lt;/h3&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;학습 테스트 코드를 개발 중에 참고할 수 있다.&lt;/li&gt;
&lt;li&gt;프레임워크나 제품 업그레이드시 호환성 검증을 도와준다.&lt;br /&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&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;테스트 결과는 일관성이 있어야 한다. 코드의 변경 없이 환경이나 테스트 실행 순서에 따라서 결과가 달라지면 안 된다.&lt;/li&gt;
&lt;li&gt;테스트는 포괄적으로 작성해야 한다. 충분한 검증을 하지 않는 테스트는 없는 것보다 나쁠 수 있다.&lt;/li&gt;
&lt;li&gt;코드 작성과 테스트 수행의 간격이 짧을수록 효과적이다.&lt;/li&gt;
&lt;li&gt;테스트하기 쉬운 코드가 좋은 코드다.&lt;/li&gt;
&lt;li&gt;테스트 코드도 애플리케이션 코드와 마찬가지로 적절한 리팩토링이 필요하다.&lt;/li&gt;
&lt;li&gt;@Before, @After를 사용해서 테스트 메소드들의 공통 준비 작업과 정리 작업을 처리할 수 있다.&lt;/li&gt;
&lt;li&gt;스프링 테스트 컨텍스트 프레임워크를 이용하면 테스트 성능을 향상시킬 수 있다.&lt;/li&gt;
&lt;li&gt;동일한 설정파일을 사용하는 테스트는 하나의 애플리케이션 컨텍스트를 공유한다.&lt;/li&gt;
&lt;li&gt;@Autowired를 사용하면 컨텍스트의 빈을 테스트 오브젝트에 DI 할 수 있다.&lt;/li&gt;
&lt;li&gt;기술의 사용 방법을 익히고 이해를 돕기 위해 학습 테스트를 작성하자.&lt;/li&gt;
&lt;li&gt;오류가 발견될 경우 그에 대한 버그 테스트를 만들어두면 유용하다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부일기/스프링</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/89</guid>
      <comments>https://young-bin.tistory.com/89#entry89comment</comments>
      <pubDate>Sun, 16 May 2021 14:21:29 +0900</pubDate>
    </item>
    <item>
      <title>토비의 스프링 - 1. 오브젝트와 의존관계(2)</title>
      <link>https://young-bin.tistory.com/88</link>
      <description>&lt;h2&gt;스프링의 IoC&lt;/h2&gt;
&lt;h3&gt;오브젝트 팩토리를 이용한 스프링 IoC&lt;/h3&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;빈 팩토리(애플리케이션 컨텍스트): 빈의 생성과 관계 설정 같은 제어를 담당하는 오브젝트&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/configuration&quot; data-hovercard-type=&quot;user&quot; data-hovercard-url=&quot;/users/configuration/hovercard&quot; data-octo-click=&quot;hovercard-link-click&quot; data-octo-dimensions=&quot;link_type:self&quot;&gt;@configuration&lt;/a&gt;: 스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식하게 만드는 애노테이션&lt;/li&gt;
&lt;li&gt;getBean(): ApplicationContext가 관맇는 오브젝트를 요청하는 메소드. 파라미터 값으로 등록된 빈의 이름과 리턴타입(클래스)을 전달한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;애플리케이션 컨텍스트의 동작방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 컨텍스트는 IoC를 적용해 관리할 모든 오브젝트에 대한 생성과 관계설정을 담당한다. 생성정보와 연관관계 정보는 별도의 설정정보를 통해 얻는다(예제에선&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/configuration&quot; data-hovercard-type=&quot;user&quot; data-hovercard-url=&quot;/users/configuration/hovercard&quot; data-octo-click=&quot;hovercard-link-click&quot; data-octo-dimensions=&quot;link_type:self&quot;&gt;@configuration&lt;/a&gt;이 붙은 DaoFactory).&lt;/li&gt;
&lt;li&gt;설정정보에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/bean&quot; data-hovercard-type=&quot;user&quot; data-hovercard-url=&quot;/users/bean/hovercard&quot; data-octo-click=&quot;hovercard-link-click&quot; data-octo-dimensions=&quot;link_type:self&quot;&gt;@bean&lt;/a&gt;이 붙은 메소드의 이름을 가져와 빈 목록을 만들어둔다.&lt;/li&gt;
&lt;li&gt;클라이언트가 애플리케이션 컨텍스트의 getBean() 메소드 호출 시 자신의 빈 목록에서 요청한 이름이 있는지 찾아 빈을 생성하는 메소드를 호출, 오브젝트를 만들어 클라이언트에게 돌려준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.&lt;br /&gt;오브젝트 팩토리가 아무리 많아지는 경우라도 클라이언트가 알아야 하거나 직접 사용할 필요가 현저히 적다.&lt;br /&gt;일관된 방식으로 원하는 오브젝트를 가져올 수 있다.&lt;/li&gt;
&lt;li&gt;종합 IoC 서비스를 제공해준다.&lt;br /&gt;오브젝트의 생성, 관계설정 + 만들어지는 방식, 시점, 전략을 다르게 가져갈 수 있다.&lt;/li&gt;
&lt;li&gt;빈을 검색하는 다양한 방법을 제공한다.&lt;br /&gt;타입만으로 빈을 검색하거나, 특별한 애노테이션이 설정되어 있는 빈을 찾을 수도 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;스프링 IoC의 용어 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈(bean)&lt;br /&gt;스프링이 IoC 방식으로 관리하는 오브젝트. 스프링이 직접 생성과 제어를 담당하는 오브젝트만 해당된다.&lt;/li&gt;
&lt;li&gt;빈 팩토리(bean factory)&lt;br /&gt;스프링의 IoC를 담당하는 핵심 컨테이너. 빈의 등록, 생성, 조회, 반환과 부가적인 관리를 담당한다. 보통은 빈 팩토리를 확장한 애플리케이션 컨텍스트를 사용한다.&lt;/li&gt;
&lt;li&gt;설정정보/설정 메타정보&lt;br /&gt;빈 팩토리(애플리케이션 컨텍스트)가 IoC를 적용하기 위해 사용하는 메타정보(configuration). 주로 IoC 컨테이너에 의해 관리되는 오브젝트를 생성 및 구성할 때 사용된다.&lt;/li&gt;
&lt;li&gt;컨테이너/IoC 컨테이너&lt;br /&gt;IoC 방식으로 빈을 관리하는 주체. 컨테이너는 주로 애플리케이션 컨텍스트를 지칭하며, IoC 컨테이너는 주로 빈 팩토리를 지칭한다.&lt;/li&gt;
&lt;li&gt;하나의 애플리케이션에서 여러 개의 애플리케이션 컨텍스트 오브젝트가 만들어 사용되는 경우는 어떤 경우일까?&lt;/li&gt;
&lt;li&gt;스프링 프레임워크&lt;br /&gt;IoC 컨테이너, 애플리케이션 컨텍스트를 포함한 스프링이 제공하는 모든 기능을 제공하는 소프트웨어 환경.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;싱글톤 레지스트리와 오브젝트 스코프&lt;/h2&gt;
&lt;p&gt;getBean(&quot;userDao&quot;, UserDao.class)으로 호출해서 가져온 오브젝트는 동일할까? 정답은 yes!&lt;br /&gt;어떻게 가능한 것일까?&lt;/p&gt;
&lt;h3&gt;싱글톤 레지스트리로서의 애플리케이션 컨텍스트&lt;/h3&gt;
&lt;p&gt;애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이다. 스프링에서 별다른 설정이 없을 경우 내부에서 생성하는 빈 오브젝트는 모두 싱글톤으로 만들어진다.&lt;/p&gt;
&lt;p&gt;서버 애플리케이션과 싱글톤&lt;/p&gt;
&lt;p&gt;스프링은 엔터프라이즈 시스템을 위해 고안된 기술이기 때문에 서버환경에서 사용될 때 그 가치가 있다. 간단히 말해 서버의 성능을 위해 싱글톤을 사용한다.&lt;/p&gt;
&lt;p&gt;싱글톤 패턴의 한계&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속할 수 없음&lt;br /&gt;싱글톤 패턴은 생성자를 private으로 제한하기 때문에 다른 생성자가 없다면 상속이 불가능하다. 이는 객체지향적인 설계의 장점을 적용하기 어렵다.&lt;/li&gt;
&lt;li&gt;테스트 하기 힘듦.&lt;br /&gt;만들어지는 방식이 제한적이기 때문에 목 오브젝트 등으로 대체하기 힘들다. 초기화 과정에서 사용할 오브젝트를 주입하기도 힘든 것도 테스트 하기 힘든 이유 중 하나.&lt;/li&gt;
&lt;li&gt;서버 환경에서 싱글톤이 하나만 만들어지는 것을 보장하지 못함&lt;br /&gt;클래스 로더를 어떻게 구성하고 있냐에 따라 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다.(ex. 여러 개의 JVM에 분산되어 설치되는 경우)&lt;/li&gt;
&lt;li&gt;전역 상태를 만들 수 있음.&lt;br /&gt;싱글톤의 static 메소드를 이용해 언제든지 싱글톤에 쉽게 접할 수 있어 전역 상태(global state)로 사용될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;싱글톤 레지스트리&lt;/p&gt;
&lt;p&gt;&lt;b&gt;스프링이 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는 것&lt;/b&gt;이 싱글톤 레지스트리이다.&lt;br /&gt;스프링 컨테이너는 싱글톤을 생성, 관리, 공급하는 싱글톤 관리 컨테이너이다. 덕분에 싱글톤 방식으로 사용될 애플리케이션 클래스도 public 생성자를 사용할 수 있다. 따라서 테스트 환경에서 자유롭게 사용 가능한 점과 더불어 자바의 싱글톤 패턴이 가진 한계를 극복할 수 있게 한다.&lt;/p&gt;
&lt;h3&gt;싱글톤과 오브젝트의 상태&lt;/h3&gt;
&lt;p&gt;싱글톤은 멀티스레드 환경에서 여러 스레드가 동시에 접근 가능하므로 상태 관리에 주의를 기울여야 한다. 따라서 서비스 형태의 오브젝트로 사용되는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;무상태&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;방식으로 만들어져야 한다.&lt;br /&gt;스프링의 싱글톤 빈으로 사용되는 클래스를 만드는 경우 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나 파라미터로 주고 받으며 사용해야 한다.(읽기 전용 정보인 경우는 예외)&lt;br /&gt;자신이 사용하는 다른 스프링 빈을 저장하려는 용도의 경우 인스턴스 변수 사용이 가능하다.(생성자 주입 방식이 가능한 이유인듯)&lt;/p&gt;
&lt;h3&gt;스프링 빈의 스코프&lt;/h3&gt;
&lt;p&gt;스프링 빈의 기본 스코프는 스프링 컨테이너 내에 한 개의 오브젝트가 만들어져 강제로 제거하지 않고, 스프링 컨테이너가 존재하는 동안 계속 유지된다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;추후 10장에서 자세히 소개 될 싱글톤 외의 스코프&lt;br /&gt;프로토 타입 스코프: 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트 반환.&lt;br /&gt;요청 스코프: HTTP 요청이 생길 때마다 생성되는 요청 스코프&lt;br /&gt;세션 스코프: 웹의 세션과 스코프가 유사하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;의존관계 주입(DI)&lt;/h2&gt;
&lt;h3&gt;제어의 역전과 의존관계 주입&lt;/h3&gt;
&lt;p&gt;의존관계란? 두 클래스 또는 모듈이 의존관계에 있다고 말할 때는 항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;방향성&lt;/b&gt;을 부여해줘야한다. 그리고 두 클래스의 의존관게는 아래와 같이 표현한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2021-05-05 오전 9.33.13.png&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;152&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xRxux/btq4NK6y5UZ/GeVeu7StoPo3VXvl2jgBb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xRxux/btq4NK6y5UZ/GeVeu7StoPo3VXvl2jgBb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xRxux/btq4NK6y5UZ/GeVeu7StoPo3VXvl2jgBb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxRxux%2Fbtq4NK6y5UZ%2FGeVeu7StoPo3VXvl2jgBb0%2Fimg.png&quot; data-filename=&quot;스크린샷 2021-05-05 오전 9.33.13.png&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;152&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;여기서 B가 변하면 A에 영향을 미친다. A에서 B에 정의된 메소드를 호출할 때 사용에 대한 의존관계가 있다고 한다. B에 새로운 메소드가 추가되거나 기존 메소드의 형식이 바뀌면 A도 영향을 받고, B에서 사용된 기능이 내부적으로 변경되는 경우 A의 기능 수행에 영향을 미칠 수 있다. 이런 사용 관계의 경우 A와 B는 의존관계가 있다고 말한다.&lt;/p&gt;
&lt;h3&gt;DaoFactory&lt;/h3&gt;
&lt;p&gt;예제에서 DaoFactory는 두 오브젝트 사이의 런타임 의존관계를 설정해주는 의존관계 주입 작업을 주도하는 존재이며, 동시에 IoC 방식으로 오브젝트의 생성, 초기화 및 제공을 하는 컨테이너다. 따라서 의존관계 주입을 담당하고 있다고 볼 수 있다.&lt;br /&gt;DI 컨테이너에 의해 런타임 시에 의존 오브젝트를 사용할 수 있도록 레퍼런스를 전달받는 과정을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;의존관계 주입&lt;/b&gt;이라고 부른다.&lt;/p&gt;
&lt;h3&gt;의존관계 검색과 주입&lt;/h3&gt;
&lt;p&gt;의존관계를 맺는 방법이 외부로부터의 주입이 아니라 스스로 검색을 이용하는 경우 의존관계 검색이라 한다. 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾지만 어떤 클래스의 오브젝트를 이용할지 결정하지는 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1620833898384&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public UserDao() { 
    AnnotationConfigApplicationContext context 
        = new AnnotationConfigApplicationContext(DaoFactory.class); 
    this.connectionMaker = context.getBean(&quot;connectionMaker&quot;, ConnectionMaker.class); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DI를 원하는 오브젝트는 컨테이너가 관리하는 빈이어야 한다.&lt;/p&gt;</description>
      <category>공부일기/스프링</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/88</guid>
      <comments>https://young-bin.tistory.com/88#entry88comment</comments>
      <pubDate>Thu, 13 May 2021 00:39:03 +0900</pubDate>
    </item>
    <item>
      <title>토비의 스프링 - 1. 오브젝트와 의존관계(1)</title>
      <link>https://young-bin.tistory.com/86</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;초난감 DAO&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JDBC를 이용하는 작업의 일반적인 순서&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB 연결을 위한 Connection을 가져온다.&lt;/li&gt;
&lt;li&gt;SQL을 담은 Statement를 만든다&lt;/li&gt;
&lt;li&gt;만들어진 Statement를 실행한다.&lt;/li&gt;
&lt;li&gt;조회의 경우 SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.&lt;/li&gt;
&lt;li&gt;작업 중에 생성된 Connection, Statement, ResultSet 같은 리소스는 작업을 마친 후 반드시 닫아준다.&lt;/li&gt;
&lt;li&gt;JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/woowacourse-tobi-spring-study/tobi-vol1/pull/9/files#diff-65accfe63d9c4d16c10d515dfe10373847d9112625db892385fc90ad6d9eced5&quot;&gt;UserDao&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DAO의 분리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;관심사의 분리&lt;/h3&gt;
&lt;p&gt;개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항은 &lt;b&gt;미래의 변화를 어떻게 대비할 것인가&lt;/b&gt;이다. 가장 좋은 대책은 &lt;b&gt;변화의 폭을 최소한으로&lt;/b&gt; 줄여주는 것이다. 변화의 폭을 최소화 하기 위한 방법으로는? &lt;b&gt;분리와 확장을 고려&lt;/b&gt;해 설계한다!&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;관심사의 분리(Separation of Cencerns)&lt;/b&gt;&lt;br /&gt;모든 변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어난다. 변화가 한 가지 관심에 집중돼 일어난다면, 개발자는 한 가지 관심이 한 군데에 집중되게 준비하면 된다. 관심이 같은 것끼리는 모으고, 관심이 다른 것은 따로 떨어져 있게 하는 것이다.&lt;br /&gt;-&amp;gt; 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게, 관심이 다른 것은 가능한 따로 떨어져서 서로 영향을 주지 않도록 분리하자.&lt;/li&gt;
&lt;li&gt;템플릿 메소드 패턴&lt;br /&gt;슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 protected 메소드 등으로 만들어, 서브 클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;DAO의 확장&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스의 분리
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;장점: 관심사와 변화의 성격이 다른 코드를 확실하게 분리할 수 있다.&lt;/li&gt;
&lt;li&gt;문제: 어떤 클래스가 사용되고, 사용되는 메소드의 이름이 뭔지 알고 있어야 한다. -&amp;gt; 특정 클래스에 종속되게 된다. -&amp;gt; 자유로운 확장 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스 도입
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;장점: 클래스를 몰라도 된다. 인터페이스를 통해 원하는 기능만을 사용한다.&lt;/li&gt;
&lt;li&gt;문제: 초기에 한 번 어떤 클래스의 오브젝트를 사용할지 결정하는 코드가 남아 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;관계설정 책임의 분리
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;장점: 오브젝트간의 관계 설정의 책임을 클라이언트에게 넘긴다. 관심사, 책임의 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;원칙과 패턴&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;개방 폐쇄 원칙(OCP, Open-Closed Principle)&lt;br /&gt;클래스나 모듈은 확장에는 열려 있고, 변경에는 닫혀있어야 한다. (ex. UserDao는 DB 연결 방법이라는 기능의 확장에는 열려있다. UserDao 자신의 핵심 기능을 구현한 코드는 외부의 변화에 영향을 받지 않고 유지할 수 있으므로 변경에는 닫혀있다고 할 수 있다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;높은 응집도와 낮은 결합도&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 응집도&lt;br /&gt;변화가 일어날 때 해당 모듈에서 변하는 부분이 크다. 모듈의 일부분에만 변경이 일어나도 되는 경우 모듈 전체에서 어떤 부분이 바뀌어야 하는지 파악해야 하고, 그 변경으로 인해 바뀌지 않는 부분이 다른 부분에 영향을 미치지는 않는지 파악해야 하는 수고가 생긴다.&lt;/li&gt;
&lt;li&gt;낮은 결합도&lt;br /&gt;책임과 관심사는 다른 오브젝트 또는 모듈과 낮은 결합도, 즉 느슨하게 연결된 형태를 유지하는 것이 바람직하다. 느슨한 연결은 관계를 유지하는 데 꼭 필요한 최소한의 방법만 간접적인 형태로 제공하고, 나머지는 서로 독립적이고 알 필요 없이 만들어주는 것이다.&lt;br /&gt;결합도가 낮아지면 변화에 대응하는 속도가 높아지고, 구성이 깔끔해지며 확장에 매우 편리하다.&lt;/li&gt;
&lt;li&gt;전략 패턴&lt;br /&gt;자신의 기능 맥락에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;제어의 역전(IoC)&lt;/h2&gt;
&lt;h3&gt;오브젝트 팩토리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팩토리&lt;br /&gt;객체의 생성 방법을 결정하고, 그렇게 만들어진 오브젝트를 돌려주는 일을 하는 오브젝트를 흔히 팩토리라고 부른다.&lt;br /&gt;Dao팩토리를 분리했을 때 얻을 수 있는 가장 큰 장점은 애플리케이션의 컴포넌트 역할을 하는 오브젝트와 애플리케이션의 구조를 결정하는 오브젝트를 분리했다는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;오브젝트 팩토리의 활용&lt;/h3&gt;
&lt;p&gt;인터페이스의 구현 클래스를 결정하고 오브젝트를 만드는 코드를 별도의 메소드로 뽑아내면, 구현 클래스를 바꿀 필요가 있을 경우에도 딱 한 군데만 수정하면 모든 DAO 팩토리 메소드에 적용된다.&lt;/p&gt;
&lt;h3&gt;제어권의 이전을 통한 제어관계 역전&lt;/h3&gt;
&lt;p&gt;제어의 역전이란? 프로그램의 제어 흐름 구조가 뒤바뀌는 것.&lt;br /&gt;일반적으로 프로그램의 흐름은 main() 메소드와 같이 프로그램이 시작되는 지점에서 다음에 사용할 오브젝트를 결정, 생성, 메소드 호출, 다음 사용 메소드 결정 등의 순서가 반복된다. 이런 구조에서 오브젝트는 프로그램 흐름을 결정하거나 사용할 오브젝트를 구성하는 작업에 능동적으로 참여한다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;b&gt;제어의 역전은 이런 제어 흐름의 개념을 거꾸로 뒤집는 것이다. 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 생성도 안하고, 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 다른 대상에게 위임하기 때문이다. 모든 객체는 위임받은 제어 권한을 갖는 특별한 객체에 의해 결정되고 만들어진다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;제어의 역전은 프레임워크만의 기술이 아니며 범용적으로 사용되는 프로그래밍 모델이다.&lt;/p&gt;</description>
      <category>공부일기/스프링</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/86</guid>
      <comments>https://young-bin.tistory.com/86#entry86comment</comments>
      <pubDate>Sat, 1 May 2021 16:54:22 +0900</pubDate>
    </item>
    <item>
      <title>우테코 두 달 생활기</title>
      <link>https://young-bin.tistory.com/85</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;우테코 과정의 레벨1 글쓰기 미션의 글.&lt;br /&gt;매 미션 끝날 때마다 회고를 적어보려 했는데 블랙잭 미션, 체스미션은 회고하지 못했다.&lt;br /&gt;대신해서 글쓰기 미션을 하며 적은 글을 올려본다.&lt;/blockquote&gt;
&lt;h2&gt;&lt;a id=&quot;user-content-한-달-간의-활동&quot; href=&quot;https://github.com/thisisyoungbin/woowa-writing-3/blob/thisisyoungbin/README.md#%ED%95%9C-%EB%8B%AC-%EA%B0%84%EC%9D%98-%ED%99%9C%EB%8F%99&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;두 달 간의 활동&lt;/h2&gt;
&lt;p&gt;사회적 거리두기로 인해 3기의 모든 과정은 온라인으로 진행됐다. 우테코 3기 온보딩 과정에서 첫 페어와 보이는 라디오 팀이 정해졌다. 보이는 라디오를 통해 서로를 조금 내려 놓으며 친밀감을 쌓았다. 미션을 수행하고, 강의를 들으며 시간이 숨가쁘게 흘러갔다. 4명의 페어와 미션을 진행했고, 세 번째 미션을 시작할 때부터 루터회관에 갈 수 있게 됐다. 매주 진행되는 강의와 테코톡의 내용을 이해하고 체화하기 위해 노력했다.&lt;/p&gt;
&lt;h2&gt;&lt;a id=&quot;user-content-우테코-3기로-생활하며-느낀-점&quot; href=&quot;https://github.com/thisisyoungbin/woowa-writing-3/blob/thisisyoungbin/README.md#%EC%9A%B0%ED%85%8C%EC%BD%94-3%EA%B8%B0%EB%A1%9C-%EC%83%9D%ED%99%9C%ED%95%98%EB%A9%B0-%EB%8A%90%EB%82%80-%EC%A0%90&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;우테코 3기로 생활하며 느낀 점&lt;/h2&gt;
&lt;h3&gt;&lt;a id=&quot;user-content-페어-프로그래밍&quot; href=&quot;https://github.com/thisisyoungbin/woowa-writing-3/blob/thisisyoungbin/README.md#%ED%8E%98%EC%96%B4-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;페어 프로그래밍&lt;/h3&gt;
&lt;p&gt;페어 프로그래밍을 빼고 우테코를 논할 수 있을까? 사실 처음 페어 프로그래밍이란 것을 접했을 땐 걱정을 많이 했다. '부족한 실력으로 페어에게 민폐만 끼치는 것은 아닐까?' '테스트 코드도 안 짜봤는데 무슨 TDD를 해?' 따위의 생각이 날 더 불안하게 했다. 부끄럽지만 '모르는데 어쩔 수 없지. 이제부터 배울거야!'라는 생각으로 페어들에게 내 상황을 설명했고, 고맙게도 모든 페어들이 이해해줬다. 내가 이해를 못 하거나 모르는 게 있으면 하나라도 더 알려주려고 했고, 이들 덕분에 많은 것을 배우고 성장할 수 있었다. 페어와 미션을 진행하는 과정에서 모르는 부분은 배울 수 있었고, 알고 있던 부분은 더 확신을 갖게 됐다.&lt;/p&gt;
&lt;p&gt;정답이 없는 프로그래밍 세계에서 본인만의 프로그래밍 철학을 가지는 것은 중요하다. 페어 프로그래밍을 통해 배움과 확신, 자신의 프로그래밍 철학을 만들어갈 수 있다고 생각한다.&lt;/p&gt;
&lt;h3&gt;&lt;a id=&quot;user-content-학습-방법&quot; href=&quot;https://github.com/thisisyoungbin/woowa-writing-3/blob/thisisyoungbin/README.md#%ED%95%99%EC%8A%B5-%EB%B0%A9%EB%B2%95&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;학습 방법&lt;/h3&gt;
&lt;p&gt;우테코를 시작하며 학습 방법에도 큰 변화가 생겼다. 개발 관련 서적들을 읽고, 스터디하기 시작했으며 TDD 방식으로 미션을 수행하는 게 익숙해지고 있다. 미션을 진행하며 생기는 모르는 부분들은 주변의 크루들에게 질문해 답을 얻는다. 공통된 관심사를 가진 사람들과 모여있는 것은 항상 즐거운 일이다. 소위 말하는 개발에 미쳐있는 사람들과 같은 공간에서 같이 학습한다는 것은 큰 행운이다.&lt;/p&gt;
&lt;h3&gt;&lt;a id=&quot;user-content-불안함과-성취감의-반복&quot; href=&quot;https://github.com/thisisyoungbin/woowa-writing-3/blob/thisisyoungbin/README.md#%EB%B6%88%EC%95%88%ED%95%A8%EA%B3%BC-%EC%84%B1%EC%B7%A8%EA%B0%90%EC%9D%98-%EB%B0%98%EB%B3%B5&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;불안함과 성취감의 반복&lt;/h3&gt;
&lt;p&gt;매번 미션을 진행하기 전 '어떻게 해야 할까...'하는 불안함이 생긴다. 막상 구현을 시작하면 어느 순간부터는 불안함이 '오! 이렇게 하면 되네~?! 재밌다!'하는 성취감으로 변한다. 하지만, PR을 하고 코드 리뷰를 받으며 다시 '그러네.. 어떻게 바꿔야 되지?'하는 불안이 생기고, 리팩토링을 끝내면 불안함은 다시 성취감으로 변한다.&lt;/p&gt;
&lt;h2&gt;&lt;a id=&quot;user-content-자기-피드백&quot; href=&quot;https://github.com/thisisyoungbin/woowa-writing-3/blob/thisisyoungbin/README.md#%EC%9E%90%EA%B8%B0-%ED%94%BC%EB%93%9C%EB%B0%B1&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;자기 피드백&lt;/h2&gt;
&lt;p&gt;사상누각. 기초가 튼튼하지 못하면 언젠간 무너진다. 개발자로서 돈을 버는 것과 같은 나중의 문제를 생각하기 보다, 매 단계마다 이뤄야하는 기본적인 것들을 확실히 이뤄야겠다고 생각한다. 그런 의미에서 난 지금 단계에서 해내야 하는 것들을 정확히 수행했을까? 코드리뷰에서 같은 피드백을 두 번 받진 않았을까? 강의를 통해 배운 내용이나 학습로그에 작성한 내용을 다른 사람에게 정확하게 설명할 수 있을까? 확신은 못하겠다. 그렇다면 내가 지금 해야 할 일은 확신을 가질 수 있도록 배운 내용들을 정확하게 내 것으로 만드는 것이다. 레벨 1이 끝나가는 시점에서, 불과 두 세달 전의 나와 비교하면 정말 많은 성장을 이뤘다. 항상 내 자신과 비교하면서 매 달, 매 레벨마다 성장했음을 느끼게 되길 바란다.&lt;/p&gt;</description>
      <category>생각과 일상/혼자의 생각</category>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/85</guid>
      <comments>https://young-bin.tistory.com/85#entry85comment</comments>
      <pubDate>Fri, 2 Apr 2021 09:23:23 +0900</pubDate>
    </item>
    <item>
      <title>Java 항해일지 - 15. 람다식</title>
      <link>https://young-bin.tistory.com/84</link>
      <description>&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;목표&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자바의 람다식에 대해 학습하세요.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;학습할 것 (필수)&lt;/span&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다식 사용법&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;함수형 인터페이스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Variable Capture&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메소드, 생성자 레퍼런스&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다란 무엇인가?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;람다 표현식&lt;/b&gt;이란 메서드로 전달할 수 있는 익명 함수를 단순화 한 것.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다의 특징 4가지&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;익명: 메서드 이름 없이 사용 가능해 메서드 이름 짓는 것에 대한 고민이 덜어진다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;함수: 특정 클래스에 종속되지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전달: 람다 표현식 자체를 메서드의 인수로 전달하거나 변수로 저장할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;간결성: 익명클래스보다 간결하다&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다표현식의 구성&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;( 람다 파라미터 ) -&amp;gt; { 람다 바디 }&lt;/span&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;표현식 스타일&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(parameters) -&amp;gt; expression&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;블록 스타일&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(parameters) -&amp;gt; { statements; }&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다 사용법&lt;/span&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;함수형 인터페이스&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스이다. 우테코 로또 미션에서 만들었던 로또 생성 인터페이스를 예시로 들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614957175359&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface LottoGenerator { 
    int MIN_LOTTO_NUMBER = 1; 
    int MAX_LOTTO_NUMBER = 45; 
    int FROM_INDEX = 0; 
    
    Lotto generate(); // 추상 메서드가 오직 하나 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다 표현식을 통해 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있어 람다 표현식 자체를 함수형 인터페이스의 인스턴스로 취급할 수 있다. 마찬가지로 로또 미션에서 예시를 가져왔다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614957304837&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static Lotto ofRandomLotto() { 
    // 람다 표현식으로 변경한 예제 
    LottoGenerator randomGenerator = () -&amp;gt; new Lotto(Arrays.asList(1,2,3,4,5,6)); 
    return randomGenerator.generate(); 
} 
    
public void makeRandomLottos(int autoAmount) { 
    for (int i = 0; i &amp;lt; autoAmount; i++) { 
        lottos.add(Lotto.ofRandomLotto()); 
    } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;함수 디스크립터&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;함수 디스크립터란 람다 표현식의 시그니처를 서술하는 메서드이다. Predicate, Consumer, Function, Supplier 등이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Predicate&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;제네릭 형식 T의 객체를 인수로 받아 불리언을 반환한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1614957359693&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Predicate&amp;lt;String&amp;gt; startsWithSeoul = (str) -&amp;gt; str.startsWith(&quot;Seoul&quot;); 

System.out.println(startsWithSeoul.test(&quot;SeoulCity&quot;)); // true 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Consumer&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;제네릭 형식 T의 객체를 인수로 받아 void를 반환하는 accept라는 추상 메서드를 정의한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1614957368557&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Consumer&amp;lt;String&amp;gt; printCity = (city) -&amp;gt; System.out.println(city); 

printCity.accept(&quot;Seoul&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Function&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;제네릭 형식 T의 객체를 인수로 받아서 제네릭 형식 R 객체를 반환하는 추상 메서드 apply를 정의한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1614957386615&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Function&amp;lt;Integer, Integer&amp;gt; plus10 = (number) -&amp;gt; number + 10; 

System.out.println(plus10.apply(10)); // 20 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그 외 다양한 함수 디스크립터&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;() -&amp;gt; T : Suuplier&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;T -&amp;gt; T : UnaryOperator&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(T, T) -&amp;gt; T : BinaryOperator&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(T, U) -&amp;gt; T : BiPredicate&amp;lt;L, T&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(T, U) -&amp;gt; void : BiConsumer&amp;lt;T, U&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(T, U) -&amp;gt; R : BiFunction&amp;lt;T, U, R&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;지역 변수 사용&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처할 수 있으나 지역 변수는 명시적으로 final로 선언되어 있어야 하거나, final로 선언된 변수와 똑같이 사용되어야 한다. 다시 말해&amp;nbsp;&lt;b&gt;한 번만 할당할 수 있는 지역 변수를 캡처(외부에서 정의된 변수를 사용하는 것)할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메서드 레퍼런스&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메서드 참조를 이용하면 람다 표현식의 인수를 더 깔끔히 전달할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정적 메서드 참조 (Integer::parseInt)&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다양한 형식의 인스턴스 메서드 참조 (String::length)&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 객체의 인스턴스 메서드 참조 (LottoNumber::getNumber)&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;생성자 레퍼런스&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;ClassName::new와 같이&lt;b&gt; 클래스명과 new 키워드를 이용&lt;/b&gt;해 기존 생성자의 참조를 만들 수 있다. 정적 메서드의 참조를 만드는 방법과 비슷하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다 표현식을 조합할 수 있는 유용한 메서드&lt;/span&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Comparator 조합&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;reversed(), thenComparing() 등을 조합해 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614957494818&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inventory.sort(comparing(Apple::getWeight)
        .reversed() // 역으로 정렬
        .thenComparing(Apple::getCountry)); 두 사과의 무게가 같으면 국가별 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Predicate 조합&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;negate(), or(), and() 등을 조합해 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614957517735&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;redApple.and(apple -&amp;gt; apple.getWeight() &amp;gt; 150)
    .or(apple -&amp;gt; GREEN.equals(apple.getColor())); 

// 빨간 사과 중 무게가 150 넘는 사과 혹은 초록 사과 (오른쪽 연결)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Function 조합&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;andThen(), compose() 등을 조합해 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614957548414&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Function&amp;lt;Integer, Integer&amp;gt; plus10 = (number) -&amp;gt; number + 10;
Function&amp;lt;Integer, Integer&amp;gt; multiply3 = (number) -&amp;gt; number * 3;

Function&amp;lt;Integer, Integer&amp;gt; multiply3AndPlus10 = plus10.compose(multiply3); 
System.out.println(multiply3AndPlus10.apply(4));  // 결과 22
        
        
Function&amp;lt;Integer, Integer&amp;gt; plus10AndMultiply3 = plus10.andThen(multiply3);
System.out.println(plus10AndMultiply3.apply(4);  // 결과 42&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정리&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다 표현식은 &lt;b&gt;익명 함수의 일종&lt;/b&gt;이다. 이름만 없을 뿐 파라미터 리스트, 바디, 반환형식을 가지며 예외도 던질 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;함수형 인터페이스&lt;/b&gt;는 &lt;b&gt;하나의 추상 메서드만을 정의&lt;/b&gt;하는 인터페이스이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급&lt;/b&gt;된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>Youngbin Kim</author>
      <guid isPermaLink="true">https://young-bin.tistory.com/84</guid>
      <comments>https://young-bin.tistory.com/84#entry84comment</comments>
      <pubDate>Sat, 6 Mar 2021 00:19:24 +0900</pubDate>
    </item>
  </channel>
</rss>