๐Ÿ”งFramework/Spring

[SpringBoot] ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

์—ฌ์šฐ๋น„_YoBi 2024. 9. 15. 16:36

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ž€?

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์ž‘์„ฑํ•œ ์ฝ”๋“œ๊ฐ€ ์˜๋„๋Œ€๋กœ ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค. 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” test ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์ž‘์—…ํ•œ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ํŒจํ„ด์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ ๋ณดํŽธ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํŒจํ„ด์€ 

"given-when-then" ํŒจํ„ด์ด๋‹ค. 

 

@DisplayName("Save New Pet")
	@Test
    public void savePetTest(){
    	// given : ๋ฐ˜๋ ค๋™๋ฌผ์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ค€๋น„ ๊ณผ์ •
        final String name = "BlackCherry";
        final int age = 4;
        
        final Pet pet = new Pet(name, age);
        
        // when : ์‹ค์ œ๋กœ ์ €์žฅ
        final long saveId = petService.save(pet)
        
        // then : ์ถ”๊ฐ€๊ฐ€ ์ž˜ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
        final Pet savePet = petService.findById(saveId).get();
        assertThat(savePet.getName()).isEqualTo(name);
        assertThat(savePet.getAge()).isEqualTo(age);
    }

 

์Šคํ”„๋ง ๋ถ€ํŠธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์™€ ์• ๋„ˆํ…Œ์ด์…˜ ์Šคํƒ€ํ„ฐ์ธ spring-boot-starter-test๋ฅผ ์ œ๊ณตํ•œ๋‹ค. 

โ˜…JUnit : ์ž๋ฐ” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์šฉ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ
Spring Test & Spring Boot Test : ์Šคํ”„๋ง ๋ถ€ํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ œ๊ณต
โ˜…AssertJ : ๊ฒ€์ฆ๋ฌธ์ธ Assertion์„ ์ž‘์„ฑํ•˜๋Š”๋ฐ ์‚ฌ์šฉ
Hamcrest : ํ‘œํ˜„์‹์„ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” Matcher ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
Mockito : ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•  ๊ฐ€์งœ ๊ฐ์ฒด์ธ Mock์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ณผ ๊ด€๋ฆฌํ•˜๋ฉฐ, ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ง€์›ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ
JSONassert : JSON์šฉ Assertion Library
JSONPath : JSON ๋ฐ์ดํ„ฐ์—์„œ ํŠน์ • ๋ฐ์ดํ„ฐ๋ฅผ ์„ ํƒํ•˜๊ณ  ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

JUnit

JUnit์€ ์ž๋ฐ” ์–ธ์–ด๋ฅผ ์œ„ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค. 

JUnit์˜ ํŠน์ง•
- ํ…Œ์ŠคํŠธ ๋ฐฉ์‹์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋Š” ์• ๋„ˆํ…Œ์ด์…˜ ์ œ๊ณต
- @Test ์• ๋„ˆํ…Œ์ด์…˜์œผ๋กœ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์‹œ๋งˆ๋‹ค ์ƒˆ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ. ๋…๋ฆฝ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
- ์˜ˆ์ƒ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ์–ด์…œ์…˜ ๋ฉ”์„œ๋“œ ์ œ๊ณต'
- ์‚ฌ์šฉ๋ฐฉ๋ฒ•์ด ๋‹จ์ˆœํ•˜๊ณ , ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ๊ฐ„์ด ์ ์Œ
- ์ž๋™ ์‹คํ–‰, ์ž์ฒด ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜๊ณ  ์ฆ‰๊ฐ์  ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต

 

public class JUnitTest{
	@Display("1+2=3?") // ํ…Œ์ŠคํŠธ ์ด๋ฆ„
    @Test // ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ
    public void junitTest(){
    	int a = 1;
        int b = 2;
        int sum = 3;
        
        Assertions.assertEquals(sum, a+b); // ๊ฐ’์ด ๊ฐ™์€์ง€ ํ™•์ธ
    }
    
