스프링 부트는 각 계층이 양 옆의 계층과 통신하는 구조를 따름
계층 : 각 역할/책임이 있는 sw의 구성 요소
서로 소통 o, 다른 계층에 직접 간섭/영향은 X
프레젠테이션 계층 , 비즈니스 계층, 퍼시스턴스 계층

프레젠테이션 계층
HTTP 요청 받고 → 비즈니스 계층으로 전송하는 역할
컨트롤러가 하는 역할임 (컨트롤러 ? → TestController클래스와 같은 것)
스프링 부트 내에 여러 개가 있을 수 있다
비즈니스 계층
모든 비즈니스 로직 처리 (비즈니스 로직 : 서비스 만들기 위한 로직)
서비스가 하는 역할
퍼시스턴스 계층
모든 DB관련 로직을 처리
이 과정에서 DB에 접근하는 DAO 객체를 사용할 수도 있음
DAO : DB 계층과 상호작용 하기 위한 객체
리포지토리가 하는 역할
계층은 개념의 영역, 컨트롤러/서비스/리포지토리는 실제 구현의 영역
정해진 프로젝트 구조 없지만 추천 프로젝트 구조가 있다
main
실제 코드를 작성하는 공간. 프로젝트 실행에 필요한 소스 코드/리소스 파일이 모두 들어가 있다.
test
프로젝트의 소스 코드를 테스트할 목적의 코드/리소스 파일
build.gradle
빌드를 설정하는 파일. 의존성이나 플러그인 설정과 같이 빌드에 필요한 설정을 할 때 사용
setting.gradle
빌드할 프로젝트의 정보를 설정하는 파일
main 디렉터리 구성하기
templates 디렉터리 : HTML 같은 뷰 관련 파일 넣음
static 디렉터리 : JS/CSS/IMG 같은 정적 파일 넣음
application.yml : 스프링 부트 설정을 할 수 있음 (스프링 부트 서버가 실행되면 자동으로 로딩되는 파일)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org-springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
TestController.java — 프레젠테이션 계층
package me.kimminju.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 — 비즈니스 계층
package me.kimminju.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 라는 빈을 주입받고,
findAll() 메서드를 호출해 멤버 테이블에 저장된 멤버 목록을 모두 가져옴

Member.java
package me.kimminju.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;
}
MemberRepository.java
package me.kimminju.springbootdeveloper;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findAll();
}
resources/data.sql
INSERT INTO member (id, name) VALUES (1, '이름1')
INSERT INTO member (id, name) VALUES (2, '이름2')
INSERT INTO member (id, name) VALUES (3, '이름3')
resources/application.yml
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
(포스트맨 HTTP 요청 시도까지 완료)

