스프링 공부/인프런 김영한 스프링 입문 노트정리

3-3. 회원 리포지토리 테스트 케이스 작성

모항 2022. 7. 27. 21:05

저번 시간에 만든 리포지토리가 잘 돌아가나 시험해보기 위해 테스트 케이스를 작성하고 테스트를 해보는 회차이다.

 

 

 

준비

test > java > hello.hellospring 패키지 안에 repository라는 패키지를 새로 만들고,

 

그 안에 클래스를 하나 만든다.

MemoryMemberRepository의 테스트케이스이므로,

관례에 따라 MemoryMemberRepository 뒤에 Test만 붙인 것을 클래스의 이름으로 사용한다.

 

만들어준 다음에 패키지 구성을 살펴보면

main에서 테스트의 대상이 될 MemoryMemberRepository의 위치와

우리가 방금 test 하위에 만든 MemoryMemberRepositoryTest의 위치가 쌍둥이처럼 비슷한 것을 확인할 수 있다.

 

테스트용 클래스는 다른 데에서 가져다 쓸 일이 없으므로 public을 붙이지 않아도 된다.

 

이 클래스 안에, @Test 어노테이션이 붙은 테스트용 메소드 세 개를 만들고

@AfterEach가 붙은 후처리용 메소드를 만들어줄 것이다.

 

일단 우리가 테스트를 할 클래스의 객체부터 하나 만들어준다.

얘가 테스트를 받을 환자다.

이 객체놈의 안에 들어있는 요소들이 잘 작동하는지를 이제부터 보는 것이다.

 

이제 테스트용 메소드들을 만든다.

 

 

 

 

save()와 findById() 테스트하기

첫 번째는 회원 정보 저장이 잘 되는지를 확인하는 메소드다.

회원 하나를 만들고, 그 이름을 정해주고, repo에 넣는다.

 

이것으로는 눈에 보이는 결과가 나오지 않으니 내친김에 findById도 한꺼번에 시험해보기로 한다.

이렇게 result를 꺼내주고 member1과 result의 내용물이 일치하는지 확인해보면 될 것 같은데, 빨간 줄이 뜬다.

이것은 우리가 만들어놓은 findById의 리턴값이 Optional이기 때문이다.

Optional로 감싸인 결과값을 꺼내는 방법에는 여러 가지가 있는데 강사님께서는 일단 테스트 코드니 간단하게 .get()으로 꺼내오자고 하셨다. 실무에서는 다른 방법으로 꺼내와야 한다고 한다.

 

이제 오류가 사라졌다.

 

이렇게 해주면 member1과 result의 내용물이 일치하는지 육안으로 확인할 수 있을 것이다.

 

save()의 왼쪽에 있는 버튼으로 이 테스트를 실행시켜보자.

결과가 나왔다. true가 출력된 것을 보니 문제 없어 보인다.

 

그런데 이런 식으로 테스트를 진행하면 그때그때 상황에 맞는 검사 방법을 내가 생각해내서 만들어야 한다.

번거로울 뿐만 아니라 너무 허술하고 일차원적이다.

 

테스트 시에 주로 사용하는 도구는 따로 있는데, 바로 Assertions다.

junit에서 제공하는 Assrtions부터 살펴보자.

용도에 따라 다양한 확인용 메소드가 준비되어있다.

Equals도 있고, NotEquals도 있고, Throws도 있고, DoesNotThrow도 있고... 엄청 많다.

우리는 두 객체의 내용물이 동일한지 여부를 판단할 것이므로 assertEquals를 사용하면 된다. 

 

expected(기대값)과 actual(실제 값)을 채워넣어주고 다시 테스트를 실행해본다.

 

콘솔에는 아무것도 출력되지 않았다.

하지만 우리는 콘솔 왼편의 목록에 오류 아이콘 없이 초록색 체크들만 떴다는 것, 그리고 콘솔에 메시지가 뜨지 않았다는 것을 보고 테스트가 잘 완료되었음을 알 수 있다.

 

의도적으로 다른 값을 넣은 모습
의도적으로 null을 넣은 모습

테스트가 성공적으로 끝나지 않았을 경우에는 콘솔 좌측의 리스트에 X 아이콘이 표시되고, 콘솔에는 상황과 관련된 내용이 출력된다.

 

 

 

다음으로는 assertj에서 제공하는 Assertions를 사용해보자.

 

