Java Spring Boot 006-3 | Spring Boot Tests

Yunny.Log ·2022년 3월 3일
0

Spring Boot

목록 보기
29/80
post-thumbnail

Unit Testing에 대하여

JUnit으로 Test 해보기

  • PostController 테스트 할거야!
  • 그 전에 buld.gradle 수정
	//test일때 실행한다는 것
	//즉 scope가 test일 때를 의미
	testImplementation ('org.junit.vintage:junit-vintage-engine'){
		//junit 빈티지에서 중복되는 아이 있어서 아래 애는 제외할겡 해주는 것
		exclude group : org.hamcrest, module:'hamcrest-core'
	}
  • BoardRepository 지우고

  • BoardEntity 지우려고 하면 에러날 것
    => 이때 safeEdit 으로 지우기 시도하면 지금 지우려는 애가 쓰이고 있는 부분들 모두 보여준다 , 이 부분 잘 처리 후 safeDelete 수행하면 잘 지워지게 됨

  • config 패키지에 JpaAuditConfig 클래스 추가

@Configuration
@EnableJpaAuditing
//테스트하면서 jpa를 활용할텐데 원래는 이 어노테이션 JpaApplication클래스에 있삼
//근데 그렇게 되면 어플리케이션 전체 굴림, 근데 jpa를 다 체크하면서 짜기엔 너무 과함
//우린 몇개 어플리케이션만 굴릴거라서 여기다가 붙여줄 거임
//그래서 거기서 삭제 후 여기서 jpa 를 활용할 것임
public class JpaAuditConfig {
}

PostController

  • 여기서 ReadPost 테스트 수행할거야
  • 테스트 만들라면 alt+enter 눌러서 테스트 누르거나 generate로!

  • 생성 누르면 위와 같이 테스트 클래스가 생성이 된다
    • 새로 보는 함수 多
    • 낯선 기능들 多
      => 자주자주 보면서 익숙하게 해주기

(1) PostControllerTest : ReadPost에 대한 테스트

PostControllerTest

@RunWith(SpringRunner.class)
@WebMvcTest(PostController.class)
//Controller, ControllerAdvice 만 ㄱㄴ
//테스트 사용할 때 지정된 애만 테스트하기위한 어노테이션
//컨트롤러 요청에 들어오는 애들 테스트 하기위함이다 명시
class PostControllerTest {
    //서비스같은애들은 따로 빈객체로 안만들어져서
    // 이렇게 서비스따로 자동와이아해줘도 빈안만드러져
//    @Autowired
//    private PostService postService;
    //그래서 서비스인"척"해줄 아이를 mock으로 만들거야
    @Autowired
    private MockMvc mockMvc;
    //HTTP클라이언트인척하는애
    //perform이라는 메소드로 http에게 해당
    //url요청을 보낸 것 같이 행동

    @MockBean
    //실제로 만들어지지 않은 빈을 임의로 만든 것
    //
    private PostService postService;

    @Test
    void readPost() throws Exception{//perfoem이 예외 던질 것 대비해서
        //3단계로 테스트 케이스
        //1단계 : given : 어떤 데이터가 준비 돼있다
        //PostEntity가 존재할 때(PostService가 PostEntity를 잘 돌려줄 때)
        final int id = 10;
        PostDto testDto = new PostDto();
        testDto.setId(10);
        testDto.setTitle("Unit Title");
        testDto.setContent("Unit Content");
        testDto.setWriter("unit");

        //위의 아이는 아무기능도 가지지 않음 - 역할 부여 해야함
        //mockito.BDDMockito 에서 기능 부여해줄 given 사용
        //postService가 이 메소드를 수행하면 일어날 일을 정의해주는 것
        given(postService.readPost(id)).willReturn(testDto);

        //2단계 : when : 어떠한 행위가 일어났을 때(함수호출 등)
        //경로에 GET 요청이 온다면
        final ResultActions actions = mockMvc.perform(get("/post/10"))
                .andDo(print());
        //actions라는 아이에게는 저러한 결과가 담기게 된다

        //3단계 : then : 어떤 결과가 올 것인지
        //PostDto가 반환된다
        actions.andExpectAll(
                status().isOk(),//status가 200인지
                content().contentType(MediaType.APPLICATION_JSON),
                //돌아온 값이 json인지 확인 & 아래는 json을 위한 정규표현식
                //$는 제이슨 한 문서를 의미 - 그 안에 title의 키 값이 is에 들가는 건지
                jsonPath("$.title", is("Unit Title")),
                jsonPath("$.content",is("Unit Content")),
                jsonPath("$.writer", is("unit"))

        );
        //actions에 정의된 함수들이 andExpectAll을 통해 출력됨
    }

