어디까지 갈 수 있을까?

테스트 코드, JPA 본문

책/스프링부트와 AWS로 혼자 구현하는 웹 서비스

테스트 코드, JPA

_Min 2021. 3. 12. 21:11

 

패키지 이름 소문자로

클래스, 인터페이스 이름 파스칼 표기법

변수명 카멜 표기법

 

1. 테스트 코드 

TDD와 단위 테스트 차이

TDD(Test Driven Development) : 테스트 주도 개발, 테스트 코드를 먼저 작성하는 것

단위 테스트 : 기능 단위의 테스트 코드를 작성하는 것을 말함

 

단위 테스트를 하면 빠른 피드백을 얻을 수 있고 자동검증이 가능해

테스트 코드로 먼저 검증 후 그래도 못 믿겠을 때 프로젝트 실행해 확인하는 것 추천

 

 

Applicaiton 클래스

앞으로 만들 프로젝트의 메인클래스

 

@SpringBootApplication 

스프링 Bean 읽기와 생성 가능하게 함, 해당 어노테이션이 있는 위치부터 읽기때문에 항상 프로젝트 최상단에 위치해야 한다.

 

SpringApplication.run

내장 WAS(톰캣)을 실행

 

 

 

문자열 출력 컨트롤러 생성

 

@RestController

JSON을 반환하는 컨트롤러 만들어줌

@ResponseBody를 메소드마다 자동으로 붙여준다고 생각

 

@GetMapping

Get 요청을 받을 수 있는 API 생성

 

 

 

 

문자열 출력 컨트롤러 테스트

테스트할 패키지를 만들어주고 테스트할 클래스이름 + Test를 붙여준다

 

 

@RunWith(SpringRunner.class)

스프링 부트 테스트와 JUnit 사이에 연결자 역할

 

@WebMvcTest

Controller Layer만 테스트 가능 하기 때문에 @SpringBootTest에 비해 가볍다

 

@Autowired

스프링이 관리하는 빈 주입

 

private MockMvc mvc

http get, post 메소드 등에 대한 테스트를 할 수 있다. mvc 목업을 만들어준다고 생각하자

 

mvc.perform(get("/hello"))

/hello 주소로 http get 요청을 한다

 

.andExpect(status().isOk())

mvc.perform의 결과를 검증한다

http header의 status를 검증한다. 여기서는 200인지 아닌지 검증

* 2xx 성공, 4xx 클라이언트 오류, 5xx 서버 오류

 

.andExpect(content().string(hello))

응답 본문의 내용이 hello가 맞는지 검증

 

 

롬복

implementation 'org.projectlombok:lombok'
testCompile "org.projectlombok:lombok"
annotationProcessor('org.projectlombok:lombok')
testAnnotationProcessor('org.projectlombok:lombok')

기본생성자, Getter, Setter, toString 자동 생성

 

 

문자열 출력 컨트롤러 롬복으로 전환하기

@Getter

선언된 모든 필드에 get 메소드 생성

 

@RequiredArgsConstructor

선언된 모든 final 필드가 포함된 생성자 생성

 

@AllArgsConstructor

모든 필드가 포함된 생성자 생성

 

 

@Getter, @RequiredArgsConstructor 제대로 동작하는지 테스트

 

assertThat

검증 메소드

 

isEqualTo

동등 비교 메소드

 

처음에 Assertions 입력하고 assertj 선택한 다음 alt+enter로 Assertions를 static import 하면 편하게 사용 가능하다

 

 

롬복_기능_테스트를 하려고 하니 variable name not initialized in the default constructor 오류 발생.

위의 코드블럭에 적어놓은 대로 build.gradle에 적으면 오류가 안 난다. 

롬복 기능 테스트 완료

 

 

파라미터 출력 컨트롤러

 

HelloController.java

@RequestParam

외부에서 API로 넘긴 파라미터를 가져오는 어노테이션

 

 

파라미터 출력 테스트

 

HelloControllerTest.java

jsonPath

JSON 응답값을 필드별로 검증

$를 기준으로 필드명을 명시

 

 

 

2. JPA

JPA는 자바 표준 ORM, 객체와 DB를 매핑

MyBtis, iBatis는 SQL Mapper, 쿼리와 DB를 매핑

 

JPA를 통해

객체지향적으로 DB에 접근할 수 있음(부모 자식 관계 표현, 상태·행위를 한 곳에서 관리)

SQL에 종속적인 개발을 하지 않아도 됨

 

Spring Data JPA

기본 CRUD(Save(), findAll(), findOne()...)을 인터페이스로 갖고 있어 인터페이스만으로 기본 기능 사용 가능

 

 

build.gradle 추가

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'

 

요구사항 분석

 

 

게시글 저장 엔티티 생성

주요 어노테이션을 클래스에 가깝게 둠

 

@Entity

테이블과 매핑될 클래스 명시

 

@Id

PK필드

 

@GeneratedValue

PK 생성 규칙

 

@Column

테이블의 칼럼 명시

 

@NoArgsConstructor

기본 생성자 자동 추가

 

@Builder

해당 클래스의 빌더 패턴 클래스를 생성

어느 필드에 어떤 값을 채울지 명확하게 인지할 수 있다

 

@NoArgsContructor 파라미터가 없는 생성자 생성
@RequiredArgsConstructor final 필드, @NonNull 필드 생성자 생성
@AllArgsConstructor 모든 필드에 대한 생성자 생성

 

