[Testing] TDD/ API ๋ฌธ์„œํ™”

Updated:

Categories:

Tags: , , ,

๐Ÿ“Œ ๊ฐœ์ธ์ ์ธ ๊ณต๊ฐ„์œผ๋กœ ๊ณต๋ถ€๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ๋ณต์Šตํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋ธ”๋กœ๊ทธ์ž…๋‹ˆ๋‹ค.
์ •ํ™•ํ•˜์ง€ ์•Š์€ ์ •๋ณด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ๋ฐ”๋ž๋‹ˆ๋‹ค :๐Ÿ˜ธ
[ํ‹€๋ฆฐ ๋‚ด์šฉ์€ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ๋ณต๋ฐ›์œผ์‹ค๊ฑฐ์—์š”]

TDD(Test Driven Development, ํ…Œ์ŠคํŠธ ์ฃผ๋„๊ฐœ๋ฐœ)

TDD์˜ ๊ฐœ๋…์„ ํ•œ๋งˆ๋””๋กœ ์š”์•ฝํ•˜์ž๋ฉด โ€˜ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ํ•˜๊ณ  ๊ตฌํ˜„์€ ๊ทธ๋‹ค์Œ์— ํ•œ๋‹คโ€™์ด๋‹ค.

TDD๋Š” ๋ชจ๋“  ์ž‘์—… ์ด์ „์— ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ €ํ•œ๋‹ค.

  1. ์ดํ•ด ๋‹น์‚ฌ์ž๋“ค ๊ฐ„์— ์ˆ˜์ง‘๋œ ์š”๊ตฌ ์‚ฌํ•ญ๊ณผ ์„ค๊ณ„๋œ ํ™”๋ฉด(UI ์„ค๊ณ„์„œ ๋“ฑ) ๋“ฑ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ๋„์ถœํ•ฉ๋‹ˆ๋‹ค.
  2. ๋„์ถœ๋œ ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ๋ฐ›์•„๋“ค์ด๋Š” ์—”๋“œํฌ์ธํŠธ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง, ๋ฐ์ดํ„ฐ ์•ก์„ธ์Šค๋ฅผ ์œ„ํ•œ ํด๋ž˜์Šค์™€ ์ธํ„ฐํŽ˜์ด์Šค ๋“ฑ์„ ์„ค๊ณ„ํ•ด์„œ ํฐ ๊ทธ๋ฆผ์„ ๊ทธ๋ ค๋ด…๋‹ˆ๋‹ค.
  3. ํด๋ž˜์Šค ์„ค๊ณ„๋ฅผ ํ†ตํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ ํฐ ๊ทธ๋ฆผ์„ ๊ทธ๋ ค๋ณด์•˜๋‹ค๋ฉด ํด๋ž˜์Šค์™€ ์ธํ„ฐํŽ˜์ด์Šค์˜ ํฐ ํ‹€์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
  4. ํด๋ž˜์Šค์™€ ์ธํ„ฐํŽ˜์ด์Šค์˜ ํฐ ํ‹€์ด ์ž‘์„ฑ๋˜์—ˆ๋‹ค๋ฉด ํด๋ž˜์Šค์™€ ์ธํ„ฐํŽ˜์ด์Šค ๋‚ด์— ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜๋ฉด์„œ ์„ธ๋ถ€ ๋™์ž‘์„ ๊ณ ๋ฏผํ•˜๊ณ , ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  5. ํ•ด๋‹น ๋ฉ”์„œ๋“œ์˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„์ด ๋๋‚ฌ๋‹ค๋ฉด ๊ตฌํ˜„ํ•œ ๊ธฐ๋Šฅ์ด ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.
  6. ํ…Œ์ŠคํŠธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ๋ฅผ ๋””๋ฒ„๊น…ํ•˜๋ฉด์„œ ๋ฌธ์ œ์˜ ์›์ธ์„ ์ฐพ์Šต๋‹ˆ๋‹ค.

โœ”๏ธ TDD ๊ด€์ ์—์„œ ๋‘๋“œ๋Ÿฌ์ง€๋Š” ์  ํ•œ ๊ฐ€์ง€๋Š” 3๋ฒˆ ~ 6๋ฒˆ์˜ ๊ณผ์ •์—์„œ ๊ตฌํ˜„์ด ๋จผ์ €๊ณ , ํ…Œ์ŠคํŠธ๊ฐ€ ๋‚˜์ค‘์ด๋ผ๋Š” ์ 

