관리 메뉴

bright jazz music

백엔드 CRUD 테스트 + 페이징 테스트 본문

Projects/react-spring

백엔드 CRUD 테스트 + 페이징 테스트

bright jazz music 2024. 5. 15. 21:38

1. 데이터 추가 테스트

package com.test.mallapi;

import com.test.mallapi.domain.Todo;
import com.test.mallapi.repository.TodoRepository;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDate;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {
    @Autowired
    private TodoRepository todoRepository;

    @Test
    public void testInsert(){
        for (int i =1; i<=100; i++) {
            Todo todo = Todo.builder()
                    .title("title..." + i)
                    .dueDate(LocalDate.of(2023,12,31))
                    .writer("user00")
                    .build();

            todoRepository.save(todo);
        }
    }

}

Hibernate: 
    insert 
    into
        tbl_todo
        (complete, due_date, title, writer) 
    values
        (?, ?, ?, ?)

 

2. 데이터 조회 테스트

 

package com.test.mallapi;

import com.test.mallapi.domain.Todo;
import com.test.mallapi.repository.TodoRepository;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDate;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {
    @Autowired
    private TodoRepository todoRepository;

...
    
    @Test
    public void testRead() {
        // 존재하는 번호로 확인
        Long tno = 33L;
        java.util.Optional<Todo> result = todoRepository.findById(tno);
        Todo todo = result.orElseThrow();
        log.info(todo);
    }
    

}

ptional은 감싸고 있는 값이 null일 수 있는 객체를 처리하기 위한 컨테이너이다. Optional 클래스는 Java 8에서 도입되었으며, null을 직접 다루는 것보다 더 안전한 방법을 제공하여 NullPointerException을 방지할 수 있도록 설계되었다.

Optional 객체는 어떤 값이 존재할 수도 있고, 존재하지 않을 수도 있는 상황에 유용하다. 예를 들어, 데이터베이스에서 특정 값을 조회했을 때 그 값이 존재하지 않을 경우를 안전하게 처리하고자 할 때 Optional을 사용할 수 있다.

예제 코드에서 Optional<Todo>은 Todo 객체가 존재할 수도 있고 아닐 수도 있음을 나타낸다. 여기서 findById(tno) 메소드는 tno에 해당하는 Todo 객체를 찾아 Optional 형태로 반환한다. 객체가 존재하지 않을 경우 Optional은 비어 있는 상태가 된다.

 

orElseThrow() 메소드는 Optional 객체가 값을 갖고 있으면 그 값을 반환하고, 값이 없을 경우 즉시 예외를 발생시킨다. 이렇게 함으로써 값의 존재를 강제할 수 있다.

Todo todo = result.orElseThrow();

이 코드 라인은 result가 비어있다면 예외를 발생시키고, 값이 있다면 Todo 객체를 todo 변수에 할당한다.

 

3. 데이터 수정

엔티티 객체는 가능한 불변하게 만들어지는 것이 좋지만 상황에 따라 수정 가능한 객체를 만들기도 한다.

package com.test.mallapi.domain;

import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDate;

//엔티티를 사용해서 DB와 애플리케이션 사이의 데이터를 동기화 하고 관리
@Entity
@Table(name="tbl_todo")
@Getter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Todo {


    @Id // DB의 pk가 됨
    // 고유한 pk를 가지게 하기 위해서 자동생성 방식을 사용. 이는 PK를 DB에서 자동 생성한다는 의미임.( 마리아디비의 경우 auto_increment)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long tno;

    private String title;
    private String writer;
    private boolean complete;
    private LocalDate dueDate;
    
    //    아래 함수 추가

    public void changeTitle(String title) {
        this.title = title;
    }

    public void chnageComplete(boolean complete) {
        this.complete = complete;
    }

    public void changeDueDate(LocalDate dueDate) {
        this.dueDate = dueDate;
    }
}
@Test
public void testModify() {
    Long tno = 33L;

    // java.util.Optional
    Optional<Todo> result = todoRepository.findById(tno);
    Todo todo = result.orElseThrow();
    todo.changeTitle("Modified 33...");
    todo.changeComplete(true);
    todo.changeDueDate(LocalDate.of(2023,10,10));

    todoRepository.save(todo);

}

 

Hibernate: 
    select
        t1_0.tno,
        t1_0.complete,
        t1_0.due_date,
        t1_0.title,
        t1_0.writer 
    from
        tbl_todo t1_0 
    where
        t1_0.tno=?
Hibernate: 
    select
        t1_0.tno,
        t1_0.complete,
        t1_0.due_date,
        t1_0.title,
        t1_0.writer 
    from
        tbl_todo t1_0 
    where
        t1_0.tno=?
Hibernate: 
    update
        tbl_todo 
    set
        complete=?,
        due_date=?,
        title=?,
        writer=? 
    where
        tno=?

 

findById()에서 한 번 조회, 그리고 save()에서 다시 한 번  select 후 update가 이루어짐

 

 

 

4. 데이터 삭제

 

@Test
public void testDelete() {
    Long tno = 33L;
    todoRepository.deleteById(tno);
}

Hibernate: 
    select
        t1_0.tno,
        t1_0.complete,
        t1_0.due_date,
        t1_0.title,
        t1_0.writer 
    from
        tbl_todo t1_0 
    where
        t1_0.tno=?
Hibernate: 
    delete 
    from
        tbl_todo 
    where
        tno=?

 