    @Test
    void readPostAll() {
    }
}
  • PostControllerTest에서 오른쪽 마우스 누르고 run test 하고 콘솔을 확인해본다면
    1) MocoRequest가 보내졌다는 사실과

    2) 우리가 set한 Dto가 잘 response로 받아와졌음을 알 수 있음

(2) PostControllerTest : ReadPostAll 에 대한 테스트

  • ReadPostAll은 하나보다 많은 dto들 필요하므로 여러개의 test dto를 만들어주자 -> 그리고 테스트 진행!
    @Test
    void readPostAll() throws Exception{
        //given : 어떤 데이터가 주어질 것이다
        //given으로 할 때 given(내가테스트할함수).willreturn(내가 지정한 데이터)
        //까지 지정해줘야 when으로 넘어가기
        PostDto post1 = new PostDto();
        post1.setTitle("title1");
        post1.setContent("test1");
        post1.setWriter("testw1");

        PostDto post2 = new PostDto();
        post2.setTitle("title2");
        post2.setContent("test2");
        post2.setWriter("testw2");

        List<PostDto> readAllPost = Arrays.asList(post1,post2);
        given(postService.readPostAll()).willReturn(readAllPost);

        //when
        final ResultActions actions = mockMvc.perform(get("/post"))
                .andDo(print());
        //then
        actions.andExpectAll(
                status().isOk(),
                content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON),
                jsonPath("$",hasSize(readAllPost.size()))
        );
    }
  • 결과는 아래와 같이 잘 통과됨

(3) PostControllerIntegrationTest

WebMVC/DataJpa 는 일부분만 체크

SpringBootTest는 스프링 어플리케이션 전체 테스트

@RunWith(SpringRunner.class)
@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)//얘는 모든 빈을 테스트하기 위한 어노테이션

@AutoConfigureMockMvc
//웹 애플리케이션에서 컨트롤러를 테스트할 때, 서블릿 컨테이너를 모킹하기 위해서
// @WebMvcTest를 사용하거나 @AutoConfigureMockMvc를 사용
@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
@AutoConfigureTestDatabase
public class PostControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;

@Autowired
private PostRepository postRepository;

@Before
//테스트 진행하면서 임의의 엔티티들 많이 만들어줘야 해서
//아예 함수로 빼준 상황
public void setEntities(){
    createTestPost("first post", "first post content", "first writer");
    createTestPost("second post", "second post content", "second writer");
}


//read 테스트를 할 때 아이디로 읽어오기 위해서
@Test
public void givenVadlidId_whenReadPost_then200() throws Exception{
    //given
    Long id = createTestPost("Read Post",  "Created on readPost()", "readPost" );

    //when
    final ResultActions actions = mockMvc.perform(get("/post/{id}",id))
            .andDo(print());

    //then
    actions
            .andExpectAll(
                    status().isOk(),
                    content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON),
                    jsonPath("$.title", is("Read Post")),
                    jsonPath("$.content", is("Created on readPost()")),
                    jsonPath("$.writer", is("readPost"))
            );
}

//테스트용 엔티티 만들어주는 함수

private Long createTestPost(String title , String content, String writer){

    PostEntity postEntity = new PostEntity();

    postEntity.setTitle(title);
    postEntity.setContent(content);
    postEntity.setWriter(writer);
    return postRepository.save(postEntity).getId();

}

}

- 에러가 나는데 아마도 sql 관련 에러인 듯 싶은데 원인을 찾을 수 없다. 나중에 찾도록 할 것

## Test Driven Development란? (심화)

- 테스트 주도 개발
: 실제 작동하는 코드 이전에 통과해야 할 테스트를 우선 만드는 개발 방식

- 코드를 작성하기 전에 미리 테스트를 작성하고서, 이에 맞는 코드를 작성해나가는 것!
(ex) 우리가 아까 삭제한 board관련 아이를 만들기 위해서 미리 test 를 제작해놓고, 이후에 코드를 테스트에 맞춰서 작성해나가는 것

