๐Ÿ’ Spring/Spring REST API

2) [test] JSON ์‘๋‹ต์œผ๋กœ 201์ด ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธ - ์Šคํ”„๋ง REST API

2020. 9. 9. 11:30
๋ชฉ์ฐจ
  1. Test 1) JSON ์‘๋‹ต์œผ๋กœ 201์ด ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธ.
  2. EventController ๊ตฌํ˜„
  3. EventControllerTest ํด๋ž˜์Šค ๊ตฌํ˜„
๋ฐ˜์‘ํ˜•

Test 1) JSON ์‘๋‹ต์œผ๋กœ 201์ด ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธ.

- Location ํ—ค๋”์— ์ƒ์„ฑ๋œ ์ด๋ฒคํŠธ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” URI ๋‹ด๊ฒจ ์žˆ๋Š”์ง€ ํ™•์ธ.
- id๋Š” DB์— ๋“ค์–ด๊ฐˆ ๋•Œ ์ž๋™ ์ƒ์„ฑ๋œ ๊ฐ’์œผ๋กœ ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธ.

 

์ผ๋‹จ Test ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ ๋‹ค

  • ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ํด๋ž˜์Šค์—์„œ ๋งฅ ๊ธฐ์ค€์œผ๋กœ ๋‹จ์ถ•ํ‚ค cmd + shift + T ๋ฅผ ๋ˆŒ๋Ÿฌ์ฃผ๋ฉด ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ์ƒ์„ฑํ•ด์ค€๋‹ค.

Location URI๋ฅผ ๋งŒ๋“ค๊ฑด๋ฐ, ์—ฌ๊ธฐ์„œ๋Š” HATEOAS๊ฐ€ ์ œ๊ณตํ•˜๋Š” linkTo() , methodOn()์„ ์‚ฌ์šฉํ•  ๊ฒƒ ์ด๋‹ค.

๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜ ํ• ๋•Œ, ObjectMapper๋ฅผ ์‚ฌ์šฉ.

 

EventController ๊ตฌํ˜„

package me.iseunghan.demoinflearnrestapi.events;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import java.net.URI;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_VALUE)

public class EventController {

    private final EventRepository eventRepository;

    public EventController(EventRepository eventRepository) {
        this.eventRepository = eventRepository;
    }

    @PostMapping
    public ResponseEntity createEvent(@RequestBody Event event) {
        Event newEvent = this.eventRepository.save(event);
        //link๋ฅผ ์ƒ์„ฑํ• ๋•,HATEOAS๊ฐ€ ์ œ๊ณตํ•˜๋Š” linkTo(), methodOn()์„ ์‚ฌ์šฉ
        //methodOn()์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์ด์œ  : URL์ด ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— ๋ถ™์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœ ์•ˆํ•ด๋„ ๋œ๋‹ค.
        URI createUri = linkTo(EventController.class).slash(newEvent.getId()).toUri();
        return ResponseEntity.created(createUri).body(event); //201์‘๋‹ต์„ Uri์— ๋‹ด์•„์„œ ๋ฆฌํ„ด์‹œํ‚จ๋‹ค.
    }
}

์œ„์—์„œ methodOn()์„ ์•ˆ์“ฐ๊ณ  linkTo(T class) ๋กœ ๋ฐ”๋กœ ๋“ค์–ด๊ฐ„ ์ด์œ ๋Š”, ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— ๋ถ™์€ @RequestMapping ๋•Œ๋ฌธ์ด๋‹ค.
Link๋ฅผ ๋งŒ๋“ค๋•Œ์—๋Š” @PostMapping("/api/events") ์ฒ˜๋Ÿผ ๋ฉ”์†Œ๋“œ๋ ˆ๋ฒจ์— ๋ถ™์€ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์ฒ˜๋Ÿผ ์ด๋ฃจ์–ด์ง€๋Š”๋ฐ,