포스트맨에서 톰캣에 /test GET 요청 함 → 스프링 부트 내로 이동
디스패처 서블릿이 URL을 분석, 이 요청을 처리할 수 있는 컨트롤러를 찾음
TestController가 /test 라는 패스에 대한 GET 요청을 처리할 수 있는 getAllMembers() 메서드를 가지고 있으므로 디스패처 서블릿은 TestController에게 /test GET 요청을 전달
마침내 /test GET 요청 처리할 수 있는 getAllMembers() 메서드와 이 요청이 매치됨.
getAllMembers() 메서드에는 비즈니스 계층과 퍼시스턴스 계층을 통해 필요한 데이터를 가져옴.
뷰 리졸버는 템플릿 엔진을 사용해 HTML 문서를 만들거나 JSON, XML 등의 데이터를 생성
결과, members 를 return하고 그 데이터를 포스트맨에서 볼 수 있게 됨
(요약)
1. 프레젠테이션 계층은 HTTP 요청 받고 비즈니스 계층으로 전송함
2. 비즈니스 계층은 모두 비즈니스 로직을 처리함. 퍼시스턴스 계층에서 제공하는 서비스를 사용할 수도 있고, 권한 부여/유효성 검사를 하기도 함
3. 퍼시스턴스 계층은 모든 스토리지 관련 로직을 처리함. 이 과정에서 데이터베이스에 접근하기 위한 계층인 DAO를 사용할 수도 있음
테스트 코드는 test 디렉터리에서 작업합니다.
테스트 코드에도 다양한 패턴이 있습니다. 그 중 사용할 패턴은 given-when-then 패턴입니다.
given-when-then 패턴
given: 테스트 실행을 준비하는 단계
when: 테스트를 진행하는 단계
then: 테스트 결과를 검증하는 단계
JUnit은 자바 언어를 위한 단위 테스트 프레임워크입니다.
단위 테스트란, 작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것을 의미합니다. 이때 단위는 보통 메서드가 됩니다.
JUnit을 사용하면 단위 테스트를 작성하고 테스트하는 데 도움을 줍니다. 테스트 결과가 직관적이라 좋습니다.
@Test 애너테이션으로 메서드를 호출할 때마다 새 인스턴스를 생성, 독립 테스트가 가능합니다.
예상 결과를 검증하는 Assertions 메서드를 제공합니다.
자동 실행, 자체 결과를 확인하고 즉각적인 피드백을 제공합니다.
public class JUnitTest {
@DisplayName("1 + 2는 3이다.") // 테스트 이름
@Test // 테스트 메서드
public void junitTest() {
int a = 1;
int b = 2;
int sum = 3;
Assertions.assertEquals(sum, a + b); // 값이 같은지 확인
}


public class JUnitCycleTest {
@BeforeAll // 전체 테스트를 시작하기 전에 1회 실행하므로 메서드는 static으로 선언
static void beforeAll() {
System.out.println("@BeforeAll");
}
@BeforeEach // 테스트 케이스를 시작하기 전마다 실행
public void beforeEach() {
System.out.println("@BeforeEach");
}
@Test
public void test1() {
System.out.println("test1");
}
@Test
public void test2() {
System.out.println("test2");
}
@Test
public void test3() {
System.out.println("test3");
}
@AfterAll // 전체 테스트를 마치고 종료하기 전에 1회 실행하므로 메서드는 static으로 선언
static void afterAll() {
System.out.println("@AfterAll");
}
@AfterEach // 테스트 케이스를 종료하기 전마다 실행
public void afterEach() {
System.out.println("@AfterEach");
}
}
어노테이션-설명
@BeforeAll-전체 테스트를 시작하기 전에 1회 실행(static)
@BeforeEach-테스트 케이스를 시작하기 전마다 실행
@Test-테스트 메서드
@AfterAll-전체 테스트를 마치고 종료하기 전에 1회 실행(static)
@AfterEach-테스트 케이스를 종료하기 전마다 실행

AssertJ
JUnit과 함께 사용해 검증문의 가독성을 높여주는 라이브러리
기대값과 비교갑이 잘 구분되지 않는 Assertion 예
Assertions.assertEquals(sum, a + b);
가독성이 좋은 AssertJ 예
Assertions.assertThat(a + b).isEqualTo(sum);
AssertJ 제공 메서드 - 설명
isEqualTo(A) A 값과 같은지 검증
isNotEqualTo(A) A 값과 다른지 검증
contains(A) A 값을 포함하는지 검증
doesNotContain(A) A 값을 포함하지 않는지 검증
startsWith(A) 접두사가 A인지 검증
endsWith(A) 접미사가 A인지 검증
isEmpty() 비어 있는 값인지 검증
isNotEmpty() 비어 있지 않은 값인지 검증
isPositive() 양수인지 검증
isNegative() 음수인지 검증
isGreaterThan(1) 1보다 큰 값인지 검증
isLessThan(1) 1보다 작은 값인지 검증
@SpringBootTest // 테스트용 애플리케이션 컨텍스트 생성
@AutoConfigureMockMvc // MockMvc 생성 및 자동 구성
class TestControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
@Autowired
private MemberRepository memberRepository;
@BeforeEach // 테스트 실행 전 실행하는 메서드
public void mockMvcSetUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@AfterEach // 테스트 실행 후 실행하는 메서드
public void cleanUp() {
memberRepository.deleteAll();
}
@DisplayName("getAllMembers: 아티클 조회에 성공한다.")
@Test
public void getAllMembers() throws Exception {
// given: 멤버를 저장
final String url = "/test";
Member savedMember = memberRepository.save(new Member(1L, "홍길동"));
// when: 멤버 리스트를 조회하는 API를 호출
final ResultActions result = mockMvc.perform(get(url).accept(MediaType.APPLICATION_JSON));
// then: 응답 코드가 200 OK이고, 반환받은 값 중에 0번째 요소의 id와 name이 저장된 값과 같은지 확인
result.andExpect(status().isOk()) .andExpect(jsonPath("$[0].id").value(savedMember.getId())) .andExpect(jsonPath("$[0].name").value(savedMember.getName()));
}
}
어노테이션-설명
@SpringBootTest @SpringBootApplication이 있는 클래스를 찾고 그 클래스에 포함되어 있는 빈을 찾음 다음 테스트용 애플리케이션 컨텍스트라는 것을 만듬
@AutoConfigureMockMvc MockMvc를 생성하고 자동으로 구성하는 어노테이션, MockMvc는 애플리케이션을 서버에 배포하지 않고도 테스트용 MVC 환경을 만들어 요청 및 전송, 응답 기능을 제공하는 유틸리티 클래스
