본문 바로가기

WINK-(Web & App)/Spring Boot 스터디

[2024 Spring Boot 스터디] 황수민 #2 주차 3~4장

반응형

3장 스프링 부트 3 구조 이해하기

3.1 스프링 부트 3 구조 살펴보기

계층

스프링 부트의 각 계층은 자신의 책임에 맞는 역할을 수행하며, 필요에 따라 소통한다.

 

  • 프레젠테이션 계층 (Controller)

HTTP 요청을 받고 이 요청을 비즈니스 계층으로 전송하는 역할

 

  • 비즈니스 계층 (Service)

모든 비즈니스 로직을 처리하는 역할

더보기

'비즈니스 로직'이란?

서비스를 만들기 위한 로직이다.

(데이터를 처리하기 위한 로직, 예외 처리 로직, 프로세스를 구현하기 위한 로직 등)

  • 퍼시스턴스 계층 (Repository)

모든 데이터베이스 관련 로직을 처리하는 역할.

이 과정에서 데이터베이스 계층과 상호작용하기 위한 객체인 DAO를 사용할 수도 있다.

 

프로젝트 디렉터리 구성

스프링 부트에는 정해진 프로젝트 구조는 X, 그렇지만 많은 개발자들이 따르는 추천 프로젝트 구조가 존재한다.

 

스프링 부트 3 프로젝트의 디렉토리 구성

  • main

실제 코드를 작성하는 공간이며, 프로젝트 실행에 필요한 소스 코드나 리소스 파일이 모두 들어있다.

 

  • test

테스트 코드나 테스트 리소스가 들어있다.

 

  • build.gradle

빌드를 설정하는 gradle 파일이며, 의존성이나 플러그인 설정 등과 같이 빌드에 필요한 설정을 할 때 사용한다.

 

  • setting.gradle

빌드할 프로젝트의 정보를 설정한다.

 

main 디렉터리 구성하기

1. 뷰 관련 파일을 넣을 templates 디렉터리 만들기

 

resources에 templates 경로 생성

 

2. 스프링 부트 설정을 할 수 있는 application.yml 파일 생성

 

더보기

application.yml 파일은 무슨 역할을 할까?

 

스프링 부트 서버가 실행되면 자동으로 로딩되는 파일.

데이터베이스의 설정 정보, 로깅 설정 정보 등이 들어갈 수 있고, 직접 설정을 정의할 때 사용하기도 한다.

resources에 application.yml 파일 생성

 

 


3.2 프로젝트 발전시키기

 

의존성을 추가한 다음에 프레젠테이션 계층, 비즈니스 계층, 퍼시스턴스 계층 순서대로 코드를 추가해보자.

 

build.gradle에 의존성 추가하기

 

추가할 라이브러리

  • 스프링 데이터 JPA: 스프링 부트용 JPA
  • H2: 로컬 환경과 테스트 환경에서 사용할 인메모리 데이터베이스
  • Lombok: 반복 메서드 생성 작업을 줄여주는 라이브러리

 

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

build.gradle

 

 

gradle에서 의존성을 추가하는 키워드별 역할

 

키워드 역할
implementation 컴파일 시점과 런타임에 모두 필요할 때 사용
testImplementation 테스트 코드를 컴파일하고 실행할 때만 필요할 때 사용
runtimeOnly 런타임에만 필요할 때 사용
compileOnly 컴파일 시에만 필요할 때 사용
annotationProcessor 컴파일 시에 애너테이션을 처리하는 상황에 필요할 때 사용

 

프레젠테이션, 서비스, 퍼시스턴스 계층 만들기

아래와 같이 코드를 작성한다.

 

TestController.java (프레젠테이션 계층, Controller)

package com.github.tnals0924.springbootdeveloper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class TestController {
    @Autowired
    TestService testService;

    @GetMapping("/test")
    public List<Member> getAllMembers() {
        List<Member> members = testService.getAllMembers();
        return members;
    }
}

 

TestService.java (서비스 계층, Service)

package com.github.tnals0924.springbootdeveloper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class TestService {
    @Autowired
    MemberRepository memberRepository;

    public List<Member> getAllMembers() {
        return memberRepository.findAll();
    }
}

 

MemberRepository.java (퍼시스턴스 계층, Repository)

package com.github.tnals0924.springbootdeveloper;

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

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}

 