@Controller
public class Controller {
 ...

@PostMapping("/api/events")
public ResponseEntity createEvent(@RequestBody Event event){
    URI createUri = linkTo(methodOn(EventController.class).createEvent(null)).slash(newEvent.getId()).toUri();
    ...
}

๋งŒ์•ฝ ์œ„์™€ ๊ฐ™์ด URL์ด ๋ฉ”์†Œ๋“œ ๋ ˆ๋ฒจ์— ๋ถ™์—ˆ๋‹ค๋ฉด methodOn() ๊นŒ์ง€ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

EventControllerTest ํด๋ž˜์Šค ๊ตฌํ˜„

package me.iseunghan.demoinflearnrestapi.events;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * MockMvc
 * - ์Šคํ”„๋ง MVC ํ…Œ์ŠคํŠธ ํ•ต์‹ฌ ํด๋ž˜์Šค
 * - ์›น์„œ๋ฒ„๋ฅผ ๋„์šฐ์ง€ ์•Š๊ณ ๋„ ์Šคํ”„๋ง Mvc(DispatherServlet)๊ฐ€
 * ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ์ž์ฃผ ์“ฐ์ž„.
 */
@RunWith(SpringRunner.class)
@WebMvcTest
public class EventControllerTests {

    @Autowired
    MockMvc mockMvc;

    @Autowired
    ObjectMapper objectMapper;

    //MockBean์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ์ด์œ  : @WebMvcTest๋Š” ์›น ๊ด€๋ จ ๋นˆ๋“ค๋งŒ ๋“ฑ๋กํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ์ง์ ‘ ๋“ฑ๋กํ•ด์•ผํ•œ๋‹ค.
    //(์ฃผ์˜) ๊ธฐ์กด ๋นˆ์„ ํ…Œ์ŠคํŠธ์šฉ ๋นˆ์ด ๋Œ€์ฒด ํ•œ๋‹ค. 
    @MockBean
    EventRepository eventRepository;

    @Test
    public void createEvent() throws Exception {
        Event event = Event.builder()
                .name("Spring")
                .description("REST API Development with Spring")
                .beginEnrollmentDateTime(LocalDateTime.of(2020, 9, 7, 2, 45))
                .closeEnrollmentDateTime(LocalDateTime.of(2020,9,8,2,45))
                .beginEventDateTime(LocalDateTime.of(2020,9,9,2,45))
                .endEventDateTime(LocalDateTime.of(2020,9,10,2,45))
                .basePrice(100)
                .maxPrice(200)
                .limitOfEnrollment(100)
                .location("๊ฐ•๋‚จ์—ญ 1๋ฒˆ ์ถœ๊ตฌ")
                .build();
        event.setId(10);

        Mockito.when(eventRepository.save(event)).thenReturn(event);

        mockMvc.perform(post("/api/events/")
                .contentType(MediaType.APPLICATION_JSON)//๋ณธ๋ฌธ ์š”์ฒญ์— json์„ ๋‹ด์•„์„œ ๋ณด๋‚ด๊ณ  ์žˆ๋‹ค๊ณ  ์•Œ๋ ค์คŒ.
                .accept(MediaTypes.HAL_JSON)//HAL_JSON์œผ๋กœ ๋ฐ›๋Š”๋‹ค.
                .content(objectMapper.writeValueAsString(event)))//์š”์ฒญ ๋ณธ๋ฌธ์— json์œผ๋กœ ๋ณ€ํ™˜ํ›„ ๋„ฃ์–ด์ค€๋‹ค
            .andDo(print())//์–ด๋–ค ์‘๋‹ต๊ณผ ์š”์ฒญ์„ ๋ฐ›์•˜๋Š”์ง€ ํ™•์ธ๊ฐ€๋Šฅ.
            .andExpect(status().isCreated())//201์š”์ฒญ์ด ๋“ค์–ด์™”๋Š”์ง€?
            .andExpect(jsonPath("id").exists()) //json์— id๊ฐ€ ์žˆ๋Š”์ง€?
            .andExpect(header().exists(HttpHeaders.LOCATION)) //header์— Location์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ์žˆ๋Š”์ง€
            .andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_VALUE))//content-type์— "application/hal+json"๊ฐ€ ๋‚˜์˜ค๋Š”์ง€?
        ;

    }

}

