๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’Š Java & Kotlin & Spring/- spring framework +

[JUnit 5] @ParameterizeTest ๋กœ ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ํ…Œ์ŠคํŠธํ•˜๊ธฐ (Springboot + ControllerAdvice, ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ)

by Wonit 2021. 4. 19.

์˜ค๋Š˜์€ JUnit5 ์˜ @ParameterizedTest ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณผ ๊ฒƒ์ด๋‹ค.
์ด๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ํ•„์š” ๊ธฐ์ˆ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • SpringBoot 2.
  • JUnit 5
  • Validation
  • ControllerAdvice

์ด์™€ ๊ฐ™์€ DTO ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

@NotBlank๋‚˜ @Size๋‚˜ @Email ์€ ๋ชจ๋‘ validation ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ธ๋ฐ, ๋งŒ์•ฝ ์ด ์ž…๋ ฅ ๊ฐ’๋“ค ์ค‘์—์„œ ํ•ด๋‹น constraint๋ฅผ ๋งŒ์กฑ์‹œํ‚ค์ง€ ๋ชปํ•œ๋‹ค๋ฉด MethodArgumentNotValidException ์„ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

 

 

๊ทธ๋Ÿผ ControllerAdvice๋ฅผ ์ด์šฉํ•ด์„œ ํ•ด๋‹น ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์„ ๋•Œ ErrorResponse๋ฅผ ์‘๋‹ตํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋งŒ๋“ค์–ด๋ณด์ž.

 

ControllerErrorAdvice.class

@ResponseBody
@ControllerAdvice
public class ControllerErrorAdvice {
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
        return new ErrorResponse(exception.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }
}

 

ํ•ด๋‹น ControllerAdvice ์—์„œ @ExceptionHandler ๋กœ ์–ด๋–ค ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋Š”์ง€๋ฅผ ์žก์•„์„œ @ResponseStatus์— ์šฐ๋ฆฌ๊ฐ€ ๋‚ด๋ ค์ค„ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ช…์‹œํ•ด์ค€๋‹ค.

 

DTO

public class ErrorResponse {
    private String message;

    public ErrorResponse(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

 

ํ•ด๋‹น ์˜ˆ์™ธ์— ์ง€์ •๋  message ํ•„๋“œ๋ฅผ ๋ฐ›์•„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌํ•ด์ค„ DTO ๊ฐ์ฒด์ด๋‹ค.

 

ControllerTest

@SpringBootTest
class ControllerTest {

    @Autowired
    private MemberService memberService;

    @ParameterizedTest // 1
    @DisplayName("ํšŒ์› ๊ฐ€์ž… - ๋น„์ •์ƒ์  ์ž…๋ ฅ")
    @MethodSource("paramsForSignUpInvalidInputs") // 2
    void signUp_with_invalid_inputs(String email, String password, String nickname) throws Exception{
        // given
        Member member = Member.builder()
                .email(email)
                .password(password)
                .nickname(nickname)
                .build();
        // when & then
        MemberRequestSignUpData memberRequestData = modelMapper.map(member, MemberRequestSignUpData.class);
        mockMvc.perform(post("/api/members")
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(memberRequestData)))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("message").exists());
    }

    private static Stream<Arguments> paramsForSignUpInvalidInputs() { // 3
        return Stream.of(
                Arguments.of("noEmail", "test1234", "James"), // ์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹˜
                Arguments.of("dhslrl321@gmail.com", "noPw", "James"), // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ 8๊ธ€์ž ๋ฏธ๋งŒ
                Arguments.of("dhslrl321@gmail.com", "test1234", "1"), // ๋‹‰๋„ค์ž„์ด 2๊ธ€์ž ๋ฏธ๋งŒ
                Arguments.of("", "", "") // ๊ฐ’์ด ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ
        );
    }
}

 

์ด์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.


์•„์ง ๋ฌด์Šจ ๋‚ด์šฉ์ธ์ง€ ๋ชฐ๋ผ๋„ ๊ดœ์ฐฎ๋‹ค.

 

ํ•˜๋‚˜ ํ•˜๋‚˜ ์•Œ์•„๋ณด์ž.

 

์ฃผ์„ 1. @ParameterizedTest

 

ParameterziedTest ๋Š” JUnit 4์—์„œ ์‚ฌ์šฉ๋˜์—ˆ๋˜ @RunWith(Parameterized.class) ์™€ ๋™์ผํ•œ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

 

ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ ํ•˜๋‚˜๋ฅผ ์ด์šฉํ•ด์„œ ๋‹ค์–‘ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ,

 

 

JUnit ๊ณต์‹ ๋ฌธ์„œ์—์„œ @ParameterizedTest๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œํ˜„๋œ๋‹ค.

 