โœ”๏ธ TDD๋ฅผ ์ž˜ํ–ˆ์„ ๋•Œ๋Š” 6๋ฒˆ์ด ์ค„์–ด๋“ค๊ธด ํ•˜๋Š”๋ฐ ํ•ด์™ธ์—์„œ๋„ ๊ทธ๋ ‡๊ณ  ๋ณต์žก๋„๋งŒ ์˜ฌ๋ผ๊ฐ€์ง€ ํฌ๊ฒŒ ์žฅ์ ์ด ์—†์–ด์„œ ์‚ฌ์šฉ์„ ํ•˜์ง€ ์•Š๋Š” ์ถ”์„ธ๋ผ๊ณ  ํ•จ.

โญ TDD์˜ ๊ฐœ๋ฐœ ๋ฐฉ์‹์€ โ€˜์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ โ†’ ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์„ฑ๊ณตํ•  ๋งŒํผ์˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โ†’ ์„ฑ๊ณตํ•˜๋Š” ํ…Œ์ŠคํŠธ โ†’ ๋ฆฌํŒฉํ† ๋ง โ†’ ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ์™€ ์„ฑ๊ณตํ•˜๋Š” ํ…Œ์ŠคํŠธ ํ™•์ธโ€™์ด๋ผ๋Š” ํ๋ฆ„์„ ๋ฐ˜๋ณตํ•œ๋‹ค.

TDD์˜ ํŠน์ง• ์ •๋ฆฌ

  • TDD๋Š” ๋ชจ๋“  ์กฐ๊ฑด์— ๋งŒ์กฑํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์ง„ํ–‰ํ•œ ๋’ค์— ์กฐ๊ฑด์— ๋งŒ์กฑํ•˜์ง€ ์•Š๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋‹จ๊ณ„์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ ์ง„์ ์œผ๋กœ ์„ฑ๊ณต์‹œํ‚จ๋‹ค.
  • TDD๋Š” ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ โ€œfailedโ€์ธ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ง€์†์ ์œผ๋กœ ๊ทธ๋ฆฌ๊ณ  ๋‹จ๊ณ„์ ์œผ๋กœ ์ˆ˜์ •ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ โ€œpassedโ€๊ฐ€ ๋˜๋„๋ก ๋งŒ๋“ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • TDD๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ โ€œpassedโ€ ๋  ๋งŒํผ์˜ ์ฝ”๋“œ๋งŒ ์šฐ์„  ์ž‘์„ฑํ•œ๋‹ค.
  • TDD๋Š” โ€˜์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ โ†’ ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์„ฑ๊ณตํ•  ๋งŒํผ์˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โ†’ ์„ฑ๊ณตํ•˜๋Š” ํ…Œ์ŠคํŠธ โ†’ ๋ฆฌํŒฉํ† ๋ง โ†’ ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ์™€ ์„ฑ๊ณตํ•˜๋Š” ํ…Œ์ŠคํŠธ ํ™•์ธโ€™์ด๋ผ๋Š” ํ๋ฆ„์„ ๋ฐ˜๋ณต

์•ž์—์„œ TDD ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•œ ํŒจ์Šค์›Œ๋“œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„์— ๋Œ€ํ•œ ์„ค๋ช… ์ž์ฒด๋Š” ๊ธธ์ง€๋งŒ ์‹ค์ œ TDD ๋ฐฉ์‹์œผ๋กœ ์ž˜ ์ง„ํ–‰๋œ๋‹ค๋ฉด ํ…Œ์ŠคํŠธ์™€ ๊ธฐ๋Šฅ ๊ตฌํ˜„, ๋ฆฌํŒฉํ† ๋ง๊นŒ์ง€ ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰์ด ๊ฐ€๋Šฅ.

API ๋ฌธ์„œํ™”

  • API ๋ฌธ์„œํ™”๋ž€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ REST API ๋ฐฑ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•Œ์•„์•ผ ๋˜๋Š” ์š”์ฒญ ์ •๋ณด(์š”์ฒญ URL(๋˜๋Š” URI), request body, query parameter ๋“ฑ)๋ฅผ ๋ฌธ์„œ๋กœ ์ž˜ ์ •๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธ
  • REST API ๊ธฐ๋ฐ˜์˜ ๋ฐฑ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํด๋ผ์ด์–ธํŠธ ์ชฝ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด API ์‚ฌ์šฉ์„ ์œ„ํ•œ ์–ด๋–ค ์ •๋ณด๊ฐ€ ํ•„์š”ํ•จ โ†’ API ์‚ฌ์šฉ์„ ์œ„ํ•œ ์–ด๋–ค ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ ์žˆ๋Š” ๋ฌธ์„œ๋ฅผ API ๋ฌธ์„œ ๋˜๋Š” API ์ŠคํŽ™(์‚ฌ์–‘, Specification)
  • ๊ฐœ๋ฐœ์ด ๋“ค์–ด๊ฐ€๊ธฐ์ „์— ๋ฌธ์„œ๊ฐ€ ๋‚˜์™€์•ผ ํ•จ โ†’ ๊ธฐํš๋‹จ๊ณ„์—์„œ ์„ค๊ณ„๋ฅผ ํ•จ.