mockMvc.perform(post("/api/events/") post๋ฐฉ์‹์œผ๋กœ "api/events/" URL๋ฅผ ๋„˜๊ฒจ์„œ ํ˜ธ์ถœํ•œ๋‹ค.
.contentType(MediaType.APPLICATION_JSON) ๋ณธ๋ฌธ ์š”์ฒญ์— JSON์„ ๋‹ด์•„์„œ ๋ณด๋‚ด๊ณ  ์žˆ๋‹ค๊ณ  ๋ช…์‹œ
.accept(MediaType.HAL_JSON) HAL + JSON ํƒ€์ž…์œผ๋กœ ๋ฐ›๊ฒ ๋‹ค๋Š” ์˜๋ฏธ
.content(objectMapper.writeValueAsString(event)) ์š”์ฒญ ๋ณธ๋ฌธ์— event ๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ๋„ฃ์–ด์ค€๋‹ค.
.andDo(print()) ์‘๋‹ต, ์š”์ฒญ print
.andExpect(status().isCreate()) 201์š”์ฒญ์ด ๋“ค์–ด์™”๋Š”๊ฐ€
.andExpect(jsonPath("id").exists()) json์— id๊ฐ€ ์กด์žฌํ•˜๋Š”๊ฐ€?

 

@MockBean, Mokito.when().then()

Test๋„์ค‘ Spring์—์„œ ์–ด๋А ์˜์กด์„ฑ๋„ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด, Mockito์˜ @Mock์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด, ์šฐ๋ฆฌ๊ฐ€ ๋ฐ”๋ผ๋Š” ๋น ๋ฅด๊ณ  ์˜์กด์„ฑ์—†๋Š” unit test๋กœ ๊ฐ€๋Š” ๋ฐฉํ–ฅ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, test ๋„์ค‘ spring container๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” bean๋“ค ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ Mockingํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด @MockBean์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @MockBean์€ ApplicationContext์— mock๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  mock๊ฐ์ฒด๋Š” ๊ฐ™์€ type์˜ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” Bean๋“ค์„ ๋Œ€์‹ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, @MockBean์œผ๋กœ ๋Œ€์‹ ๋œ context๋Š” ๋‹ค๋ฅธ context์ด๋ฏ€๋กœ Spring Boot๋Š” ApplicationContext๋ฅผ test ์ดˆ๊ธฐํ™” ์ค‘์— ๋‹ค์‹œ ๋กœ๋”ฉํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

 

ํ…Œ์ŠคํŠธ ์‹คํŒจ

 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ๋Š” eventRepoitory๋ฅผ Mock ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ–ˆ๋Š”๋ฐ, @MockBean์„ ์ด์šฉํ•ด์„œ ์ƒ์„ฑํ•œ ๊ฐ์ฒด์˜ ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ฆฌํ„ด ํƒ€์ž…์ด ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ null์„ ๋ฆฌํ„ดํ•˜๊ณ , ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ํƒ€์ž…์ธ ๊ฒฝ์šฐ ๊ธฐ๋ณธ ๊ฐ’์„ ๋ฆฌํ„ดํ•œ๋‹ค. ๊ทธ๋ฆฌํ•˜์—ฌ, ํ…Œ์ŠคํŠธ์—์„œ Controller์˜ UrI๋งŒ๋“œ๋Š” ๊ณผ์ •์—์„œ
newEvent.getId() ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ณผ์ •์—์„œ newEvent๊ฐ€ null์ด๊ธฐ ๋•Œ๋ฌธ์— NullpointerException์ด ๋ฐœ์ƒํ•˜์—ฌ ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์ง€๊ฒŒ ๋œ๋‹ค.

์ด๋Ÿด๋•, Mockito๋ฅผ ์ด์šฉํ•œ๋‹ค..

Mockito๋Š” Mock ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ์•Œ๋งž์€ ๊ฐ’์„ ๋ฆฌํ„ดํ•˜๋Š” ์Šคํ…์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” when - then์˜ ํ˜•์‹์„ ๋„๊ณ 
๊ทธ๋ฆฌํ•˜์—ฌ, when : eventRepository.save ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ ๋ ๋•Œ! -> thenReturn : event ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

 

์ถœ์ฒ˜ : https://javacan.tistory.com/entry/MocktestUsingMockito

๋ฐ˜์‘ํ˜•
์ €์ž‘์žํ‘œ์‹œ (์ƒˆ์ฐฝ์—ด๋ฆผ)
  1. Test 1) JSON ์‘๋‹ต์œผ๋กœ 201์ด ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธ.
  2. EventController ๊ตฌํ˜„
  3. EventControllerTest ํด๋ž˜์Šค ๊ตฌํ˜„