JPA가 select이후 update/delete를 수행하는 이뉴는 JPA결과적으로 원하는 것이 애플리케이션의 데이터와 DB의 동기화이기 때문이다. JPA는 객체로 관리되는 상태를 DB에 자동으로 반영해주는데, 이 때문에 DB에만 접근하는 것이 아니라 데이터를 관리하는 존재(엔티티매니저)를 통해서 모든 작업이 이루어진다. 특정한 엔티티 객체를 변화시키기 위해서는 우선 엔티티 매니저의 관리 하에 있어야만 하기 때문에 select문을 통해서 메모리상으로 로딩하는 과정을 수행하게 된다.

 

5. 페이징 처리 테스트

 

Spring Data JPA에서는 Pageable타입을 사용해서 별도의 코드 작성 없이 페이징 처리를 할 수 있다.

JpaRepository에는 findAll()메서드를 통해서 한 번에 페이지에 대한 처리가 가능하다. findAll()의 파라미터 타입인 Pageable은 PageRequest.of(페이지번호, 사이즈)의 형태로 생성하는데 주의할 점은 페이지 번호가 0부터 시작한다는 것이다.

 

findAll()의 결과는 Page<엔티티> 타입으로 생성되는데 데이터의 수가 충분하면 내부적으로 DB에 count쿼리를 같이 실행한다.

 

package com.test.mallapi;

import com.test.mallapi.domain.Todo;
import com.test.mallapi.repository.TodoRepository;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.time.LocalDate;
import java.util.Optional;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {
    @Autowired
    private TodoRepository todoRepository;

   // ..
   .
    @Test
    public void testPaging() {
        //  import org.springframework.data.domain.Pageable;
        Pageable pageable =
                PageRequest.of(0, 10, Sort.by("tno").descending());

        Page<Todo> result = todoRepository.findAll(pageable);

        log.info(result.getTotalElements());
        result.getContent().stream().forEach(todo -> log.info(todo));
    }


}

 

 

Hibernate: 
    select
        t1_0.tno,
        t1_0.complete,
        t1_0.due_date,
        t1_0.title,
        t1_0.writer 
    from
        tbl_todo t1_0 
    order by
        t1_0.tno desc 
    limit
        ?, ?
Hibernate: 
    select
        count(t1_0.tno) 
    from
        tbl_todo t1_0
2024-05-15T21:34:59.469+09:00  INFO 25012 --- [mallapi] [    Test worker] com.test.mallapi.TodoRepositoryTests     : 99
2024-05-15T21:34:59.476+09:00  INFO 25012 --- [mallapi] [    Test worker] com.test.mallapi.TodoRepositoryTests     : Todo(tno=100, title=title...100, writer=user00, complete=false, dueDate=2023-12-31)
2024-05-15T21:34:59.477+09:00  INFO 25012 --- [mallapi] [    Test worker] com.test.mallapi.TodoRepositoryTests     : Todo(tno=99, title=title...99, writer=user00, complete=false, dueDate=2023-12-31)
2024-05-15T21:34:59.477+09:00  INFO 25012 --- [mallapi] [    Test worker] com.test.mallapi.TodoRepositoryTests     : Todo(tno=98, title=title...98, writer=user00, complete=false, dueDate=2023-12-31)
2024-05-15T21:34:59.477+09:00  INFO 25012 --- [mallapi] [    Test worker] com.test.mallapi.TodoRepositoryTests     : Todo(tno=97, title=title...97, writer=user00, complete=false, dueDate=2023-12-31)
2024-05-15T21:34:59.478+09:00  INFO 25012 --- [mallapi] [    Test worker] com.test.mallapi.TodoRepositoryTests     : Todo(tno=96, title=title...96, writer=user00, complete=false, dueDate=2023-12-31)

쿼리에 limit가 걸려 있다. tno의 역순으로 정렬된 것을 확인할 수 있다.

 

 

 

 

아래는 전문

package com.test.mallapi;

import com.test.mallapi.domain.Todo;
import com.test.mallapi.repository.TodoRepository;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.time.LocalDate;
import java.util.Optional;

@SpringBootTest
@Log4j2
public class TodoRepositoryTests {
    @Autowired
    private TodoRepository todoRepository;

    @Test
    public void testInsert(){
        for (int i =1; i<=100; i++) {
            Todo todo = Todo.builder()
                    .title("title..." + i)
                    .dueDate(LocalDate.of(2023,12,31))
                    .writer("user00")
                    .build();

            todoRepository.save(todo);
        }
    }
    
    @Test
    public void testRead() {
        // 존재하는 번호로 확인
        Long tno = 33L;
        java.util.Optional<Todo> result = todoRepository.findById(tno);
        Todo todo = result.orElseThrow();
        log.info(todo);
    }

    @Test
    public void testModify() {
        Long tno = 33L;

        // java.util.Optional
        Optional<Todo> result = todoRepository.findById(tno);
        Todo todo = result.orElseThrow();
        todo.changeTitle("Modified 33...");
        todo.changeComplete(true);
        todo.changeDueDate(LocalDate.of(2023,10,10));

        todoRepository.save(todo);

    }

    @Test
    public void testDelete() {
        Long tno = 33L;
        todoRepository.deleteById(tno);
    }

    @Test
    public void testPaging() {
        //  import org.springframework.data.domain.Pageable;
        Pageable pageable =
                PageRequest.of(0, 10, Sort.by("tno").descending());

        Page<Todo> result = todoRepository.findAll(pageable);

        log.info(result.getTotalElements());
        result.getContent().stream().forEach(todo -> log.info(todo));
    }


}

'Projects > react-spring' 카테고리의 다른 글

백엔드 생성 및 초기 테스트  (0) 2024.05.15
Comments