@ArgumentsSource ๋‚˜ ํ•ด๋‹น ์ฃผ์„์— ๋Œ€์‘ํ•˜๋Š” ํ•„๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ „๋‹ฌํ•  ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋Š” private ์ด๋‚˜ static ์ด์–ด์„œ๋Š” ์•ˆ๋œ๋‹ค.

 

๋‹ค์–‘ํ•œ Source๋ฅผ ์ด์šฉํ•ด์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค.

 

๋‹ค์–‘ํ•œ Source

  • @ValueSource : string, int ํ˜•์‹์˜ primtive ํƒ€์ž…์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
    • @ValueSource(strings = {"a", "b", "c"})
  • @NullSource : Null ๊ฐ’์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„ฃ์–ด์ค€๋‹ค.
  • @EmptySource :
  • @EnumSource : Enum ํƒ€์ž…์˜ value๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„ฃ์–ด์ค€๋‹ค.
    • @EnumSource(value = UserRole.class, name = {"ADMIN", "USER"}
  • @MethodSource
    • ๊ฐ€์žฅ ์ค‘์š”ํ•œ Source๋กœ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด์ž.

์ฃผ์„ 2. @MethodSource

 

ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์ด ๊ฐ€์žฅ ์ค‘์š”ํ•˜๋‹ค๋Š” ๋ฐ์—๋Š” ์ด์œ ๊ฐ€ ์žˆ๋‹ค.

 

์œ„์—์„œ ๋ณด์•˜๋˜ ๋ฐฉ์‹์˜ ์–ด๋…ธํ…Œ์ด์…˜์€ ํ•˜๋‚˜์˜ ํƒ€์ž…๋งŒ์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋Š”๋ฐ, @MethodSource๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ํƒ€์ž…์„ ๋„˜๊ธฐ๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

 

์ด๊ฒŒ ์™œ ์ค‘์š”ํ•œ๊ฑธ๊นŒ?

์šฐ๋ฆฌ๊ฐ€ ํ…Œ์ŠคํŠธํ•  ๋Œ€์ƒ์€ ํ•˜๋‚˜์˜ ํƒ€์ž…๋งŒ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ์žˆ์ง€ ์•Š๊ณ  ๋ณต์žกํ•œ ํƒ€์ž…์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋งค์šฐ ํฌ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์•ž์„œ ๋ณด์•˜๋˜ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ์— ์กฐ๊ธˆ ๋ณ€ํ˜•์„ ์‹œ์ผœ๋ณด์ž.

  • email: String
  • age: int
  • UserType: UserType.Enum
  • nickname: String

์ด๋ผ๊ณ  ํ•œ๋‹ค๋ฉด ๋ฒŒ์จ 3๊ฐœ์˜ ํƒ€์ž…์ด ์กด์žฌํ•œ๋‹ค.

 

์ด๋Ÿฐ ๊ฒฝ์šฐ @MethodSource๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

 

@MethodSource๋Š” ๋ฉ”์„œ๋“œ์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ์ŠคํŠธ๋ฆผ์„ ์ด์šฉํ•ด์„œ ํ…Œ์ŠคํŠธ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ธฐ๋Š”๋ฐ, ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋  ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

์•„๋ž˜์„œ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด์ž.

 

์ฃผ์„ 3. paramsForSignUpInvalidInputs()

private static Stream<Arguments> paramsForSignUpInvalidInputs() ์˜ ํ˜•ํƒœ๋ฅผ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ํ•ด๋‹น ๋ฉ”์„œ๋“œ์—์„œ ์ฃผ๋ชฉํ•ด์•ผํ•  ์ ์ด 2๊ฐœ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

 

  • static
  • Stream

์•ž์„œ ๋ง ํ–ˆ๋“ฏ MethodSource๋Š” ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํ–ˆ๋‹ค.

 

์šฐ๋ฆฌ๋Š” arguments ์ œ๋„ˆ๋ฆญ์„ ์ด์šฉํ•œ ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์ด๊ณ  ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์€ ๊ผญ static ๋ฉ”์„œ๋“œ์—ฌ์•ผ ํ•œ๋‹ค.

 

 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋Œ๋ ค๋ณด๊ธฐ

 

์œ„์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๊ฐ€ ๋„˜๊ธฐ๋Š” Stream์œผ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋Œ๋ ค๋ณด์ž.

 

 

 

๊ทธ๋Ÿผ ์ •์ƒ์ ์œผ๋กœ ์šฐ๋ฆฌ์˜ ๊ธฐ๋Šฅ์ด ์ž˜ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๋Œ“๊ธ€