'๐Ÿ’ Spring/Spring REST API' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • 5) [test] ์ž…๋ ฅ๊ฐ’ ์ œํ•œํ•˜๊ธฐ (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์œผ๋กœ ๊ฒ€์‚ฌ) - ์Šคํ”„๋ง REST API
  • 4) [test] ์ž…๋ ฅ๊ฐ’ ์ œํ•œํ•˜๊ธฐ (Bad_Request ๋ฐœ์ƒ) - ์Šคํ”„๋ง REST API
  • 3) [test] ์ž…๋ ฅ๊ฐ’ ์ œํ•œํ•˜๊ธฐ (๋ฌด์‹œํ•˜๋Š” ๋ฐฉ๋ฒ•) - ์Šคํ”„๋ง REST API
  • 1) Spring-Boot ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ - ์Šคํ”„๋ง REST API
iseunghan
iseunghan
๊พธ์ค€ํ•˜๊ฒŒ ์—ด์‹ฌํžˆ..
iseunghan
iseunghan

๊ณต์ง€์‚ฌํ•ญ

  • ์–ด์ œ๋ณด๋‹ค ๋‚˜์€ ์˜ค๋Š˜์ด ๋˜๊ธฐ ์œ„ํ•ด ๐Ÿ”ฅ
  • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (261)
    • ๐Ÿ’ Spring (14)
      • ๊ฐœ๋… ๋ฐ ์ดํ•ด (2)
      • Spring ํ•ต์‹ฌ ๊ธฐ์ˆ  (24)
      • Spring REST API (8)
      • Spring MVC, DB ์ ‘๊ทผ ๊ธฐ์ˆ  (7)
      • Spring Security (23)
      • Spring in Action (1)
    • ๐ŸŒป JAVA (84)
      • ์ž๋ฐ” ORM ํ‘œ์ค€ JPA ํ”„๋กœ๊ทธ๋ž˜๋ฐ (20)
      • ์•Œ๊ณ ๋ฆฌ์ฆ˜, ์ž๋ฃŒ๊ตฌ์กฐ (13)
      • ๋””์ž์ธ ํŒจํ„ด (7)
      • ์ •๋ฆฌ์ •๋ฆฌ์ •๋ฆฌ (43)
      • JUnit (1)
    • ๐Ÿ”– Snippets (3)
      • Javascript (3)
    • โš™๏ธ Devops (22)
      • โ› Git (11)
      • ๐Ÿณ Docker (6)
      • ๐Ÿง Linux (3)
      • ๐ŸŒˆ Jenkins (1)
      • ๐Ÿ“ฌ Kafka (1)
    • ๐Ÿ’ฌ ETC.. (4)
      • ๐Ÿ’ป macOS (2)
    • ๐ŸŒง๏ธ ORM (2)
      • JPA (2)
    • ๐Ÿ Python (2)
    • ๐Ÿ“š Databases (15)
      • ์˜ค๋ผํด๋กœ ๋ฐฐ์šฐ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐœ๋ก ๊ณผ ์‹ค์Šต(2ํŒ) (3)
      • RealMySQL 8.0 (8)
    • ๐Ÿ”ฅ Computer Science (5)
      • ๐Ÿ“ก ๋„คํŠธ์›Œํฌ (5)
    • ๐Ÿท๏ธ ํ˜‘์—… (1)
    • ๐Ÿ“œ ์ฝ”๋”ฉํ…Œ์ŠคํŠธ (38)
      • BAEKJOON\์ˆ˜ํ•™ 1, ์ˆ˜ํ•™ 2 (8)
      • BAEKJOON\์žฌ๊ท€ (5)
      • BAEKJOON\๋ธŒ๋ฃจํŠธ ํฌ์Šค (3)
      • BAEKJOON\์ •๋ ฌ (1)
      • BAEKJOON\๋ฐฑํŠธ๋ž˜ํ‚น (5)
      • BAEKJOON\BFS, DFS (6)
      • BAEKJOON\์ด๋ถ„ํƒ์ƒ‰ (1)
      • BAEKJOON\๋‹ค์ด๋‚˜๋ฏน ํ”„๋กœ๊ทธ๋ž˜๋ฐ (9)
      • BAEKJOON\๊ทธ๋ฆฌ๋”” ์•Œ๊ณ ๋ฆฌ์ฆ˜ (0)
    • โœจ ISEUNGHAN (1)