얘는 일단 assertThat(대상)까지 쓴 다음에,

그 뒤에서 다양한 선택지 중 하나를 고르는 식으로 사용한다.

우리는 두 객체의 내용물이 동일한지 보아야 하므로 isEqualTo()를 고른다.

여기서 팁: 만약 내용물(값)을 비교하는 것이 아니라 두 대상의 주소값 자체, 즉 완전히 동일한지를 비교해야 한다면 isSameTo()를 쓰면 된다.

 

result(결과값)이 member1(기대값)과 같다는 것을 확인해라

이렇게 적어주고 돌려보면 잘 돌아간다.

일부러 다른 걸 넣어보면 이렇게 뜬다.

 

 

 

그리고 Assertions를 더 편리하게 쓰는 팁을 주셨다.

junit
assertj

이렇게 Assertions 이하의 모든 내용물을 static하게 import시켜놓으면

본 코드 내에서는 Assertions.를 쓸 필요 없이 바로 그 뒤부터 적으면 된다.

얘네한테만 통하는 게 아니라 자바에서 폭 넓게 통하는 것 같다.

 

 

 

 

findByName() 테스트하기

그 다음으로는 findByName()이 잘 동작하는지 테스트한다.

두 개의 Member 객체를 repo에 save한 다음 특정 객체를 findByName으로 찾아온다. 찾아온 놈과 의도한 놈의 내용물이 동일한지 assert한다.

돌려보니 문제 없이 테스트가 성공했다.

 

여기서 테스트코드의 좋은 점을 하나 알려주셨다.

지금처럼 하나 이상의 테스트 메소드가 같은 클래스에 들어있을 때,

이런 식으로 모든 테스트를 한 번에 실행할 수 있다.

 

그럼 콘솔 좌측의 리스트에는 여러 테스트들의 결과가 함께 표시된다.

 

 

 

 

 

findAll() 테스트하기

다음으로는 findAll()이 잘 작동하는지 테스트한다.

두 개의 객체를 repo에 넣고, findAll()로 모든 객체의 리스트를 가져온 다음에, 그 리스트의 크기가 2가 맞는지 확인하는 코드이다.

실행해보니 잘 된다.

 

 

 

@AfterEach 만들기

 

그런데!

전체 클래스를 한 번에 실행해보면?

 

오류가 뜬다!

 

각자 실행했을 때는 괜찮았는데 지금 갑자기 오류가 뜨는 이유는, 테스트 사이에 충돌하는 객체가 있기 때문이다.

 

보면 findAll()과 findByName() 사이에 이름이 똑같은 Member 객체들이 존재한다. 그래서 문제가 발생했다.

 

그리고 또 하나 기억할 것이 있다.

테스트 여러 개를 한 번에 실행할 때, 그 순서는 보장되지 않는다.

이번에는 findAll() - findByName() - save() 의 순서대로 실행됐다. 우리는 이런 순서를 정한 적이 없다! 제멋대로 정해진 것이다.

그래서 우리는 어떤 순서로 테스트들이 실행되더라도 오류가 발생하지 않도록, 즉 테스트들이 실행 순서에 의존적이지 않도록 설계해야 한다.

 

 

 

이를 위한 기능이 바로 @AfterEach이다.

우리는 @AtferEach를 사용해서, 테스트 하나가 끝날 때마다 repo에 들어있는 데이터를 clear해줄 것이다.

 

일단 우리가 테스트를 하는 대상인 본체, 즉 MemoryMemberRepository.java로 가서

위와 같이 clearStore() 메소드를 만들어준다. store의 내용물을 비워주는 역할이다.

 

그 다음 다시 테스트 코드로 돌아와서

위와 같이 afterEach 메소드를 만든다.

 

이제 전체 테스트를 돌려보면 문제 없이 모두 pass된다.

 

 

 

 

TDD(Test-driven Development, 테스트 주도 개발)

우리는 테스트할 대상을 먼저 개발한 다음, 그걸 검증하는 데 알맞은 테스트를 나중에 짰다.

 

이와 반대로, 테스트부터 먼저 만드는 개발 방식도 있다.

이를 테스트 주도 개발이라고 한다.

 

테스트 주도 개발에서는

완성될 프로그램이 충족하기를 바라는 내용이 들어간 테스트를 먼저 만들어둔다.

그리고 그 테스트를 기반으로, 테스트를 문제 없이 pass하는 프로그램을 만든다.