![](https://velog.velcdn.com/images%2Fmyway00%2Fpost%2F0637bd06-8621-4b92-ae76-2b2843fef36f%2Fimage.png)

Red > Green > Refactor

## tdd 를 적용해 Board 제작
### (1) BoardControllerTest : 
![](https://velog.velcdn.com/images%2Fmyway00%2Fpost%2Fd2cf1447-4c24-4385-b5ed-5f389fdf12f1%2Fimage.png)
- 없는 보드 컨트롤러를 만들었으니 당연히 빨간줄
- 타겟 데스티네이션을 main으로 해서 생성
![](https://velog.velcdn.com/images%2Fmyway00%2Fpost%2F8c72dc42-5368-4360-aa85-49d2c852c195%2Fimage.png)
		
`BoardControllerTest`
```java
@RunWith(SpringRunner.class)
@WebMvcTest(BoardController.class)
//없는 보드 컨트롤러를 만들었으니 당연히 빨간줄
//타겟 데스티네이션을 main으로 해서 생성
public class BoardControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void createBoard() throws Exception {
        //(1) given
        BoardDto boardDto = new BoardDto();
        //얘도 똑같이 만들고
        boardDto.setName("notice");
        //setname 해봤는데 boarddto에 없어 그럼 또 추가

        //(2) when
        final ResultActions actions = mockMvc.perform(post("/board"))
                .andDo(print());
        //(3) then
        actions.andExpectAll(
                status().is2xxSuccessful()
        );
    }
  • 위와 같이 설정하고 돌리면 당연히 제대로 돌아가지 않는다
  • 이유는 /board 라는 endpoint가 존재하지 않기 때문
  • BoardController 만들어서 엔드포인트 추가생성

BoardController

@RequestMapping("board")
@RestController
public class BoardController {
}
  • 여전히 클래스에 대해서만 우리가 requestmapping 붙여줌

  • 상세한 메소드들도 정의 & mapping 해줘야 해

  • 일단은 testcode 수정

@RunWith(SpringRunner.class)
@WebMvcTest(BoardController.class)
//없는 보드 컨트롤러를 만들었으니 당연히 빨간줄
//타겟 데스티네이션을 main으로 해서 생성
public class BoardControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void createBoard() throws Exception {
        //(1) given
        BoardDto boardDto = new BoardDto();
        //얘도 똑같이 만들고
        boardDto.setName("notice");
        //setname 해봤는데 boarddto에 없어 그럼 또 추가

        //(2) when

        //여기에 지금 request 요청 임의 만드는 거니깐 requestBody도 추가
        final ResultActions actions = mockMvc.perform(post("/board")
                        .contentType(MediaType.APPLICATION_JSON)
                        //.contentType(boardDto)//boardDto는 스트링 타입이어야지
                        .content(toJson(boardDto))//json의 형태로
                )
                .andDo(print());
        //(3) then
        actions.andExpectAll(
                status().is2xxSuccessful(),
                content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON),
                jsonPath("%.name", is("notice"))

        );
    }
//자바 객체를 byte 로 돌려주는 아이 - Gson으로도 가능
    private byte[] toJson(Object object) throws IOException{
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return mapper.writeValueAsBytes(object);
    }
}

이제 만들면서 Dto도 만들어주고,,

public class BoardDto {
    private String name;
  • 생략했지만 물론 기본생성자랑 생성자랑 게터 세터 구현 ㅇ

컨트롤러도 마저 짜주고

@RequestMapping("board")
@RestController
public class BoardController {
    @PostMapping
    public BoardDto createBoard(@RequestBody BoardDto dto){
        return new BoardDto(dto.getName());
    }
}

테스트 코드도 수정하고
Service 용 MockBean까지 붙여주자

    //서비스 추가 근데 서비스 없어서 제작해주기
    @MockBean
    private BoardService boardService;

그리고 위와 같이 동일하게 서비스 만들어주고 그 안에 dto 를 가지고 수행하고 등등..
코드 익숙해지면 tdd 방식으로 구현 진행진행

에러

PostControllerIntegrationTest 테스트를 수행하다가 발생

Error executing DDL

enerationTarget encountered exception accepting command : Error executing DDL "create table

n is org.hibernate.exception.SQLGrammarException: could not prepare statement

org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into post (created_at, updated_at, content, title, writer) values (?, ?, ?, ?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement

  • 뭔가 sql 구문 오류 같음 찾을 수 없어

0개의 댓글