    @Display("1+2=4?")
    @Test
    public void junitFailedTest(){
    	int a = 1;
        int b = 2;
        int sum = 4;
        
        Assertions.assertEquals(sum, a+b);
    }
}

@DisplayName ์• ๋„ˆํ…Œ์ด์…˜์€ ํ…Œ์ŠคํŠธ ์ด๋ฆ„์„ ๋ช…์‹œํ•ด์ค€๋‹ค. 

@Test ์• ๋„ˆํ…Œ์ด์…˜์„ ๋ถ™์ธ ๋ฉ”์„œ๋“œ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ ๋œ๋‹ค. ํ…Œ์ŠคํŠธ ๋ผ๋ฆฌ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋„๋ก ๊ฐ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์‹คํ–‰ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ํ…Œ์ŠคํŠธ๊ฐ€ ์ข…๋ฃŒ๋˜๋ฉด ์‹คํ–‰ ๊ฐ์ฒด๋Š” ์‚ญ์ œ๋œ๋‹ค. 

JUnit์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒ€์ฆ ๋ฉ”์„œ๋“œ์ธ assertEquals๋Š” ์ธ์ˆ˜๋กœ ๋ฐ›์€ ๋‘ ์ธ์ž๊ฐ€ ๊ฐ™์€์ง€๋ฅผ ํ™•์ธํ•ด ์ค€๋‹ค. 

 

ํ…Œ์ŠคํŠธ๋Š” ์• ๋„ˆํ…Œ์ด์…˜์— ๋”ฐ๋ผ ์‹คํ–‰ ์ˆœ์„œ๊ฐ€ ์ •ํ•ด์ง„๋‹ค. 

import org.junit.jupiter.api*;

public class JUnitCycleTest{
	@BeforeAll // ์ „์ฒด ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „ 1ํšŒ ์‹คํ–‰ํ•˜๋ฏ€๋กœ static์œผ๋กœ ์„ ์–ธ
    static void beforeAll(){
    	System.out.println("@BeforeAll");
    }
    
    @BeforeEach // ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์‹œ์ž‘ํ•˜๊ธฐ ์ „๋งˆ๋‹ค ์‹คํ–‰
    public void beforEach(){
    	System.out.println("@BeforeEach");
    }
    
    @Test
    public void test1(){
    	System.out.println("test1");
    }
    
    @Test
    public void test1(){
    	System.out.println("test1");
    }
    
    @AfterAll // ์ „์ฒด ํ…Œ์ŠคํŠธ๋ฅผ ๋งˆ์น˜๊ณ  ์ข…๋ฃŒ์ „ 1ํšŒ ์‹คํ–‰ํ•˜๋ฏ€๋กœ static์œผ๋กœ ์„ ์–ธ
    static void afterAll(){
    	System.out.println("@AfterAll");
    }
    
    @AfterEach // ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ข…๋ฃŒ ์ „๋งˆ๋‹ค ์‹คํ–‰
    public void afterEach(){
    	System.out.println("@AfterEach");
    }
}

@BeforeAll 

์ „์ฒด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ์ „์— ์ตœ์ดˆ 1ํšŒ ์‹คํ–‰ํ•œ๋‹ค. 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—ฐ๊ฒฐํ•˜๊ฑฐ๋‚˜ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. 

์ „์ฒด ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ฃผ๊ธฐ์—์„œ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์„œ๋“œ๋ฅผ static์œผ๋กœ ์„ ์–ธํ•œ๋‹ค. 

@BeforeEach

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „๋งˆ๋‹ค ์‹คํ–‰๋œ๋‹ค. ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ฑฐ๋‚˜, ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ๊ฐ’์„ ๋ฏธ๋ฆฌ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค. 

@AfterAll

