회원 리포지토리 클래스가 원하는 대로 동작하는가를 검증하는 방법
개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다.
이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고, 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다.
자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.
테스트 케이스를 작성해보기 위해서 test - java - hello - hellospring 밑에 repository라는 패키지를 생성하였다.
package hello.hellospring.repository;
import org.junit.jupiter.api.Test;
public class MemoryMemberRespositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
}
save기능이 잘 동작하는지 실행시켜보기 위해 만든 테스트 코드이다.
@Test를 입력하고, import org.junit.jupiter.api.Test;와 같이 Test를 import 해주었다.
그 후 run save()해보았더니 위 사진과 같이 메소드가 실행되었다는 녹색 표시가 뜬다.
다음은 저장이 정말 제대로 잘 되는지 테스트 코드를 작성해서 돌려보았다.
@Test
public void save(){
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
System.out.println("result = " + (result == member));
}
-> 우선 Member 객체 member을 생성하였다.
member의 이름을 spring이라고 설정하였다.
*Tip) command + shift + enter를 누르면 spring 입력 후 바로 다음 줄로 엔터가 가능하다.
그 후 repository에 member를 save 하였다.
그리고 repository.findById(member.getId())으로 검증을 하는데,
타입이 Optional 이었다.
-> Optional에서 값을 꺼낼 때는
repository.findById(member.getId()).get();를 사용한다.
**get()으로 값을 바로 꺼내는 것이 좋은 방법은 아니지만, 우선 테스트 코드이므로 감안하자.
값을 바로 꺼냈으므르 코드를
Member result = repository.findById(member.getId()).get();
로 수정하였다.
새로 저장한 member name과 DB의 name이 똑같으면 참인 것이므로
일단 출력문으로 작성을 해보았다.
System.out.println("result = " + (result == member));
실행시켜보면 result = true 라는 글자가 뜬다.
위처럼 출력해서 확인해보는 방법도 있으나, 계속 글자로 출력시켜볼 수는 없으니
'assert' 라는 기능을 쓴다.
Assertions.assertEquals(member, result);
member와 result가 같은지 판별해주는 기능을 가진다.
코드를 실행해보면
System.out.println 문으로 작성한 것처럼 result = true라는 글자가 뜨진 않지만,
왼쪽 아래 실행 결과 창에 Test Results가 초록색 체크표시가 되어 있는 것을 볼 수 있다.
코드가 옳다는 뜻이다.
그렇다면 반대로 옳지 않은 결과를 내게 할 수 있도록 result를 적은 자리에 null을 넣어보자.Expected :hello.hellospring.domain.Member@38089a5a
Actual :null
과 같은 경고가 뜬다.
기대했던 것과 다르게 실제 값으로 null이 들어왔다는 뜻의 경고문이다.
Assertions.assertThat(member).isEqualTo(result);
또 하나의 방법은 위 코드를 쓰는 방법인데,
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
를 해주면 다음부터 Assertions를 쓰지 않아도 assertThat를 사용 가능하다.
**강의에서는 아래 코드를 쓰라고 나와 있었지만, 왜인지 모르게 나는 위 코드를 써야만 제대로 동작했다.
(import static org.junit.jupiter.api.Assertions.*;)
다음 테스트해 볼 것은 findByName이었다.
@Test
public void findByName(){
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member result = repository.findByName("spring1").get();
assertThat(result).isEqualTo(member1);
}
save를 테스트 해 봤던 것과 동일하게
Member 객체를 생성하고, member1의 이름을 spring1이라고 지정한 후,
저장소에 저장하였다.
그 후 result 객체에 spring1을 get()을 써서 꺼내주어 저장하고,
assertThat으로 동일한지 비교를 하였다.
*원래는 Optional result = repository.findByName("spring1"); 이지만,
get()을 쓰면 Optional에서 꺼내서 result에 저장이 가능하다.
"spring1"을 "spring2"로 바꾸면 assertThat(result).isEqualTo(member1)을 그대로 놔뒀을 때, 다른 객체라는 경고가 뜰 것이다.
따라서 member1도 함께 member2라고 바꿔주어야 한다.
초록창을 보니 마음이 아주 편안~하다.
TestCase의 장점은, class레벨에서 run을 하면 모든 Test가 돌아간다는 것이다.
위 사진처럼 class MemoryMemberRepositoryTest에서 run을 실행하니 save와 findByName의 결과가 함께 뜨는 것을 확인할 수 있다.
이번엔 findAll Testcase 코드를 작성하였다.
@Test
public void findAll() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2);
}
assertThat(result.size()).isEqualTo(2);
위 코드는 isEqualTo 결과의 기댓값이 2개라는 뜻이다.
실제 결과도 2개가 맞으므로 제대로 실행되는 걸 확인해 볼 수 있다.
isEqualTo(3)로 바꾸면
사용자가 기대한 값은 3이었지만 실제론 2개라는 경고가 뜬다.
이제 모든 testcase를 작성했으니 전체 run을 실행해보자.
어라? 그런데 갑자기 실행창에 경고가 떴다.
이유가 무엇일까?
"test들은 서로 순서에 관한 의존관계 없이 설계 되어야 한다"
그러나 나의 코드를 살펴보면,
findAll에서 setName해주었던 것과 findByName에서 setName해주었던 것이 겹친다.
그래서 먼저 돌아간 findAll만 실행이 정상적으로 되고, findByname에서 오류가 떴던 것이다.
그렇다면 이 오류를 어떻게 수정할까?
test가 하나 끝나고 나면, data를 깔끔하게 clear 해주어야 한다.
관련 코드는 아래 코드이다.
@AfterEach
public void afterEach(){
}
@AfterEach는, 메소드가 끝날때마다 callback을 해주는 메소드이다.
save가 끝나고 호출되고, findByname이 끝나고 호출되고, findAll이 끝나고 호출된다.
위 코드를 test - java - hellospring - repository 아래의 MemoryMemberRepositoryTest에 추가하고 나서,
main - java - hellospring - repository 아래의 MemoryMemberReposity에
public void clearStore(){
store.clear();
}
-> store을 clear해주는 위 코드를 작성한다.
그리고 다시 MemoryMemberRepositoryTest에 돌아가서,
@AfterEach
public void afterEach(){
repository.clearStore();
}
AfterEach 내부 코드를 채워넣는다.
.
.
.
.
.
여태까지의 이 과정(작품을 만들고, 틀을 짜 보는 것)과 반대로,
별 모양 작품을 만들고 싶은데, 미리 검증 하기 위한 별모양 틀을 만들어 놓고 작품이 완성되면 그 틀에 맞는지 맞춰보는 것.
이것을 test 주도 개발, TTD라고 한다.