🚧

Application μ½”λ“œμ™€ ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±ν•˜κΈ°

purpplee 2021. 12. 21. 11:06

Application μ½”λ“œ μž‘μ„±ν•˜κΈ°

Application ν΄λž˜μŠ€λŠ” springboot ν”„λ‘œμ νŠΈμ˜ 메인 ν΄λž˜μŠ€κ°€ λœλ‹€. src/main/java 에 com.study.springbootaws νŒ¨ν‚€μ§€λ₯Ό μƒμ„±ν•œλ‹€. νŒ¨ν‚€μ§€λͺ…은 보톡 νšŒμ‚¬ λ„λ©”μΈμ˜ μ—­μˆœμœΌλ‘œ λ§Œλ“ λ‹€. κ·Έ μ•„λž˜μ— Application class λ₯Ό μƒμ„±ν•˜κ³  μ•„λž˜μ²˜λŸΌ λ‚΄μš©μ„ μž‘μ„±ν•œλ‹€.

 

src/main/java/com.study.springbootaws/Application

package com.study.springbootaws;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//ν”„λ‘œμ νŠΈμ˜ μ‹œμž‘μ . μŠ€ν”„λ§λΆ€νŠΈ, Bean 읽기/생성 λ“± μžλ™μœΌλ‘œ μ„€μ •ν•΄μ£ΌλŠ” μ–΄λ…Έν…Œμ΄μ…˜
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //λ‚΄μž₯ WAS μ‹€ν–‰
        SpringApplication.run(Application.class, args);
    }
}

 

@SpringbootApplicaiton μ–΄λ…Έν…Œμ΄μ…˜ 덕뢄에 μŠ€ν”„λ§λΆ€νŠΈμ˜ μžλ™μ„€μ •, Bean 읽기/생성을 λͺ¨λ‘ μžλ™μœΌλ‘œ μ„€μ •λœλ‹€. 또 이 μ–΄λ…Έν…Œμ΄μ…˜μ΄ μžˆλŠ” μœ„μΉ˜λΆ€ν„° ν”„λ‘œμ νŠΈλ₯Ό μ½μ–΄κ°€λ―€λ‘œ, Application ν΄λž˜μŠ€λŠ” 항상 μ΅œμƒλ‹¨μ— μœ„μΉ˜ν•΄μ•Όλ§Œ ν•œλ‹€.

 

SpringApplication.run λ©”μ†Œλ“œλŠ” λ‚΄μž₯ WAS λ₯Ό μ‹€ν–‰ν•œλ‹€. μ΄λ ‡κ²Œ 되면 μ„œλ²„μ— 톰캣을 μ„€μΉ˜ν•  ν•„μš” 없이 μŠ€ν”„λ§ λΆ€νŠΈλ‘œ λ§Œλ“€μ–΄μ§„ μ‹€ν–‰νŒŒμΌ(Jar) 둜 μ‹€ν–‰ν•˜λ©΄ λœλ‹€. λ‚΄μž₯ WAS λ₯Ό 쓰지 μ•Šμ•„λ„ λ˜μ§€λ§Œ, μ–Έμ œ μ–΄λ””μ„œλ‚˜ 같은 ν™˜κ²½μ—μ„œ μŠ€ν”„λ§ λΆ€νŠΈλ₯Ό 배포할 수 있기 λ•Œλ¬Έμ— μ‚¬μš©μ΄ ꢌμž₯λœλ‹€.

 

 

ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±ν•˜κΈ°

ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ 무엇이고 μ™œ ν•„μš”ν•œμ§€λŠ” πŸ‘‰  TDD (Test Driven Development), ν…ŒμŠ€νŠΈ 주도 κ°œλ°œμ΄λž€? μ„ μ°Έκ³ ν•΄μ£Όμ„Έμš”.

 

src/main/java/com.study.springbootaws/web/HelloController