Spring Rest Docs vs Swagger

Swagger

  1. ํŠน์ง•
    • ์• ๋„ˆํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜์˜ API ๋ฌธ์„œํ™” ๋ฐฉ
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์— ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•œ ์• ๋„ˆํ…Œ์ด์…˜๋“ค์ด ํฌํ•จ๋œ๋‹ค.
    • API ๋ฌธ์„œ์™€ API ์ฝ”๋“œ ๊ฐ„์˜ ์ •๋ณด ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
    • API ํˆด๋กœ์จ์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋ฌธ์„œํ™” ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํ…Œ์ŠคํŠธ ํˆด์˜ ๊ธฐ๋Šฅ ์žˆ๊ณ  UI๊ฐ€ ๊น”๋”ํ•ด์„œ ๋ฌธ์„œ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•˜๊ธฐ ์ข‹์Œ.
    • BUT, ๊ธฐ๋Šฅ๋ณ„ ๋ถ„๋ฆฌ๊ฐ€ ์•ˆ๋˜๊ณ  ๊ฐ€๋…์„ฑ ๋ฐ ์œ ์ง€ ๋ณด์ˆ˜์„ฑ์ด ๋–จ์–ด์ง„๋‹ค.
    • ๋ฐฐํฌ์‹œ์ ์— ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค. โ†’ ์ฃผ์„๊ณผ ๋กœ๊ทธ๋ฅผ ํฌํ•จ์‹œํ‚ค์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ธ๋ฐ ๋ฐฐํฌํ• ๋•Œ๋Š” ๋ฌธ์„œ๊ฐ€ ํ•„์š”์—†๋Š”๋ฐ ์ผ์ผ์ด ๋ฒ—๊ฒจ๋‚ด์•ผํ•œ๋‹ค.
  2. ์˜ˆ์ œ
    1. Controller โ†’ ๋ฌด์ˆ˜ํžˆ ๋งŽ์€ ์• ๋„ˆํ…Œ์ด์…˜

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      
       @ApiOperation(value = "ํšŒ์› ์ •๋ณด API", tags = {"Member Controller"}) // (1)
       @RestController
       @RequestMapping("/v11/swagger/members")
       @Validated
       @Slf4j
       public class MemberControllerSwaggerExample {
           private final MemberService memberService;
           private final MemberMapper mapper;
              
           public MemberControllerSwaggerExample(MemberService memberService, MemberMapper mapper) {
               this.memberService = memberService;
               this.mapper = mapper;
           }
              
           // (2)
           @ApiOperation(value = "ํšŒ์› ์ •๋ณด ๋“ฑ๋ก", notes = "ํšŒ์› ์ •๋ณด๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.")
              
           // (3)
           @ApiResponses(value = {
                   @ApiResponse(code = 201, message = "ํšŒ์› ๋“ฑ๋ก ์™„๋ฃŒ"),
                   @ApiResponse(code = 404, message = "Member not found")
           })
           @PostMapping
           public ResponseEntity postMember(@Valid @RequestBody MemberDto.Post memberDto) {
               Member member = mapper.memberPostToMember(memberDto);
               member.setStamp(new Stamp()); // homework solution ์ถ”๊ฐ€
              
               Member createdMember = memberService.createMember(member);
              
               return new ResponseEntity<>(
                       new SingleResponseDto<>(mapper.memberToMemberResponse(createdMember)),
                       HttpStatus.CREATED);
           }
              
           ...
           ...
              
           // (4)
           @ApiOperation(value = "ํšŒ์› ์ •๋ณด ์กฐํšŒ", notes = "ํšŒ์› ์‹๋ณ„์ž(memberId)์— ํ•ด๋‹นํ•˜๋Š” ํšŒ์›์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.")
           @GetMapping("/{member-id}")
           public ResponseEntity getMember(
                   @ApiParam(name = "member-id", value = "ํšŒ์› ์‹๋ณ„์ž", example = "1")  // (5)
                   @PathVariable("member-id") @Positive long memberId) {
               Member member = memberService.findMember(memberId);
               return new ResponseEntity<>(
                       new SingleResponseDto<>(mapper.memberToMemberResponse(member))
                                           , HttpStatus.OK);
           }
              
           ...
           ...
       }
              
      
    2. DTO, Request Body๋‚˜ Response Body ๊ฐ™์€ DTO ํด๋ž˜์Šค์—๋„ ์•„๋ž˜์˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด Swagger์˜ ์• ๋„ˆํ…Œ์ด์…˜์„ ์ผ์ผ์ด ์ถ”๊ฐ€ํ•ด ์ฃผ์–ด์•ผ ํ•จ.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      
       @ApiModel("Member Post")  // (1)
       @Getter
       public class MemberPostDto {
           // (2)
           @ApiModelProperty(notes = "ํšŒ์› ์ด๋ฉ”์ผ", example = "hgd@gmail.com", required = true)
           @NotBlank
           @Email
           private String email;
              
           // (3)
           @ApiModelProperty(notes = "ํšŒ์› ์ด๋ฆ„", example = "ํ™๊ธธ๋™", required = true)
           @NotBlank(message = "์ด๋ฆ„์€ ๊ณต๋ฐฑ์ด ์•„๋‹ˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
           private String name;
              
           // (4)
           @ApiModelProperty(notes = "ํšŒ์› ํœด๋Œ€ํฐ ๋ฒˆํ˜ธ", example = "010-1111-1111", required = true)
           @Pattern(regexp = "^010-\\\\d{3,4}-\\\\d{4}$",
                   message = "ํœด๋Œ€ํฐ ๋ฒˆํ˜ธ๋Š” 010์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” 11์ž๋ฆฌ ์ˆซ์ž์™€ '-'๋กœ ๊ตฌ์„ฑ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
           private String phone;
       }
              
      
  3. Swagger API ๋ฌธ์„œ ํ™”๋ฉด ์˜ˆ

    Postman์—์„œ MemberControlle์— HTTP ์š”์ฒญ์„ ์ „์†กํ•˜๋“ฏ์ด [Execute] ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด MemberController์— ์š”์ฒญ์„ ์ „์†ก๊ฐ€๋Šฅ

Spring Rest Docs

  1. ํŠน์ง•
    • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๊ธฐ๋ฐ˜์˜ API ๋ฌธ์„œํ™” ๋ฐฉ์‹
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์— ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•œ ์ •๋ณด๋“ค์ด ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค.
    • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์‹คํ–‰์ด โ€œpassedโ€์—ฌ์•ผ API ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.
    • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋ฐ˜๋“œ์‹œ ์ž‘์„ฑํ•ด์•ผ ๋œ๋‹ค.
    • API ํˆด๋กœ์จ์˜ ๊ธฐ๋Šฅ์€ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค.
  2. ์˜ˆ์ œ

    • ์ฝ”๋“œ์˜ ๋…ธ๋ž€ ๋ฐ•์Šค๋ถ€๋ถ„์ด ๋ฌธ์„œํ™” ๊ด€๋ จ์ฝ”๋“œ์ด๋‹ค.
    • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๊ฒ€์ฆ์— ๋ฌธ์„œํ™” ์ฝ”๋“œ์— ํฌํ•จ๋˜์–ด ์žˆ์Œ.โ†’ ๊ทธ๋ ‡๊ฒŒ ๋•Œ๋ฌธ์— ๋ฌธ์„œํ™”ํ•˜๋ ค๋ฉด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ํ•„์ˆ˜
  3. Spring Rest Docs์˜ API ๋ฌธ์„œํ™”๋ฉด ์˜ˆ

Spring Rest Docs

Spring Rest Docs์˜ API ๋ฌธ์„œ ์ƒ์„ฑ ํ๋ฆ„

  1. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ
    1. ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ โ…ฐ. Spring Rest Docs๋Š” Controller์˜ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ์™€ ๋ฐ€์ ‘ํ•œ ๊ด€๋ จ์ด ์žˆ์Œ
    2. API ์ŠคํŽ™ ์ •๋ณด ์ฝ”๋“œ ์ž‘์„ฑ โ…ฐ. ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋‹ค์Œ์— Controller์— ์ •์˜๋˜์–ด ์žˆ๋Š” API ์ŠคํŽ™ ์ •๋ณด(Request Body, Response Body, Query Parameter ๋“ฑ)๋ฅผ ์ฝ”๋“œ๋กœ ์ž‘์„ฑ
  2. test ํƒœ์Šคํฌ(task) ์‹คํ–‰
    1. ์ž‘์„ฑ๋œ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. โ…ฐ. ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ์‹คํ–‰์‹œ์ผœ๋„ ๋˜์ง€๋งŒ ์ผ๋ฐ˜์ ์œผ๋กœ Gradle์˜ ๋นŒ๋“œ ํƒœ์Šคํฌ(task)์ค‘ ํ•˜๋‚˜์ธ test task๋ฅผ ์‹คํ–‰์‹œ์ผœ์„œ API ๋ฌธ์„œ ์Šค๋‹ˆํŽซ(snippet)์„ ์ผ๊ด„ ์ƒ์„ฑํ•œ๋‹ค.
    2. ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ โ€œpassedโ€์ด๋ฉด ๋‹ค์Œ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ณ , โ€œfailedโ€์ด๋ฉด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ˆ˜์ •ํ•œ ํ›„, ๋‹ค์‹œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค. โ†’ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•ด์•ผ ๋ฌธ์„œ ์ž‘์—… ๊ฐ€๋Šฅ.
  3. API ๋ฌธ์„œ ์Šค๋‹ˆํŽซ( .adoc ํŒŒ์ผ) ์ƒ์„ฑ
    1. ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ โ€œpassedโ€์ด๋ฉด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ํฌํ•จ๋œ API ์ŠคํŽ™ ์ •๋ณด ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ API ๋ฌธ์„œ ์Šค๋‹ˆํŽซ์ด .adoc ํ™•์žฅ์ž๋ฅผ ๊ฐ€์ง„ ํŒŒ์ผ๋กœ ์ƒ์„ฑ๋œ๋‹ค.

์Šค๋‹ˆํŽซ์ด๋ž€? ์Šค๋‹ˆํŽซ์€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ํ•˜๋‚˜๋‹น ํ•˜๋‚˜์˜ ์Šค๋‹ˆํŽซ์ด ์ƒ์„ฑ๋˜๋ฉฐ, ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์Šค๋‹ˆํŽซ์„ ๋ชจ์•„์„œ ํ•˜๋‚˜์˜ API ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑ ๊ฐ€๋Šฅํ•˜๋‹ค. API ๋ฌธ์„œ ์ƒ์„ฑ

  1. ์ƒ์„ฑ๋œ API ๋ฌธ์„œ ์Šค๋‹ˆํŽซ์„ ๋ชจ์•„์„œ ํ•˜๋‚˜์˜ API ๋ฌธ์„œ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  2. API ๋ฌธ์„œ๋ฅผ HTML๋กœ ๋ณ€ํ™˜
    1. ์ƒ์„ฑ๋œ API ๋ฌธ์„œ๋ฅผ HTML ํŒŒ์ผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    2. HTML๋กœ ๋ณ€ํ™˜๋œ API ๋ฌธ์„œ๋Š” HTML ํŒŒ์ผ ์ž์ฒด๋ฅผ ๊ณต์œ ํ•  ์ˆ˜๋„ ์žˆ๊ณ , URL์„ ํ†ตํ•ด ํ•ด๋‹น HTML์— ์ ‘์†ํ•ด์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Spring Rest Docs ์„ค์ •

  1. build.gradle ์„ค์ •

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    
     plugins {
     	id 'org.springframework.boot' version '2.7.1'
     	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
     	id "org.asciidoctor.jvm.convert" version "3.3.2"    // (1)
     	id 'java'
     }
        
     /*
        
      (1) .adoc ํŒŒ์ผ ํ™•์žฅ์ž๋ฅผ ๊ฐ€์ง€๋Š” AsciiDoc ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ๋Š”
      Asciidoctor๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€
        
     */
        
     group = 'com.springboot'
     version = '0.0.1-SNAPSHOT'
     sourceCompatibility = '11'
        
     repositories {
     	mavenCentral()
     }
        
     ext { // (2) ext ๋ณ€์ˆ˜์˜ set() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ API ๋ฌธ์„œ ์Šค๋‹ˆํŽซ์ด ์ƒ์„ฑ๋  ๊ฒฝ๋กœ๋ฅผ ์ง€์ •
     	set('snippetsDir', file("build/generated-snippets"))
     }
        
     /* 
     (3)
     AsciiDoctor์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์˜์กด ๊ทธ๋ฃน์„ ์ง€์ •:asciidoctor task๊ฐ€ ์‹คํ–‰๋˜๋ฉด 
     ๋‚ด๋ถ€์ ์œผ๋กœ (3)์—์„œ ์ง€์ •ํ•œ โ€˜asciidoctorExtensionsโ€™๋ผ๋Š” ๊ทธ๋ฃน์„ ์ง€์ •
     */
        
     configurations {
     	asciidoctorExtensions
     }
        
     dependencies {
             /* (4) org.springframework.restdocs:spring-restdocs-mockmvc'๋ฅผ ์ถ”๊ฐ€ํ•จ์œผ๋กœ์จ 
             spring-restdocs-core์™€ spring-restdocs-mockmvc ์˜์กด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€๋œ๋‹ค.*/
        	
     	testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
        
             /*(5)
             pring-restdocs-asciidoctor ์˜์กด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€
             (3)์—์„œ ์ง€์ •ํ•œ asciidoctorExtensions ๊ทธ๋ฃน์— ์˜์กด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํฌํ•จ๋œ๋‹ค
             */
     	asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
        
     	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
     	implementation 'org.springframework.boot:spring-boot-starter-validation'
     	implementation 'org.springframework.boot:spring-boot-starter-web'
     	compileOnly 'org.projectlombok:lombok'
     	runtimeOnly 'com.h2database:h2'
     	annotationProcessor 'org.projectlombok:lombok'
     	testImplementation 'org.springframework.boot:spring-boot-starter-test'
     	implementation 'org.mapstruct:mapstruct:1.5.1.Final'
     	annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.1.Final'
     	implementation 'org.springframework.boot:spring-boot-starter-mail'
        
     	implementation 'com.google.code.gson:gson'
     }
        
     // (6) :test task ์‹คํ–‰ ์‹œ, API ๋ฌธ์„œ ์ƒ์„ฑ ์Šค๋‹ˆํŽซ ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •
     tasks.named('test') {
     	outputs.dir snippetsDir
     	useJUnitPlatform()
     }
        
     /* (7) sciidoctor task ์‹คํ–‰ ์‹œ, Asciidoctor ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด 
     :asciidoctor task์— asciidoctorExtensions์„ ์„ค์ •*/
        
     tasks.named('asciidoctor') {
     	configurations "asciidoctorExtensions"
     	inputs.dir snippetsDir
     	dependsOn test
     }
        
     /* (8) :build task ์‹คํ–‰ ์ „์— ์‹คํ–‰๋˜๋Š” task
     :copyDocument task๊ฐ€ ์ˆ˜ํ–‰๋˜๋ฉด index.html ํŒŒ์ผ์ด 
     src/main/resources/static/docs ์— copy ๋˜๋ฉฐ, 
     copy ๋œ index.html ํŒŒ์ผ์€ API ๋ฌธ์„œ๋ฅผ ํŒŒ์ผ ํ˜•ํƒœ๋กœ ์™ธ๋ถ€์— ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„๋กœ ์‚ฌ์šฉ*/
        
     task copyDocument(type: Copy) {
     	dependsOn asciidoctor            
     		// (8-1) :asciidoctor task๊ฐ€ ์‹คํ–‰๋œ ํ›„์— task๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ์˜์กด์„ฑ์„ ์„ค์ •
     	from file("${asciidoctor.outputDir}")   
     		// (8-2) build/docs/asciidoc/" ๊ฒฝ๋กœ์— ์ƒ์„ฑ๋˜๋Š” index.html์„ copy
     	into file("src/main/resources/static/docs")   
     		// (8-3) src/main/resources/static/docs" ๊ฒฝ๋กœ๋กœ index.html์„ ์ถ”๊ฐ€
     }
        
     build {
     	dependsOn copyDocument  
     		// (9):build task๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— :copyDocument task๊ฐ€ ๋จผ์ € ์ˆ˜ํ–‰๋˜๋„๋ก ํ•จ.
     }
        
     // (10)  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ํŒŒ์ผ์ด ์ƒ์„ฑํ•˜๋Š” :bootJar task ์„ค์ •
     bootJar {
     	dependsOn copyDocument    
     		// (10-1)  :bootJar task ์‹คํ–‰ ์ „์— :copyDocument task๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ์˜์กด์„ฑ์„ ์„ค์ •
     	from ("${asciidoctor.outputDir}") { 
     		into 'static/docs'    
     		 /*(10-2) Asciidoctor ์‹คํ–‰์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” index.html ํŒŒ์ผ์„ jar ํŒŒ์ผ ์•ˆ์— ์ถ”๊ฐ€
     		 jar ํŒŒ์ผ์— index.html์„ ์ถ”๊ฐ€ํ•ด ์คŒ์œผ๋กœ์จ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ ‘์†
     		 (http://localhost:8080/docs/index.html) ํ›„, API ๋ฌธ์„œ๋ฅผ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.*/
     	}
     }
        
    
  2. API ๋ฌธ์„œ ์Šค๋‹ˆํŽซ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํ…œํ”Œ๋ฆฟ API ๋ฌธ์„œ ์ƒ์„ฑ

    • Gradle ๊ธฐ๋ฐ˜ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์•„๋ž˜ ๊ฒฝ๋กœ์— ํ•ด๋‹นํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
      • src/docs/asciidoc/
    • ๋‹ค์Œ์œผ๋กœ src/docs/asciidoc/ ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์— ๋น„์–ด์žˆ๋Š” ํ…œํ”Œ๋ฆฟ ๋ฌธ์„œ(index.adoc)๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ๋ฉด ๋œ๋‹ค.

์Šฌ๋ผ์ด์Šคํ…Œ์ŠคํŠธ

  1. MemberController๊ฐ€ ์š”์ฒญ์„ ์ž˜ ์ „๋‹ฌ๋ฐ›๊ณ , ์‘๋‹ต์„ ์ž˜ ์ „์†กํ•˜๋ฉฐ ์š”์ฒญ๊ณผ ์‘๋‹ต์ด ์ •์ƒ์ ์œผ๋กœ ์ˆ˜ํ–‰๋˜๋ฉด API ๋ฌธ์„œ ์ŠคํŽ™ ์ •๋ณด๋ฅผ ์ž˜ ์ฝ์–ด ๋“ค์—ฌ์„œ ์ ์ ˆํ•œ ๋ฌธ์„œ๋ฅผ ์ž˜ ์ƒ์„ฑํ•˜๋А๋ƒ ํ•˜๋Š” ๊ฒƒ์„ Testํ•˜๋ฉด ๋จ
  2. MemberService์™€ MemberMapper์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋„๋ก ๊ด€๊ณ„๋ฅผ ๋‹จ์ ˆ โ‡’ MemberService์™€ MemberMapper์˜ Mock Bean์„ ์ฃผ์ž…

1๏ธโƒฃ ์• ๋„ˆํ…Œ์ด์…˜

  1. @WebMvcTest ์• ๋„ˆํ…Œ์ด์…˜์€ Controller๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ์ „์šฉ ์• ๋„ˆํ…Œ์ด์…˜, ๊ด„ํ˜ธ ์•ˆ์—๋Š” ํ…Œ์ŠคํŠธ ๋Œ€์ƒ Controller ํด๋ž˜์Šค๋ฅผ ์ง€์ •
  2. @MockBean(JpaMetamodelMappingContext.class)

@SpringBootTest vs @WebMvcTest

๊ธฐ์กด์—๋Š”@SpringBootTest + @AutoConfigureMockMvc ์• ๋„ˆํ…Œ์ด์…˜์œผ๋กœ Controller์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰

@SpringBootTest์™€ @WebMvcTest์˜ ์ฐจ์ด์ ์€? ๋จผ์ € @SpringBootTest ์• ๋„ˆํ…Œ์ด์…˜์€ @AutoConfigureMockMvc๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜์–ด Controller๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ „์ฒด Bean์„ ApplicationContext์— ๋“ฑ๋กํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค. ํ•œ๋งˆ๋””๋กœ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์€ ํŽธ๋ฆฌํ•˜๊ธด ํ•œ๋ฐ ์‹คํ–‰ ์†๋„๊ฐ€ ์ƒ๋Œ€์ ์œผ๋กœ ๋А๋ฆผ.

@WebMvcTest ์• ๋„ˆํ…Œ์ด์…˜์˜ ๊ฒฝ์šฐ Controller ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ Bean๋งŒ ApplicationContext์— ๋“ฑ๋กํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹คํ–‰ ์†๋„๋Š” ์ƒ๋Œ€์ ์œผ๋กœ ๋น ๋ฅด๋‹ค.

๋‹ค๋งŒ, Controller์—์„œ ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ๊ฐ์ฒด๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ๊ฐ์ฒด์— ๋Œ€ํ•ด์„œ Mock ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜์กด์„ฑ์„ ์ผ์ผ์ด ์ œ๊ฑฐํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ @SpringBootTest๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊นŒ์ง€ ์š”์ฒญ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ด์–ด์ง€๋Š” ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์— ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๊ณ , @WebMvcTest๋Š” Controller๋ฅผ ์œ„ํ•œ ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธ์— ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

2๏ธโƒฃ MemberController์˜ postMember() ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ API ์ŠคํŽ™์ •๋ณด ์ถ”๊ฐ€

  1. ๋…ธ๋ž€๋ฐ•์Šค ๋‚ด์˜ document(โ€ฆ) ๋ฉ”์„œ๋“œ๋Š” API ์ŠคํŽ™ ์ •๋ณด๋ฅผ ์ „๋‹ฌ๋ฐ›์•„์„œ ์‹ค์งˆ์ ์ธ ๋ฌธ์„œํ™” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” RestDocumentationResultHandler ํด๋ž˜์Šค์—์„œ ๊ฐ€์žฅ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ๋ฉ”์„œ๋“œ
  2. document() ๋ฉ”์„œ๋“œ์˜ ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” API ๋ฌธ์„œ ์Šค๋‹ˆํŽซ์˜ ์‹๋ณ„์ž ์—ญํ• ์„ ํ•˜๋ฉฐ, โ€œpost-memberโ€๋กœ ์ง€์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์„œ ์Šค๋‹ˆํŽซ์€ post-member ๋””๋ ‰ํ† ๋ฆฌ ํ•˜์œ„์— ์ƒ์„ฑ๋œ๋‹ค.
  3. getRequestPreProcessor() ์™€ getResponsePreProcessor()

    ๋ฌธ์„œ ์Šค๋‹ˆํŽซ์„ ์ƒ์„ฑํ•˜๊ธฐ ์ „์— request์™€ response์— ํ•ด๋‹นํ•˜๋Š” ๋ฌธ์„œ ์˜์—ญ์„ ์ „์ฒ˜๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋Š”๋ฐ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ณตํ†ตํ™”ํ•œ ํ›„, ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     package com.springboot.util;
        
     import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor;
     import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor;
        
     import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
        
     public interface ApiDocumentUtils {
        static OperationRequestPreprocessor getRequestPreProcessor() {
            return preprocessRequest(prettyPrint());
         }
        
         static OperationResponsePreprocessor getResponsePreProcessor() {
             return preprocessResponse(prettyPrint());
         }
     }
        
    
  4. requestFields(โ€ฆ)๋Š” ๋ฌธ์„œ๋กœ ํ‘œํ˜„๋  request body๋ฅผ ์˜๋ฏธ, List<FieldDescriptor>์˜ ์›์†Œ์ธ FieldDescriptor ๊ฐ์ฒด๊ฐ€ request body์— ํฌํ•จ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œํ˜„
  5. responseHeaders(โ€ฆ)๋Š” ๋ฌธ์„œ๋กœ ํ‘œํ˜„๋  response header๋ฅผ ์˜๋ฏธ,ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋˜๋Š” HeaderDescriptor ๊ฐ์ฒด๊ฐ€ response header๋ฅผ ํ‘œํ˜„
  6. ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ โ€œpassedโ€์ด๋ฉด ์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•œ API ์ŠคํŽ™ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฌธ์„œ ์Šค๋‹ˆํŽซ์ด ๋งŒ๋“ค์–ด์ง.


3๏ธโƒฃ MemberController์˜ patchMember() ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•œ API ์ŠคํŽ™์ •๋ณด ์ถ”๊ฐ€

  1. memberId์˜ ๊ฒฝ์šฐ, path variable ์ •๋ณด๋กœ memberId๋ฅผ ์ „๋‹ฌ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— MemberDto.Patch DTO ํด๋ž˜์Šค์—์„œ request body์— ๋งคํ•‘๋˜์ง€ ์•Š๋Š” ์ •๋ณด โ‡’ ๊ทธ๋ž˜์„œ (2)์™€ ๊ฐ™์ด ignored()๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ API ์ŠคํŽ™ ์ •๋ณด์—์„œ ์ œ์™ธ
  2. ํšŒ์›์ •๋ณด๋Š” ์„ ํƒ์ ์œผ๋กœ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋ฏ€๋กœ optional()์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์„ ํƒ์ •๋ณด๋กœ ์„ค์ •ํ•จ.

Spring ์นดํ…Œ๊ณ ๋ฆฌ ๋‚ด ๋‹ค๋ฅธ ๊ธ€ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

Leave a comment