스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 읽고 - 1일차

박세건·2023년 8월 29일
0

스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 읽고 내가 혼자 만들어보았던 토이 프로젝트와 이동욱님이 작성하신 스프링부트 프로젝트의 차이를 알고 더 좋은 방향으로 배우고 싶기에 진행하게되었다!!

인텔리제이로 스프링 부트 시작하기

우선 본격적으로 스프링 부트 개발을 진행하기전에 인텔리제이에서 스프링부트 세팅과 깃허브 연결을 해보자.

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.15'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.qkrtprjs'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '1.8'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

빌드는 기본적인 형태로 작성하였고, 인텔리제이 안에있는 share project on github 라는 action을 통해서 repository를 만들어서 커및&푸쉬 해서 연동시켰다.

스프링 부트에서 테스트 코드를 작성하자

이전에 만들어보았던 게시판 토이 프로젝트를 진행하면서 테스트 코드의 중요성을 무시하고 작동되는 화면을 보면서 잘 작동되는지를 테스트해서 진행했었는데 진행하면서 테스트 코드의 중요성을 느끼게 되었고 마침 테스트 코드에 대해서 알려주니 잘 배워가도록 하자!

테스트 코드 작성의 기본

package com.qkrtprjs.springbootproject.web;


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

hello 를 반환하는 컨트롤러를 하나 만들어주고 테스트로 넘어갑니다.

테스트 작성할때 보통 구조를 이전에 만들어 놓았던 controller의 구조와 동일하게 만들어줍니다.
테스트할 클래스명은 기존에 만들어 놓았던 클래스에 + Test 를 추가해줍니다.

package com.qkrtprjs.springbootproject.web;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@RunWith(SpringRunner.class)
//Junit에 내장된 샐행자 외에 다른 실행자를 실행시킨다. 여기서는 SpringRunner라는 스프링 실행자를 사용, 스프링 부트 테스트와 Junit 사이에 연결자 역할
@WebMvcTest //Web에 집중할 수 있는 스프링 테스트 어노테이션, @Controller, @ControllerAdvice 사용가능 서비스나 레파지토리는 사용 불가
public class HelloControllerTest {  //이름은 보통 대상클래스이름에 Test를 추가로 붙여준다 구조는 생성했던 구조와 비슷하게 만들어준다.

    @Autowired
    private MockMvc mvc;
    //스프링 api를 테스트하는데에 여러가지 방법이 있지만 MockMVC는 가상의 서버를 동작시키기때문에 더 빠른 속도로 테스트를 할 수 있다.

    @Test
    public void hello가_리턴된다() throws Exception {
        String hello = "hello";

        mvc.perform(get("/hello"))
                .andExpect(status().isOk()) //정상인지 Status 코드 확인
                .andExpect(content().string(hello));
    }
}

Mock 클래스들은 정보처리기사 공부할 때 접해봤던거 같은데 이렇게 직접 사용하니 신기했다.
실제로 웹에서 어떤 결과를 가져오는지 알기 위해서 사용한다.
테스트 내용은 /hello 주소로 get 방식으로 접근했을때에 결과(content())가 hello와 동일한지를 확인하는것이다 또한 정상인지도 확인한다.

정상적으로 통과한다.

직접 애플리케이션을 실행시키고 확인해봐도 결과는 동일했다.

롬복 테스트

package com.qkrtprjs.springbootproject.web.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
    private final String name;
    private final int amount;
}

dto 하나를 만들어주고
@Getter
@RequiredArgsConstructor
을 추가한다.
getter는 말 그대로 getter를 생성해주고
requiredArgsConstructor는 final로 지정된 필드들의 생성자를 만들어준다
모든 필드를 기준으로 생성자를 만들고 싶다면 AllArgsConstructor를 사용!

package com.qkrtprjs.springbootproject.web.dto;

import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class HelloResponseDtoTest {

    @Test
    public void 롬복기능_테스트() {
        //given
        String name = "test";
        int amount=1000;
        //when
        HelloResponseDto dto = new HelloResponseDto(name,amount);
        //then
        assertThat(dto.getName()).isEqualTo(name);
        //테스트 검증 라이브러리의 검증 메소드 검증하고 싶은 댓상을 메소드 인자로 받는다.
        assertThat(dto.getAmount()).isEqualTo(amount);
    }
}

assertj의 assertThat함수를 사용해서 검증한다.
Junit의 assertThat보다 장점 : 추가적인 라이브러리가 필요없다 + 자동완성을 지원한다.