λ¨Όμ € ν…ŒμŠ€νŠΈν•  컨트둀러λ₯Ό μœ„ 경둜λ₯Ό 따라 λ§Œλ“ λ‹€. 참고둜 @RestController λŠ” 컨트둀러λ₯Ό JSON 을 λ°˜ν™˜ν•˜λŠ” 컨트둀러둜 λ§Œλ“€μ–΄μ€€λ‹€. μ˜ˆμ „μ—” JSON을 λ°˜ν™˜ν•˜λ €λ©΄ @ResponseBody λ₯Ό 각 λ©”μ†Œλ“œλ§ˆλ‹€ μ„ μ–Έν•΄μ•Ό ν–ˆλ‹€. @GetMapping 도 μ΅œμ‹  문법이닀. λŒ€μ‹  @GetMapping은 λ©”μ†Œλ“œ λ‹¨μœ„μ—μ„œλ§Œ μ“Έ 수 있고 μ΄μ „μ˜ @RequestMapping 은 클래슀 λ‹¨μœ„κΉŒμ§€ μ“Έ 수 μžˆλ‹€.

package com.study.springbootaws.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

//JSON 을 λ°˜ν™˜ν•˜λŠ” 컨트둀러둜 λ§Œλ“€μ–΄μ€Œ
@RestController
public class HelloController {
    @GetMapping("/hello")   // = @RequestMapping(method = RequestMethod.GET)
    public String hello() {
        return "hello";
    }
}

 

build.gradle

JUnit 은 Java ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±μ„ λ„μ™€μ£ΌλŠ” ν”„λ ˆμž„μ›Œν¬λ‹€. μ±…μ—μ„œλŠ” JUnit4둜 μ§„ν–‰ν•˜μ§€λ§Œ λ‚˜λŠ” JUnit5둜 진행할 것이닀. λ§Œμ•½ JUnit4λ₯Ό μ΄μš©ν•˜κ³  μ‹Άλ‹€λ©΄ build.gradle 에 μ•„λž˜ μ½”λ“œλ₯Ό μΆ”κ°€ν•΄μ£Όκ³  ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό μ±…κ³Ό 같이 μ“°λ©΄ λœλ‹€.

dependencies {
	...
    implementation 'junit:junit:4.13.1'
    ...
}

JUnit5 λŠ” Springboot 2.2.x 버전에 λ‚΄μž₯λ˜μ–΄ μžˆμ–΄μ„œ μΆ”κ°€ν•  ν•„μš”κ°€ μ—†λ‹€.

 

src/test/java/com.study.springbootaws/web/HelloControllerTest

이제 μœ„ κ²½λ‘œμ— 컨트둀러λ₯Ό ν…ŒμŠ€νŠΈν•  ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό μž‘μ„±ν•˜μž. 참고둜 @RunWith 은 JUnit4 의 λ¬Έλ²•μœΌλ‘œ, JUnit ν…ŒμŠ€νŠΈλ₯Ό μŠ€ν”„λ§ λΆ€νŠΈ ν…ŒμŠ€νŠΈλ‘œ ν™•μž₯μ‹œμΌœμ£ΌλŠ” 역할을 ν•œλ‹€. JUnit5 둜 λ„˜μ–΄μ˜€λ©° @ExtendWith λΌλŠ” μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ λ°”λ€Œμ—ˆμœΌλ‚˜ @SrpringbootTest, @WebMvcTest 같은 μ–΄λ…Έν…Œμ΄μ…˜μ— 이미 λ‚΄μž₯λ˜μ–΄ μžˆμœΌλ―€λ‘œ μƒλž΅ν•  수 μžˆλ‹€.

package com.study.springbootaws.web;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

//Springboot의 λͺ¨λ“  λΉˆμ„ λ‘œλ“œν•˜μ§€ μ•Šκ³  controller κ΄€λ ¨ μ½”λ“œλ§Œ ν…ŒμŠ€νŠΈν•  λ•Œ μ‚¬μš©. 단, Service, Component, RepositoryλŠ” μ‚¬μš©x
@WebMvcTest(HelloController.class)
public class HelloControllerTest {
    @Autowired  //Bean μžλ™ μ£Όμž…
    private MockMvc mvc;    //μ›Ή API ν…ŒμŠ€νŠΈ(GET, POST λ“±)μ‹œ μ‚¬μš©. μŠ€ν”„λ§ MVC ν…ŒμŠ€νŠΈμ˜ μ‹œμž‘μ 

    @Test
    public void returnHello() throws Exception {
        String hello = "hello";

        mvc.perform(get("/hello"))      // /hello μ£Όμ†Œλ‘œ get μš”μ²­
                .andExpect(status().isOk())         //κ²°κ³Ό 쀑 HTTP Header 의 status code 검증.
                .andExpect(content().string(hello));    //κ²°κ³Ό 쀑 λ³Έλ¬Έ 검증.
    }
}

 

