지난 시간에는 Repository 테스트 코드를 하나만 작성해보고 말았었는데, 오늘은 Repository 테스트 코드를 모두 작성하고, Service 테스트 코드도 작성해보겠습니다.
제가 작성하게되는 테스트 코드는 TDD를 연습하기 위해 메타코딩님의 Junit 강의를 참고하여 작성한 코드이며, 모범적인 테스트 코드가 아닐 수 있습니다.
혹시 오류나 어색한 부분이 있을 경우 말씀해주시면 감사하겠습니다.
@Test
public void findAllTest() {
//given
Book bookItem1 = Book.builder()
.title("Test Title1")
.content("Test Content1")
.author("Writer1")
.build();
Book bookItem2 = Book.builder()
.title("Test Title2")
.content("Test Content2")
.author("Writer2")
.build();
//when
bookRepository.save(bookItem1);
bookRepository.save(bookItem2);
List<Book> bookList = bookRepository.findAll();
//then
Assertions.assertThat(bookList.size()).isEqualTo(2);
}
다음처럼 Entity 2개를 생성하여 Repository에 저장해준 뒤, List의 크기와 비교하는 방식으로 테스트 코드를 작성하였습니다.
테스트를 통과하는 모습을 확인할 수 있었으며, 테스트가 맞게 동작하는지 확인하기 위하여, 주어지는 데이터를 변형해보겠습니다.
@Test
public void findAllTest() {
//given
Book bookItem1 = Book.builder()
.title("Test Title1")
.content("Test Content1")
.author("Writer1")
.build();
Book bookItem2 = Book.builder()
.title("Test Title2")
.content("Test Content2")
.author("Writer2")
.build();
Book bookItem3 = Book.builder()
.title("Test Title3")
.content("Test Content3")
.author("Writer3")
.build();
//when
bookRepository.save(bookItem1);
bookRepository.save(bookItem2);
bookRepository.save(bookItem3);
List<Book> bookList = bookRepository.findAll();
//then
Assertions.assertThat(bookList.size()).isEqualTo(2);
}
다음처럼 Entity를 1개 더 생성하여 저장하였습니다.
우리가 계획한대로, 테스트는 실패하였으며 실제 리스트의 크기는 3인데 2를 기대하고 있어서 테스트가 실패하였다는 로그를 확인할 수 있었습니다.
다음으로는 책 단일 조회 기능을 테스트 수행하였습니다.
@Test
public void findBookTest() {
//given
Long bookId = 1L;
Book book = Book.builder()
.title("Test title")
.content("Test content")
.author("Writer")
.build();
bookRepository.save(book);
Book cpyBook = Book.builder()
.id(book.getId())
.title(book.getTitle())
.content(book.getContent())
.author(book.getAuthor())
.build();
//when
Optional<Book> findItem = bookRepository.findById(bookId);
//then
Assertions.assertThat(findItem).isPresent();
Assertions.assertThat(findItem.get()).isEqualTo(cpyBook);
}
조회하고자하는 Entity의 Id값을 변수를 통하여 선언한 뒤, book Entity를 만들고, 비교하고자하는 Entity을 하나 더 만들어주었습니다.
마지막 검증 부분에서는 사용자가 입력한 Id에 해당하는 Entity가 존재하는지 확인하고, 존재할 경우에는 비교용으로 만들어놓은 Entity를 통하여 값을 비교하는 방식으로 테스트 코드를 작성하였습니다.
정상적으로 테스트를 통과하는 모습을 확인할 수 있었으며, 테스트 코드가 잘 작성되었는지 확인하기 위하여 Id값을 변경해보겠습니다.
@Test
public void findBookTest() {
//given
Long bookId = 2L;
Book book = Book.builder()
.title("Test title")
.content("Test content")
.author("Writer")
.build();
bookRepository.save(book);
Book cpyBook = Book.builder()
.id(book.getId())
.title(book.getTitle())
.content(book.getContent())
.author(book.getAuthor())
.build();
//when
Optional<Book> findItem = bookRepository.findById(bookId);
//then
Assertions.assertThat(findItem).isPresent();
Assertions.assertThat(findItem.get()).isEqualTo(cpyBook);
}
다음처럼 조회하고자하는 Id값을 2로 변경해주었습니다.
우리가 바라는대로 테스트는 실패하였으며, 로그를 확인해보면
Id를 통하여 조회한 Entity가 존재하지 않는 것 같습니다.
그럼 이번에는 Entity의 정보가 중간에 수정되었을 경우를 살펴보겠습니다.
@Test
public void findBookTest() {
//given
Long bookId = 1L;
Book book = Book.builder()
.title("Test title")
.content("Test content")
.author("Writer")
.build();
bookRepository.save(book);
Book cpyBook = Book.builder()
.id(book.getId())
.title(book.getTitle())
.content(book.getContent())
.author(book.getAuthor())
.build();
//when
Optional<Book> findItem = bookRepository.findById(bookId);
cpyBook.setTitle("Changed Title");
//then
Assertions.assertThat(findItem).isPresent();
Assertions.assertThat(findItem.get()).isEqualTo(cpyBook);
}
다음처럼 중간에 cpyBook의 제목을 변경하는 코드를 삽입하였으며,
다음처럼 테스트가 실패하는 것을 확인할 수 있었습니다.
다음 수행할 테스트는 책 삭제 테스트입니다.
@Test
public void deleteBookTest() {
//given
long bookId = 1L;
Book bookItem1 = Book.builder()
.title("Test Title1")
.content("Test Content1")
.author("Writer1")
.build();
Book bookItem2 = Book.builder()
.title("Test Title2")
.content("Test Content2")
.author("Writer2")
.build();
bookRepository.save(bookItem1);
bookRepository.save(bookItem2);
//when
bookRepository.deleteById(bookId);
//then
Optional<Book> findBook = bookRepository.findById(1L);
Assertions.assertThat(findBook).isNotPresent();
}
일단은 데이터를 채워넣기 위하여 given에서 book Entity를 2개 저장해주고, when에서 bookId를 통하여 삭제를 진행하였습니다.
마지막으로 then에서 Id를 통하여 Entity를 조회하여 Optional로 받게 되는데, null인 경우에는 Id에 해당하는 Entity가 없기 때문에 정상적으로 삭제가 진행되었음을 알 수 있지만, 만일 null이 아닌 경우에는 삭제가 정상적으로 이루어지지 않았음을 확인할 수 있을 것입니다.
일단 테스트는 정상적으로 수행됨을 확인하였으며, 이번에는 고의적으로 테스트가 실패하게끔 코드를 수정해보겠습니다.
@Test
public void deleteBookTest() {
//given
long bookId = 1L;
Book bookItem1 = Book.builder()
.title("Test Title1")
.content("Test Content1")
.author("Writer1")
.build();
Book bookItem2 = Book.builder()
.title("Test Title2")
.content("Test Content2")
.author("Writer2")
.build();
bookRepository.save(bookItem1);
bookRepository.save(bookItem2);
//when
bookRepository.deleteById(bookId);
//then
Optional<Book> findBook = bookRepository.findById(2L);
Assertions.assertThat(findBook).isNotPresent();
}
다음 코드는 마지막 증명 과정에서 사용하는 Id의 값을 1에서 2로 변경해주었습니다.
다음처럼 테스트는 실패하였으며, 로그를 통하여 어떠한 부분이 잘못되었는지 확인해보면,
다음처럼 Optional로 받은 Entity가 null이 아니기 때문에 해당 오류가 발생함을 확인할 수 있습니다.
그런데 여기서 한가지 문제점이 발생하였습니다.
Repository 전체 테스트를 수행한 결과, 다음처럼 오류가 발생하였습니다.
로그를 통하여 확인해보니, 우리가 책 삭제 테스트 코드에서 사용하였던 Id 값에 해당하는 Entity가 존재하지 않아 오류가 발생한다고합니다.
아니 분명 삭제 테스트에서 Repository에 Entity를 저장헤주었는데 왜 1에 해당하는 Entity가 없다는 오류가 뜨는거냐고 생각하실 수 있습니다.
그런데, 이 문제는 사실 데이터베이스와 관련된 문제입니다.
지금까지 작성하였던 4개의 단위 테스트 코드를 다음처럼 수정해보겠습니다.
@Test
public void saveTest() {
String title = "테스트 도서 제목";
String content = "테스트 도서 내용";
String author = "테스트 도서 저자";
Book book = Book.builder()
.title(title)
.content(content)
.author(author)
.build();
Book savedBook = bookRepository.save(book);
System.out.println("saveTest! " + book.getId());
Book newBook = Book.builder()
.id(savedBook.getId())
.title(savedBook.getTitle())
.content(savedBook.getContent())
.author(savedBook.getAuthor())
.build();
Assertions.assertThat(newBook).isEqualTo(book);
}
/*
* 책 모두 보기 테스트
* */
@Test
public void findBooksTest() {
Book bookItem1 = Book.builder()
.title("Test Title1")
.content("Test Content1")
.author("Writer1")
.build();
Book bookItem2 = Book.builder()
.title("Test Title2")
.content("Test Content2")
.author("Writer2")
.build();
bookRepository.save(bookItem1);
bookRepository.save(bookItem2);
System.out.println("findBoosTest : " + bookItem1.getId() + " " + bookItem2.getId());
List<Book> bookList = bookRepository.findAll();
Assertions.assertThat(bookList.size()).isEqualTo(2);
}
/*
* 책 단일 조회 테스트
* */
@Test
public void findBookTest() {
//given
Long bookId = 1L;
Book book = Book.builder()
.title("Test title")
.content("Test content")
.author("Writer")
.build();
bookRepository.save(book);
System.out.println("findBookTest : " + book.getId());
Book cpyBook = Book.builder()
.id(book.getId())
.title(book.getTitle())
.content(book.getContent())
.author(book.getAuthor())
.build();
//when
Optional<Book> findItem = bookRepository.findById(bookId);
//then
Assertions.assertThat(findItem).isPresent();
Assertions.assertThat(findItem.get()).isEqualTo(cpyBook);
}
@Test
public void deleteBookTest() {
//given
long bookId = 1L;
Book bookItem1 = Book.builder()
.title("Test Title1")
.content("Test Content1")
.author("Writer1")
.build();
Book bookItem2 = Book.builder()
.title("Test Title2")
.content("Test Content2")
.author("Writer2")
.build();
bookRepository.save(bookItem1);
bookRepository.save(bookItem2);
System.out.println("deleteBookTest : " + bookItem1.getId() + " " + bookItem2.getId());
//when
bookRepository.deleteById(bookId);
//then
Optional<Book> findBook = bookRepository.findById(1L);
Assertions.assertThat(findBook).isNotPresent();
}
다음처럼 각 테스트 코드 내부 Entity의 Id를 출력하는 구문을 추가해주었습니다.
그리고 Repository 테스트를 다시 돌려보면,
아까처럼 테스트는 실패하였으나, 우리가 관찰해야할 것은 바로 로그입니다.
다음처럼 Id의 값이 모두 다른 것을 확인할 수 있습니다.
왜 그럴까요?
답은 바로 데이터베이스의 PK값 증가 방식에 있습니다.
우리가 save를 통하여 Entity를 저장하게 될 경우, 데이터 베이스에 의하여 PK를 부여받는데, 이 카운트는 초기화되지않고 있기 때문에 Entity가 들어올때마다 증가되는 값을 부여받는 것입니다.
따라서, 우리는 일부 테스트가 수행되기 전에 이 증가된 PK값을 초기화시킬 필요가 있습니다.
그럼 우선은 테스트가 수행되기 전 실행된 SQL 쿼리문부터 만들어줍니다.
drop table if exists Book;
create table Book (
id bigint not null auto_increment,
author varchar(10) not null,
content longtext not null,
title varchar(50) not null,
primary key (id)
);
다음처럼 Book이라는 테이블이 존재할경우, 다시 테이블을 생성하는 SQL문을 만들어줍니다.
이를 resources에 저장한 뒤, 다음처럼 테스트 실행 전에 초기화가 필요한 테스트에 @Sql 어노테이션을 달아줍니다.
@Test
@Sql("classpath:templates/table.sql")
public void deleteBookTest() {
//given
long bookId = 1L;
Book bookItem1 = Book.builder()
.title("Test Title1")
.content("Test Content1")
.author("Writer1")
.build();
Book bookItem2 = Book.builder()
.title("Test Title2")
.content("Test Content2")
.author("Writer2")
.build();
bookRepository.save(bookItem1);
bookRepository.save(bookItem2);
//when
bookRepository.deleteById(bookId);
//then
Optional<Book> findBook = bookRepository.findById(1L);
Assertions.assertThat(findBook).isNotPresent();
}
다음처럼 @Sql 어노테이션이 지정된 테스트의 경우, 테스트가 시행되기 전에 해당 SQL 쿼리문이 먼저 수행된 뒤, 테스트가 수행된다고합니다.
그 결과, 다음처럼 테스트가 모두 통과하는 것을 확인할 수 있었습니다.
그럼 마지막으로 책 수정 기능을 테스트해보겠습니다.
@Test
@Sql("classpath:templates/table.sql")
public void bookEditTest() {
//given
long bookId = 1L;
Book bookItem = Book.builder()
.title("수정 전 제목")
.content("수정 전 내용")
.author("Writer")
.build();
bookRepository.save(bookItem);
//when
Book book = bookRepository.findById(bookId).get();
book.setTitle("수정 후 제목");
book.setContent("수정 후 내용");
Book cpyBook = Book.builder()
.id(book.getId())
.title(book.getTitle())
.content(book.getContent())
.author(book.getAuthor())
.build();
Book changedBook = bookRepository.findById(bookId).get();
//then
Assertions.assertThat(changedBook).isEqualTo(cpyBook);
}
bookId를 통하여 Book Entity를 조회하여 수정한 뒤, 다시 한번 조회하여 이를 확인하는 방식으로 테스트 코드를 작성하였습니다.
테스트 코드는 정상적으로 동작함을 확인하였으며,
@Test
@Sql("classpath:templates/table.sql")
public void bookEditTest() {
//given
long bookId = 1L;
Book bookItem = Book.builder()
.title("수정 전 제목")
.content("수정 전 내용")
.author("Writer")
.build();
bookRepository.save(bookItem);
//when
Book book = bookRepository.findById(bookId).get();
book.setTitle("수정 후 제목");
book.setContent("수정 후 내용");
Book cpyBook = Book.builder()
.id(book.getId())
.title(book.getTitle())
.content(book.getContent())
.author(book.getAuthor())
.build();
Book changedBook = bookRepository.findById(bookId).get();
cpyBook.setTitle("수정이 진행된 제목");
//then
Assertions.assertThat(changedBook).isEqualTo(cpyBook);
}
다음처럼 테스트의 유효성을 확인하기 위하여 수정한 코드로 테스트를 실행할 경우,
다음처럼 테스트가 실패하는 것을 확인할 수 있습니다.
https://www.youtube.com/watch?v=kYqGAM2culU&list=PL93mKxaRDidEZfpXoyWZ-2ZLsYrQByDMP