์ „์ฒด ํ…Œ์ŠคํŠธ๋ฅผ ๋งˆ์น˜๊ณ  ์ข…๋ฃŒํ•˜๊ธฐ์ „์— ์‹คํ–‰๋จ. 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ข…๋ฃŒํ•˜๊ฑฐ๋‚˜ ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ž์›์„ ํ•ด์ œํ•  ๋•Œ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค.

@AfterEach

๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ข…๋ฃŒํ•˜๊ธฐ ์ „ ๋งค๋ฒˆ ์‹คํ–‰๋œ๋‹ค. ํ…Œ์ŠคํŠธ ์ดํ›„ ํŠน์ • ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•œ๋‹ค. 

AssertJ

AssetJ๋Š” JUnit๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด ๊ฒ€์ฆ๋ฌธ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์—ฌ์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. 

assertThat(a+b).isEqualTo(sum);

 

AssertJ์—๋Š” ๋‹ค์–‘ํ•œ ๋ฉ”์„œ๋“œ๋“ค์ด ์กด์žฌํ•œ๋‹ค. 

๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์„ค๋ช…
isEqualTo(A) A ๊ฐ’๊ณผ ๊ฐ™์€์ง€ ๊ฒ€์ฆ
isNotEqualTo(A) A๊ฐ’๊ณผ ๋‹ค๋ฅธ์ง€ ๊ฒ€์ฆ
contains(A) A๊ฐ’์„ ํฌํ•จํ•˜๋Š”์ง€ ๊ฒ€์ฆ
doesNotContains(A) A๊ฐ’์„ ํฌํ•จํ•˜์ง€ ์•Š๋Š”์ง€ ๊ฒ€์ฆ
startsWith(A) ์ ‘๋‘์‚ฌ๊ฐ€ A์ธ์ง€ ๊ฒ€์ฆ
endsWith(A) ์ ‘๋ฏธ์‚ฌ๊ฐ€ A์ธ์ง€ ๊ฒ€์ฆ
isEmpty() ๋น„์–ด์žˆ๋Š” ๊ฐ’์ธ์ง€ ๊ฒ€์ฆ
isNotEmpty ๋น„์–ด์žˆ์ง€ ์•Š์€ ๊ฐ’์ธ์ง€ ๊ฒ€์ฆ
isPositive() ์–‘์ˆ˜์ธ์ง€ ๊ฒ€์ฆ
isNegative() ์Œ์ˆ˜์ธ์ง€ ๊ฒ€์ฆ
isGreaterThan(1) 1๋ณด๋‹ค ํฐ ๊ฐ’์ธ์ง€ ๊ฒ€์ฆ
isLessThen(1) 1๋ณด๋‹ค ์ž‘์€ ๊ฐ’์ธ์ง€ ๊ฒ€์ฆ

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ

ํด๋ž˜์Šค ์ด๋ฆ„ ์œ„์— ๋งˆ์šฐ์Šค ์ปค์„œ๋ฅผ ๋†“๊ณ  Alt+Enter๋ฅผ ๋ˆ„๋ฅด๋ฉด ํ…Œ์ŠคํŠธ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ฐฝ์ด ์ƒ๊ธด๋‹ค. 

OK๋ฅผ ๋ˆ„๋ฅด๊ฒŒ ๋˜๋ฉด `ํด๋ž˜์Šค์ด๋ฆ„Test.java` ํŒŒ์ผ์ด test/java/ํŒจํ‚ค์ง€ ์•„๋ž˜์— ์ƒ์„ฑ๋œ๋‹ค. 

@SpringBootTest // ํ…Œ์ŠคํŠธ์šฉ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ
@AutoConfigurationMocMvc // MockMVC ์ƒ์„ฑ ๋ฐ ์ž๋™ ๊ตฌํ˜„
class TestControllerTest{
	@Autowired
    protected MockMvc mockMvc;
    
    @Autowired
    private WebApplicationContext context;
    
    @Autowired
    private PetRepository petRepository;
    