Entity 클래스에서는 Setter 메소드를 만들지 않는다

값 삽입은 생성자를 통해 하고, 변경하고자 하는 레코드의 id값을 넣은 후 다른 값들을 넣어주면 update 문이 실행된다

 

 

게시글 저장 리포지토리 생성

 

JpaRepository가 DB접근을 가능하게 한다

인터페이스 생성 후 JpaRepository<Entity 클래스, PK타입>을 상속하면 기본 CRUD 메소드가 자동 생성

Entity 클래스Repository는 밀정한 관계이기 때문에 도메인 패키지에 함께 위치

@Repository를 추가할 필요가 없다

*기본 CRUD 메소드 : save, findAll, findById, findOne, deleteAll, deleteById, delete

 

 

게시글 저장 리포지토리 테스트

 

@After

단위 테스트가 끝날때마다 수행되는 메소드를 지정

 

postsRepository.save

id값이 있다면 update, 없다면 insert 쿼리가 실행

 

postsRepository.findAll

모든 데이터 조회

 

 

show_sql : 쿼리 로그 확인

MySQL5InnoDBDialect : 쿼리 로그를 MySQL 버전으로 변경

 

org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into posts (author, content, title) values (?, ?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement 오류 발생

 

spirngboot 버전만 2.1.9.RELEASE로 바꿔주고 빌드하면 해결된다

 

 

spring 웹 계층

Web Layer : 눈에 보이는 화면과 소통 / 컨트롤러, 뷰 템플릿, 필터, 인터셉터, 컨트롤러 어드바이스 등 외부 요청과 응답

Service Layer : 트랜잭션, 도메인 기능 간의 순서를 보장

Repository Layer : DB에 접근

Domain Model : @Entity, 비지니스 처리 담당

DTO : Request 데이터를 받음, 계층간 데이터 교환 

 

 

게시글 등록/수정/조회 API 만들기

Request 데이터 받을 Dto
API 요청 받을 Controller
트랜잭션, 도메인 기능 간의 순서를 보장 Service 생성

 

스프링에서는 Bean을 주입받을 때 생성자로 주입받는 걸 권장함

@RequiredArgConstructor가 생성자 역할 해 줌

 

 

Entity 클래스와 거의 유사한 형태임에도 Dto 클래스를 추가로 생성한 이유:

Entity 클래스를 기준으로 테이블이 생성되고, 스키마가 변경되기 때문에 매우 중요한 클래스이다.

화면 변경과 같은 사소한 일이 일어났을 때 테이블과 연결된 Entity 클래스를 변경하는 것이 아니라 Dto 클래스를 이용한다.

 

Dto는 View를 위한 클래스라 자주 변경이 일어난다

View Layer와 DB Layer의 역할 분리 철저히 해주기

 

Entity : DB 접근

Dto : 뷰의 데이터 다른 계층에 넘겨주기

 

 

게시글 등록/수정/조회 API 테스트

@WebMvcTest는 JPA 기능이 작동하지 않기 때문에 

JPA 기능까지 한 번에 테스트 할 때는 @SpringBootTest와 TestRestTemplate 사용

 

다시보기???

 

게시글 수정/조회 기능

 

PostsApiController.java

Dto는 Entity를 받아 처리함

RequestDto도 별도로 두는 이유는 여러테이블에 동시에 나눠 저장되거나,
일부는 Redis에 일부는 데이터베이스에 저장해야될 수 있기 때문

 

Posts.java

 

PostsService.java

update 기능에서 쿼리를 날리는 부분이 없는 이유

JPA의 영속성 컨텍스트 때문 *엔티티를 영구 저장하는 환경

JPA의 엔티티 매니저가 활성화된 상태로 트랜잭션 안에서 데이터를 가져오면 영속성 컨텍스트가 유지된 상태

이 상태에서 해당 데이터의 값을 변경하면 트랜잭션이 끝나는 시점에 해당 테이블에 변경분을 반영

즉, Entity 객체의 값만 변경하면 별도로 Update 쿼리를 날릴 필요가 없다

이 개념을 더티 채킹

 

 

게시글 수정/조회 기능 테스트

PostsApiControllerTest.java

 

 

application.properties에 h2 데이터베이스 웹 콘솔 옵션 활성화

 

 

 

JPA Auditing

엔티티에는 생성시간과 수정시간을 포함해 유지보수 용이하게 함

JPA Auditiing은 Spring Data JPA에서 시간에 대해서 자동으로 값을 넣어주는 기능

 

BaseTimeEntity는 모든 Entity의 상위 클래스가 되어 Entity들의 createdDate, modifiedDate를 자동으로 관리하는 역할

 

@MappedSuperclass

JPA Entity 클래스들이 BaseTimeEntity를 상속할 경우 필드들(createdDate, modifiedDate)도 칼럼으로 인식하도록 함

 

@EntityListeners(AuditingEntityListener.class)

BaseTimeEntity 클래스에 Auditing 기능을 포함

 

@CreatedDate

Entity가 생성되어 저장될 때 시간이 자동 저장

 

@LastModifiedDate

Entity의 값을 변경할 때 시간이 자동 저장

 

 

@EnableJpaAuditing

Jpa Auditing 어노테이션 모두 활성화

 

 

JPA Auditing 테스트

BaseTimeEntity를 상속받으면 등록일/수정일이 자동으로 해결된다

 

 

 

 

 

출처 : 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 (이동욱 저)

 

728x90
Comments