์ธ๊ธฐ ๊ธ€

์ตœ๊ทผ ๊ธ€

์ „์ฒด
์˜ค๋Š˜
์–ด์ œ
๋ฐ˜์‘ํ˜•
hELLO ยท Designed By ์ •์ƒ์šฐ.
iseunghan
2) [test] JSON ์‘๋‹ต์œผ๋กœ 201์ด ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธ - ์Šคํ”„๋ง REST API
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”

๊ฐœ์ธ์ •๋ณด

  • ํ‹ฐ์Šคํ† ๋ฆฌ ํ™ˆ
  • ํฌ๋Ÿผ
  • ๋กœ๊ทธ์ธ

๋‹จ์ถ•ํ‚ค

๋‚ด ๋ธ”๋กœ๊ทธ

๋‚ด ๋ธ”๋กœ๊ทธ - ๊ด€๋ฆฌ์ž ํ™ˆ ์ „ํ™˜
Q
Q
์ƒˆ ๊ธ€ ์“ฐ๊ธฐ
W
W

๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€

๊ธ€ ์ˆ˜์ • (๊ถŒํ•œ ์žˆ๋Š” ๊ฒฝ์šฐ)
E
E
๋Œ“๊ธ€ ์˜์—ญ์œผ๋กœ ์ด๋™
C
C

๋ชจ๋“  ์˜์—ญ

์ด ํŽ˜์ด์ง€์˜ URL ๋ณต์‚ฌ
S
S
๋งจ ์œ„๋กœ ์ด๋™
T
T
ํ‹ฐ์Šคํ† ๋ฆฌ ํ™ˆ ์ด๋™
H
H
๋‹จ์ถ•ํ‚ค ์•ˆ๋‚ด
Shift + /
โ‡ง + /

* ๋‹จ์ถ•ํ‚ค๋Š” ํ•œ๊ธ€/์˜๋ฌธ ๋Œ€์†Œ๋ฌธ์ž๋กœ ์ด์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ํ‹ฐ์Šคํ† ๋ฆฌ ๊ธฐ๋ณธ ๋„๋ฉ”์ธ์—์„œ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.