    @BeforeEach
    public void mockMvcSetUp(){
    	this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }
    
    @AfterEach
    public void cleanUp(){
    	petRepository.deleteAll();
    }
    
    @DisplayName("getAllPets test")
    @Test
    public void getAllPets() throws Exception{
    	// given
        final String url = "/test";
        Pet savedPet = petRepository.save(new Pet(1l, "Rose"));
        
        // when
        final ResultActions result = mockMvc.perform((get(url)
        	.accept(MediaType.APPLICATION_JSON));   
    }
    
    // then
    result
    	.andExpect(status().isOk())
        .andExpect(jsonPath("$[0].id").value(savePet.getId()))
        .andExpect(jsonPath("$[0].name").value(savePet.getName()));
}
  • @SpringBootTest
    • @SpringBootTest ์• ๋„ˆํ…Œ์ด์…˜์€ ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํด๋ž˜์Šค์— ์ถ”๊ฐ€ํ•˜๋Š” ์• ๋„ˆํ…Œ์ด์…˜์ธ @SpringBootApplication์ด ์žˆ๋Š” ํด๋ž˜์Šค๋ฅผ ์ฐพ๊ณ  ๊ทธ ํด๋ž˜์Šค์— ํฌํ•จ๋˜์–ด ์žˆ๋Š” ๋นˆ์„ ์ฐพ๊ณ  ํ…Œ์ŠคํŠธ์šฉ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค.
  • @AutoConfigureMockMvc
    • @AutoConfigureMockMvc์€ MockMvc๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ž๋™์œผ๋กœ ๊ตฌ์„ฑํ•˜๋Š” ์• ๋„ˆํ…Œ์ด์…˜์ด๋‹ค. 
    • MockMvc๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„œ๋ฒ„์— ๋ฐฐํฌํ•˜์ง€ ์•Š๊ณ ๋„ ํ…Œ์ŠคํŠธ์šฉ MVCํ™˜๊ฒฝ์„ ํ†ตํ•ด ์š”์ฒญ ๋ฐ ์ „์†ก, ์‘๋‹ต ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค์ด๋‹ค. 
    • ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. 
  • perform()
    • ์š”์ฒญ์„ ์ „์†กํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค. 
    • ResultActions ๊ฐ์ฒด๋ฅผ ๊ฒฐ๊ณผ๋กœ ๋ฐ›์•„์˜ค๋ฉฐ ResultActions๋Š” ๋ฐ˜ํ™˜๊ฐ’์„ ๊ฒ€์ฆํ•˜๊ณ  ํ™•์ธํ•˜๋Š” andExpect() ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. 
  • accept()
    • ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ๋ฌด์Šจ ํƒ€์ž…์œผ๋กœ ์‘๋‹ต์„ ๋ฐ›์„์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค. 
    • ์œ„ ์ฝ”๋“œ์—์„œ๋Š” JSON์œผ๋กœ ๋ฐ›๋Š”๋‹ค. 
  • andExpect()
    • ๋ฉ”์„œ๋“œ ์‘๋‹ต์„ ๊ฒ€์ฆ
  • jsonPath("$[0].${ํ•„๋“œ๋ช…}")
    • JSON ์‘๋‹ต๊ฐ’์„ ๊ฐ€์ ธ์˜ค๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰

 

** HTTP ์ฃผ์š” ์‘๋‹ต ์ฝ”๋“œ ๊ฒ€์ฆ **

๋งคํ•‘ ๋ฉ”์„œ๋“œ ์ฝ”๋“œ
isOK() 200 OK
isCreated() 201 Created
isBadRequest() 400 Bad Request
isForbidden() 403 Forbidden
isNotFound() 404 Not Found
is4xxClientError() 400๋ฒˆ๋Œ€ ์ฝ”๋“œ
isInternalServerError() 500 Internal Server Error
is5xxServerError() 500๋ฒˆ๋Œ€ ์ฝ”๋“œ