Member.java (DAO 객체)

package com.github.tnals0924.springbootdeveloper;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", updatable = false)
    private long id;

    @Column(name = "name", nullable = false)
    private String name;
}

 

이제 실행하기 위해 데이터베이스 관련 설정을 해주자.

 

resources 경로에 data.sql 파일을 생성하고 아래와 같이 테스트용 SQL문을 생성한다.

 

INSERT INTO member (id, name) VALUES (1, 'name 1')
INSERT INTO member (id, name) VALUES (2, 'name 2')
INSERT INTO member (id, name) VALUES (3, 'name 3')

 

이전에 생성해뒀던 application.yml 파일에 아래와 같이 작성해준다.

 

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true

    defer-datasource-initialization: true

 

 

스프링 부트 애플리케이션을 재실행하고, 잘 실행되었는지 확인하기 위해 포스트맨으로 HTTP 요청을 시도해본다.

 

포스트맨으로 localhost:8080/test 에 GET 요청을 보낸 결과

 


3.3 스프링 부트 요청-응답 과정

  1. 포스트맨에서 톰캣(WAS)에 /test GET 요청을 한다.
  2. 요청은 스프링 부트 내로 이동하여 스프링 부트의 디스패처 서블릿이 URL을 분석하고, 이 요청을 처리할 수 있는 Controller를 찾는다.
  3. TestController에서 /test GET 요청을 수행할 수 있으므로 getAllMembers()와 요청이 매치되고, 비즈니스 계층(TestService)과 퍼시스턴스 계층(MemberRepository)을 통하면서 필요한 데이터를 가져온다.
  4. 뷰 리졸버는 템플릿 엔진을 사용해 HTML 문서를 만들거나 JSON, XML 등과 데이터를 생성한다.
  5. 그 결과 members를 return하고 그 데이터를 포스트맨에서 볼 수 있게 된다.

 


4. 스프링 부트 3와 테스트

4.1 테스트 코드 개념 익히기

테스트 코드란?

작성한 코드가 의도대로 잘 동작하고 예상치 못한 문제가 없는지 확인할 목적으로 작성하는 코드다.

유지보수에도 좋고, 코드 수정 시 기존 기능이 제대로 작동하지 않을까봐 걱정하지 않아도 된다는 장점이 있다.

 

테스트 코드는 test 디렉터리에서 작업한다.

 

test 디렉터리

 

이 책에서 적용하는 테스트 코드 패턴은 'given-when-then' 패턴이다.

 

given-when-then 패턴

 

테스트 코드를 아래의 3단계로 구성한다.

  • given: 테스트 실행을 준비하는 단계
  • when: 테스트를 진행하는 단계
  • then: 테스트 결과를 검증하는 단계

 

@DisplayName("새로운 메뉴를 저장한다.")
@Test
public void saveMenuTest() {
    //given: 메뉴를 저장하기 위한 준비 과정
    final String name = "아메리카노";
    final int price = 2000;
    final Menu americano = new Menu(name, price);
    
    //when: 실제로 메뉴를 저장
    final long savedId = menuService.save(americano);
    
    //then: 메뉴가 잘 추가되었는지 검출
    final Menu savedMenu = menuService.findById(savedId).get();
    assertThat(savedMenu.getName()).isEqualTo(name);
    assertThat(savedMenu.getPrice()).isEqualTo(price);
}

 


 

4.2 테스트

Junit이란?

자바 언어를 위한 단위 테스트 프레임워크이다.

단위 테스트란, 작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것을 의미한다.

 

Junit으로 단위 테스트 코드 만들기

 

src/test/java 폴더에 JUnitTest.java 파일 생성하고 코드 작성하기

 

@DisplayName("1 + 2는 3이다.")
@Test
public void junitTest() {
    int a = 1;
    int b = 2;
    int sum = 3;

    Assertions.assertEquals(sum, a + b);
}

 

테스트 결과

 

이번엔 실패하는 테스트 케이스를 입력해보자

 

    @DisplayName("1 + 3는 4이다.")
    @Test
    public void junitFailedTest() {
        int a = 1;
        int b = 3;
        int sum = 3;

        Assertions.assertEquals(sum, a + b);
    }

 

아래는 실패하는 테스트 코드를 실행한 결과이다.

 

실패한 테스트 결과

 

반응형