MockMVC를 이용한 JSON 리턴 테스트

    @GetMapping("/hello/dto")
    public HelloResponseDto helloResponseDto(@RequestParam("name") String name, @RequestParam("amount") int amount) {
        return new HelloResponseDto(name, amount);
    }
    

테스트하기위한 함수를 하나 만들어주고 @RestController로 작성되어있기때문에 JSON형태로 반환한다.

 @Test
    public void helloDto가_리턴된다() throws Exception {
        String name = "hello";
        int amount = 1000;

        mvc.perform(get("/hello/dto")
                        .param("name", name)
                        .param("amount", String.valueOf(amount)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("name").value(name))	//json응당값을 필드별로 검증할 수 있는 메소드
                .andExpect(jsonPath("amount").value(amount));
    }

GET방식으로 name과 amount를 넘겨주었을때에 결과가 일치하는지를 확인한다.

스프링 부트에서 JPA로 데이터베이스를 다뤄보자

프로젝트에 Spring Data Jpa 적용하기

implementation("org.springframework.boot:spring-boot-starter-data-jpa")
 implementation 'com.h2database:h2:'

의존성을 추가합니다.
간단한 설명으로 spring-boot-starter-data-jpa는 스프링 부트용 spring data jpa 추상화 라이브러리입니다.
h2 : 인메모리 관계형 데이터베이스입니다. 별도의 설치가 필요없이 프로젝트의 의존성만으로 관리할 수 있습니다. 메모리에서 실행되기 때문에 애플리케이션을 재시작할 때마다 초기화된다는 점을 이용하여 테스트 용도로 많이 사용됩니다.

domain이라는 패키지를 하나 만들어줍니다.
용도 : 게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제 영역 이라고 생각하자.

Entity객체를 하나 만들어줍니다.

package com.qkrtprjs.springbootproject.domain.posts;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

//해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드상으로 명확하게 구분할 수가 없어, 차후 기능 변경 시 정말 복잡해진다.
//따라서 Entity클래스에는 절대로 Setter메소드를 만들지 않습니다. 값 변경이 필요하다면 명확한 목적과 의도를 갖는 메소드를 추가한다
@Getter
@NoArgsConstructor
@Entity
public class Posts {
    @Id //해당 필드가 PK임을 나타냄
    @GeneratedValue(strategy = GenerationType.IDENTITY) //PK 생성 규칙을 나타낸다.
    private Long id;    //Long 타입에 Auto_increment 추천

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;

    }
}

해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드상으로 명확하게 구분할 수가 없어, 차후 기능 변경 시 정말 복잡해진다.
따라서 Entity클래스에는 절대로 Setter메소드를 만들지 않습니다. 값 변경이 필요하다면 명확한 목적과 의도를 갖는 메소드를 추가한다

여기서 명확한 목적과 의도를 예시를 들면

//변경전
public void  주문서비스의_취소이벤트(){
        order.setStatus(false);
        }
       
       //변경후
public void  주문서비스의_취소이벤트(){
    order.cancelOrder();
        }

Repository interface도 만들어줍니다.

package com.qkrtprjs.springbootproject.domain.posts;

import org.springframework.data.jpa.repository.JpaRepository;

public interface PostsRepository extends JpaRepository<Posts, Long> {
}

PostsRepository를 테스트할 클래스를 만들어줍니다.
생성하고 제대로 저장이 되었는지를 확인합니다.

package com.qkrtprjs.springbootproject.domain.posts;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;


@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {

    @Autowired
    PostsRepository postsRepository;

    @After
    public void cleanup() {
        postsRepository.deleteAll();

    }

    @Test
    public void 게시글저장_불러오기() {
        //given
        String title = "테스트 게시글";
        String content = "테스트 본문";

        postsRepository.save(
                Posts.builder()
                        .title(title)
                        .content(content)
                        .build()
        );

        //when
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);
        assertThat(posts.getTitle()).isEqualTo(title);
        assertThat(posts.getContent()).isEqualTo(content);
    }

}


정상적으로 테스트가 성공했고
이때 어떤 쿼리로 DB에 테이블이 만들어지고 Posts가 등록되고 삭제되고 조회되는지 보고싶다면
application.yml에 아래 구문을 추가해주고

spring:
  jpa:
    show-sql: true

책에서 mysql 버전으로 쿼리를 보고싶다면

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect

를 추가하라고 했지만 이 구문은 deprecated(더 이상 사용하지 않음)되었기때문에

spring:
  jpa:
    show-sql: true
    generate-ddl: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
  datasource:
    url: jdbc:h2:mem:db;MODE=MYSQL

이렇게 수정해줍니다!

profile
멋있는 사람 - 일단 하자

0개의 댓글