3.3.0 개발한 기능 test 하는 방법
1) main 메소드 실행 (ex. save가 정상 동작 하는지)
2) 웹 애플리케이션 컨트롤러 사용
위의 2가지 방법은 다음과 같은 단점이 있다.
1) 반복적으로 test 하기 어려움
2) 여러 test case를 한번에 실행하기 어려움
자바에서는 junit 프레임 워크로 test code를 만들고,
test code 를 실행하여 위의 문제를 해결한다!
3.3.1 테스트 케이스 작성하기
앞서 섹션 3.2 에서 작성한 repository 클래스가
실제로 동작하는지 검증하기 위한 테스트 케이스를 작성해보고자 한다.
test > java > hello.hellospring 에 repository 패키지 만든다. (대부분 같은 이름으로 만듦)
repository 패키지에 MemoryMemberRepositoryTest 클래스를 만든다.
다른 class 에서 사용하지 않아도 되므로,
public 으로 작성하지 않는다.
class MemoryMemberRepositoryTest
다음으로, 앞서 작성했던 기능의 테스트 코드를 하나씩 작성해보자.
1) save
@Test
public void save(){
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
Assertions.assertEquals(member, result);
}
1) @ Test 를 상단에 작성하여 실행할 수 있음.
2) Member 객체를 생성하고, name에 "spring" 을 저장
3) name 을 repository 에 save ( save 할 때, id 가 세팅됨)
4) get() 을 사용하여, optional 반환 타입에서 값을 꺼낼 수 있음
5) 생성한 객체 member 와 db 에서 꺼낸 result 값이 같으면 True
member 와 result 값이 같은지 비교할 때, 아래와 같은 코드로도 작성할 수 있다.
System.out.println("result = " + (result == member));
다음과 같이 작성하면, 아래의 화면처럼 " return = true " 값이 출력된다.
하지만, 많은 test case 의 결과 값을 하나씩 확인해 볼 수 없으므로, Assertion 기능을 주로 사용한다.
Assertions 을 사용하면 출력되는 값은 없지만,
테스트 성공시에 아래와 같은 녹색 체크 표시가 나타난다.
(실패할 경우, 빨간 표시가 나타난다)
Assertions.assertEquals(member, result);
다음의 코드로도 test 를 실행할 수 있다.
Assertion.assertThat(member).isEqualTo(result);
2) 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();
Assertions.assertEquals(member1, result);
}
- 정교한 test 를 위해 2개의 case 작성
- name 을 각각 "spring1" , "spring2" 로 지정
- get() 을 사용하여 return 값을 Optional 에서 꺼냄
테스트를 실행해보면, 아래와 같이 정상 작동하는 것을 볼 수 있다.
아래의 사진처럼 클래스 수준에서 test 하여,
여러 test를 동시에 확인할 수도 있다.
Member result = repository.findByName("spring2").get();
Assertions.assertEquals(member1, result);
코드를 변형시켜, test에 실패하는 경우를 확인해보자.
만약 위의 코드처럼,
spring2 와 member1(spring1) 이 같은지를 test 해보면,
test 에 fail 했다는 출력값을 볼 수 있다.
3) findAll
@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();
Assertions.assertEquals(result.size(), 2);
}
- 정교한 test 를 위해 2개의 case 작성
- name 을 각각 "spring1" , "spring2" 로 지정
- List에 저장된 객체의 개수와 실제 db 의 개수를 비교함
test 를 실행해보면, 아래와 같이 정상적으로 동작한다.
3.3.2 clear 함수 작성하기
testcase 의 장점은, 여러개의 test 메소드를 동시에 돌릴 수 있다는 것이다.
하지만, 위에서 작성한 3개의 test code를 동시에 돌려보면,
다음과 같이 test fail 메시지를 확인할 수 있다.
앞서, test case 를 개별적으로 돌렸을 때는 정상적으로 동작했지만,
클래스 레벨에서 test를 돌렸을 때, findByName 테스트에서 실패하는 이유는 무엇일까.
test 를 class 레벨에서 돌렸을 때, 임의의 순서로 메소드가 실행된다.
즉, 첫번째로 테스트가 돌아간 findAll() 메소드에서 spring1, spring2 값이 저장된다.
이후 findByName() 메소드의 test 가 실행될 때, 이미 저장되어 있던 spring1 값이 가져와지면서 test가 fail 된다.
따라서, test끼리 서로 의존 관계가 없도록, 공용 데이터를 모두 지워주어야 한다.
즉, test 가 하나 끝날 때마다, repository 를 clear 해주는 코드를 추가로 작성해야 한다!
clear 함수
먼저, memorymemberrepository.java에 clearstore 메소드를 작성한다.
public void clearStore(){
store.clear();
}
store.clear() 를 사용하여, store 값을 모두 삭제하도록 한다.
memorymemberrepositoryTest.java에도 코드를 추가한다.
다음과 같이 코드를 작성하면,
테스트가 진행되고 끝날 때마다 repository 가 지워지고 test의 순서는 무의미해진다.
@AfterEach
public void afterEach(){
repository.clearStore();
}
다시 클래스 레벨에서 test를 동시에 돌려보면,
이번에는 정상적으로 동작하는 것을 확인할 수 있다.
이러한 테스트 케이스 작성은 다수의 개발자가 함께 참여하는 프로젝트의 경우 상당히 중요해진다.
MemoryMemberRepository.java 전체 코드
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long,Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member){
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id){
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name){
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll(){
return new ArrayList<>(store.values());
}
public void clearStore(){
store.clear();
}
}
MemoryMemberRepositoryTest.java 전체 코드
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach(){
repository.clearStore();
}
@Test
public void save(){
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
Assertions.assertEquals(member, result);
}
@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();
Assertions.assertEquals(member1, result);
}
@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();
Assertions.assertEquals(result.size(), 2);
}
}
김영한 '스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술' 강의를 기반으로 작성하였습니다.
'SPRING 입문 [ 코드로 배우는 스프링 부트 ]' 카테고리의 다른 글
[스프링 입문] 섹션 3.5 회원 관리 예제 (회원 서비스 테스트) (1) | 2023.01.10 |
---|---|
[스프링 입문] 섹션 3.4 회원 관리 예제 (회원 서비스 개발) (0) | 2023.01.10 |
[스프링 입문] 섹션 3.2 회원 관리 예제 (회원 도메인과 리포지토리 만들기) (0) | 2023.01.09 |
[스프링 입문] 섹션 3.1 회원 관리 예제 (비즈니스 요구사항 정리) (0) | 2023.01.09 |
[스프링 입문] 섹션 2.3 스프링 웹 개발 기초 (API) (0) | 2023.01.09 |