ν…ŒμŠ€νŠΈ μ‹€ν–‰

μ•„λž˜ μ΄λ―Έμ§€μ²˜λŸΌ Run 'returnHello()' λ₯Ό ν΄λ¦­ν•˜λ©΄ 터미널에 κ²°κ³Όκ°€ λ‚˜νƒ€λ‚œλ‹€. λ§Œμ•½ Execution failed for task ':test' μ—λŸ¬κ°€ λ‚œλ‹€λ©΄ πŸ‘‰  [πŸ”§ νŠΈλŸ¬λΈ”μŠˆνŒ…] IntelliJ JUnit ν…ŒμŠ€νŠΈ 쀑 Execution failed for task ':test' μ—λŸ¬ κ²Œμ‹œλ¬Όμ„ ν™•μΈν•˜μž.

ν…ŒμŠ€νŠΈκ°€ 톡과됐닀!

 

둬볡으둜 λ³€ν™˜ν•˜κ³  ν…ŒμŠ€νŠΈν•΄λ³΄κΈ°

μ§€κΈˆμ€ μž‘μ€ 규λͺ¨μ§€λ§Œ, 큰 규λͺ¨μ˜ ν”„λ‘œμ νŠΈμ— 둬볡을 μΆ”κ°€ν•˜λ©΄ μ–΄λ–€ μ½”λ“œμ—μ„œ μ—λŸ¬κ°€ λ‚˜λŠ”μ§€ μ•ˆ λ‚˜λŠ”μ§€ ν™•μΈν•˜κΈ° 쉽지 μ•Šμ„ 것이닀. κ·ΈλŸ¬λ‚˜ ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ 우리의 μ½”λ“œλ₯Ό μ§€μΌœμ£Όλ―€λ‘œ, ν…ŒμŠ€νŠΈ μ½”λ“œλ§Œ 돌렀보면 λ¬Έμ œκ°€ μƒκΈ°λŠ”μ§€ μ•Œ 수 μžˆλ‹€. 둬볡으둜 λ³€ν™˜ν•œ ν›„ ν…ŒμŠ€νŠΈλ₯Ό ν•œλ²ˆ ν•΄λ³Ό 것이닀.

 

둬볡 μ„€μ •

둬볡은 μžλ°” κ°œλ°œμ— 자주 μ‚¬μš©ν•˜λŠ” getter, setter, construct, toStrign 등을 μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μžλ™ μƒμ„±ν•΄μ£ΌλŠ” νŽΈλ¦¬ν•œ λΌμ΄λΈŒλŸ¬λ¦¬λ‹€. 이전에 plugins μ—μ„œ μ„€μΉ˜ν–ˆλ˜ κ²ƒμ²˜λŸΌ lombok ν”ŒλŸ¬κ·ΈμΈμ„ μ„€μΉ˜ν•΄μ€€λ‹€. λ‚˜λŠ” 이미 μ„€μΉ˜λ˜μ–΄ μžˆμ–΄μ„œ 과정은 μƒλž΅ν•˜κ² λ‹€. 단, 둬볡 ν”ŒλŸ¬κ·ΈμΈμ€ ν•œλ²ˆλ§Œ μ„€μΉ˜ν•˜λ©΄ λ˜μ§€λ§Œ 둬볡 μ‚¬μš© 섀정은 ν”„λ‘œμ νŠΈλ§ˆλ‹€ ν•΄μ€˜μ•Ό ν•˜λ―€λ‘œ μ•„λž˜μ²˜λŸΌ μ„€μ •ν•΄μ€€λ‹€.

build.gradle

dependencies {
	...
	//gradle 이 버전 4 라면
    implementation 'org.projectlombok:lombok'
    
    //gradle 이 버전 5 라면
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
	...
}

 

 

src/main/java/com.study.springbootaws/web/dto/HelloResponseDto

둬볡이 μ‚¬μš©λœ Dto λ₯Ό μœ„ κ²½λ‘œμ— μΆ”κ°€ν•΄μ€€λ‹€.

package com.study.springbootaws.web.dto;


import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter     //λͺ¨λ“  ν•„λ“œ getter λ©”μ†Œλ“œ 생성
@RequiredArgsConstructor    //final μ΄λ‚˜ @NonNull 값인 λͺ¨λ“  ν•„λ“œλ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ°›λŠ” μƒμ„±μž 생성
public class HelloResponseDto {
    private final String name;
    private final int amount;
}

 

src/test/java/com.study.springbootaws/web/dto/HelloResponseDtoTest

μœ„ κ²½λ‘œμ— ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μΆ”κ°€ν•΄μ£Όκ³  μ‹€ν–‰ν•œλ‹€. λ§Œμ•½ not initialized in the default constructor μ—λŸ¬κ°€ λ‚œλ‹€λ©΄ gradle 버전에 맞게 lokmok을 μΆ”κ°€ν–ˆλŠ”μ§€ ν™•μΈν•˜μž. assertThat 은 λ©”μ†Œλ“œ 인자둜 받은 value λ₯Ό 검증해쀀닀. 참고둜 Junit 의 κΈ°λ³Έ assertThat 이 μ•„λ‹Œ assertj λ₯Ό μ‚¬μš©ν–ˆλ‹€. μ΄μœ λŠ” assertj λŠ” Assert 클래슀λ₯Ό λ°˜ν™˜ν•˜λ―€λ‘œ λ©”μ†Œλ“œ μžλ™μ™„μ„±μ΄ 되고 체이닝 λ©”μ†Œλ“œ νŒ¨ν„΄μ„ μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμ΄λ‹€.

package com.study.springbootaws.web.dto;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

public class HelloResponseDtoTest {
    @Test
    public void lombokTest() {
        String name = "test";
        int amount = 1000;

        HelloResponseDto dto = new HelloResponseDto(name, amount);

        //asseartThat 은 λ©”μ†Œλ“œ 인자둜 받은 value 비ꡐ 및 κ²€μ¦ν•΄μ€Œ.
        //isEqualTo 말고도 greaterThan, lessThan λ“±μ˜ 체이닝도 κ°€λŠ₯.
        assertThat(dto.getName()).isEqualTo(name);
        assertThat(dto.getAmount()).isEqualTo(amount);
    }
}

 

λ§ˆμ°¬κ°€μ§€λ‘œ ν…ŒμŠ€νŠΈν•˜λ©΄ 잘 λ™μž‘ν•œλ‹€.

 

HelloController 에 HelloResponseDto λ₯Ό μ‚¬μš©ν•˜κ³  ν…ŒμŠ€νŠΈν•΄λ³΄κΈ°

HelloResponseDto κ°€ 문제 없이 λ™μž‘ν•¨μ„ ν™•μΈν–ˆμœΌλ‹ˆ HelloController 에 μ μš©ν•΄λ³΄μž. 

src/main/java/com.study.springbootaws/web/HelloController

HelloControlelr ν΄λž˜μŠ€μ— HelloReponseDto λ₯Ό μ‚¬μš©ν•˜λŠ” μƒˆλ‘œμš΄ 경둜λ₯Ό μ•„λž˜μ²˜λŸΌ μΆ”κ°€ν•˜μž.

import org.springframework.web.bind.annotation.RequestParam;

...

@GetMapping("/hello/dto")
public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount) {
	return new HelloResponseDto(name, amount);
}

...

 

 

src/test/java/com.study.springbootaws/web/HelloControllerTest

HelloControllerTest 에 μΆ”κ°€ν•œ helloDto λ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€. jsonPath λŠ” 응닡값을 json ν•„λ“œλ‘œ 검증할 수 μžˆλŠ” λ©”μ†Œλ“œλ‹€.

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

...

@Test
public void returnHelloDto() throws Exception {
	String name = "hello";
	int amount = 1000;

	mvc.perform(get("/hello/dto")
           .param("name", name)
           .param("amount", String.valueOf(amount)))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.name").value(name))      //$ => 루트, .name => ['name'] ν•˜μœ„μš”μ†Œ
           .andExpect(jsonPath("$.amount").value(amount));
}

...

 

ν…ŒμŠ€νŠΈλ₯Ό 돌리면 λ¬΄μ‚¬νžˆ ν†΅κ³Όν•œλ‹€!

 

λ°˜μ‘ν˜•