1. 왜 테스트케이스를 써야 하는지?
쉽게 말하면, 내가 작성한 개발코드가 정상적으로 동작하는지 검증할 수 있기 때문입니다.
Q. 테스트케이스의 정의
개발한 기능을 테스트 하려고 할 때,
방법1(=테스트케이스 사용X)
- 자바의 main 메서드를 통해서 실행한다.
- (또는) 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다.
방법1의 단점:
- 이러한 방법은 준비하고 실행하는데 오래 걸린다.
- 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다.
그래서 테스트케이스는 뭐예요?
자바의 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결합니다.
2. 코드!
>테스트케이스를 만들어보자!
1편에서 만든 'MemoryMemberRepository'(=회원리포지토리)를 코드로 검증할 수 있는 방법을 배울거예요.
1. test>repository(패키지)>MemoryMemberRepositoryTest(클래스)를 생성합니다.

package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import hello.hellospring.repositoty.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach() {
repository.clearStore();
}
@Test
public void save() {
//given
Member member = new Member();
member.setName("spring"); //when
repository.save(member);
//then
Member result = repository.findById(member.getId()).get();
assertThat(member).isEqualTo(result);
}
@Test
public void findByName() {
//given
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
//when
Member result = repository.findByName("spring1").get();
//then
assertThat(result).isEqualTo(member1);
}
@Test
public void findAll() {
//given
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
//when
List<Member> result = repository.findAll();
//then
assertThat(result.size()).isEqualTo(2);
}
}
@Test (어노테이션)은 뭔가요?
이름을 적어듯이, 코드 앞에 '@Test'를 적어서 테스트코드라고 명시하는 겁니다.
그리고 @Test는 import org.junit.jupiter.api.Test 라이브러리입니다.
3. 전체코드 실행
(※전체코드의 afterEach() 함수가 없다는 가정하에)
Q. 2.번 전체코드를 그냥 실행(run)하면 에러가 뜨고, 원하는 대로 동작이 안됩니다.

※에러가 발생한 이유
- findAll()에서 spring1, spring2이 저장됩니다.
- findByName()할 때, findAll()에 저장된 객체들이 나와서 에러발생
▶findAll()에 저장된 Member객체들이 동작이 끝나고, 다음 테스트코드인 findByName()로 넘어가 저장됩니다.
그래서 findByName()의 검증단계에서 이전에 저장된 member객체와 꼬이면서 에러가 발생합니다.
Q. 그래서 코드를 어떻게 짜야 하나요?
모든 테스트코드는 따로 동작되도록 설계해야 합니다.
그래서 메소드 종료시에 저장된 객체를 지워주는 코드를 넣어줘야 합니다.
Q. MemoryMemberRepository에 코드추가
public void clearStore() {
store.clear();
}
메소드 종료시에 공용데이터를 지워주게 합니다.
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach() {
repository.clearStore();
}
Q. AfterEach
'메소드가 끝날 때마다, 어떤 동작을 한다'라는 롤백이라고 합니다.
테스트코드 작성할 때 넣으면 각각의 테스트가 끝날 때마다 AfterEach로 돌아와 (객체를 저장한) 저장소를 초기화합니다.
MemoryMemberRepository에서 @AfterEace를 추가해서 'findAll' 'findByName' 'save'등 끝날 때 데이터를 초기화하여
각각의 테스트코드가 따로 동작할 수 있게 해줍니다.
4. 전체코드 해석
Q1. 테스트코드_save()의 경우
@Test
public void save() {
//given
Member member = new Member();
member.setName("spring"); //when
repository.save(member);
//then
Member result = repository.findById(member.getId()).get();
assertThat(member).isEqualTo(result);
}
- given > "spring"이라는 이름의 member객체가 있고,
- when > 그 member객체를 repository에 저장할 때,
- then > 저장이 잘 되었는지 검증한다.
Q2. 테스트코드_findByName()의 경우
@Test
public void findByName() {
//given
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
//when
Member result = repository.findByName("spring1").get();
//then
assertThat(result).isEqualTo(member1);
}
- given > "spring1", "spring2"이라는 이름의 member객체를 repository에 저장하고,
- when > 그 "spring1"이름을 조회할 때,
- then > "spring1"이름이 member1객체와 같은 사람인지 검증한다.
Q3. 테스트코드_findAll()의 경우
@Test
public void findAll() {
//given
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
//when
List<Member> result = repository.findAll();
//then
assertThat(result.size()).isEqualTo(2);
}
- given > "spring1", "spring2"이라는 이름의 member객체를 repository에 저장하고,
- when > 모든 member를 list로 조회할 때,
- then > member가 모두 2명인지 검증한다.
5. then단계_findById(조회)
then단계_findById(조회)
1. ropository.findById()로 제대로 저장이 되었는지 조회해봅니다.
>repository.findById(member.getId()).get(); 로 조회하는 이유?
member객체를 저장하면, Id가 세팅이 되니까 getId().get()으로 조회합니다.
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
(이전 1편에서 만든 MemoryMemberRepository 코드의 save()코드 - setId()를 기억해주세요)
2. findById()의 반환타입이 Optional<>인데 꺼낼 때 get()으로 바로 꺼낼 수 있습니다.
Optional<Member> result = repository.findById(member.getId());
>Optional<Member>를 바로 get()으로 꺼내도 괜찮은지?
get()으로 바로 꺼내는 게 좋지 않지만 테스트코드에서는 사용해도 괜찮습니다.
6. then단계_assertThat(검증하기)
then단계_assertThat(검증하기)
>방법0 "member객체 == getId()" 비교로 검증한다.
Member result = repository.findById(member.getId()).get();
System.out.println("result= " +(result == member));

'==' 비교를 통해 조회한 Id와 실제 member객체를 비교하여, 같으면 'true'를 출력시켜 확인합니다.
>방법1 Assertions(jupiter버전)를 사용한다.
Q. Assertions를 사용하면 방법0처럼 출력확인 없이 검증할 수 있습니다.

import org.junit.jupiter.api.Assertions;
Member result = repository.findById(member.getId()).get();
Assertions.assertEquals(result, member);
jupiter라이브러리의 Assertions를 사용해서 방법1처럼 비교하여 검증합니다.
Q. 만약에 '불일치' 검증되어 실패한다면?
@Test
public void save() {
//사람1
Member member = new Member();
member.setName("spring");
repository.save(member);
//사람2
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
//검증
Member result = repository.findById(member.getId()).get();
assertThat(member2).isEqualTo(result);

(예시 코드에 대한 에러 메시지)
만약에 "불일치"검증결과가 나온다면 위의 메시지처럼 "expected(기대대상), Actual(실제값)"가 다르다고 알려줍니다.
방법2> Assertions(assertj버전)를 사용한다.
전체코드의 방법으로 assertj버전의 Assertions를 사용하는 방법입니다.
Member result = repository.findById(member.getId()).get();
assertThat(member).isEqualTo(result);
Q.실무에서는 이를 활용하여, 빌드튤과 엮어서 오류테스트케이스를 통과 못하면 그 다음 단계로 못가게 막습니다.
Q. 위의 코드가 assertThat으로 시작한 이유는?
import static org.assertj.core.api.Assertions.assertThat;
Assertions.assertThat(member).isEqualTo(result);
Assertions에서 옵션으로 'Add on demand static import옵션'을 사용하여
import static으로 만들어, 다음 작성할 때에 assert으로 시작하게 만들 수 있습니다.
6. Assertion
Assertion는 테스트에 넣을 수 있는 정적 메서드 호출입니다.
Q. Assertion은 무엇인가요?
어떤 조건이 참인지 검증하는 방법으로,
단언한 조건이 참이 아니면 테스트는 그 자리에서 멈추고 실패합니다.
Q. assertThat조건은 뭔가요?
목적은 "명확한 값을 비교"으로 기대하는 값과 반환된 실제 값을 비교합니다.
Assertions.assertEquals(expected=예상값, actual=실제값);
assertThat(actual=실제값).isEqualTo(expected=기대대상);
Q. 어디에 활용될까요?
- 객체 타입을 검사
- 두 객체의 참조가 같은 인스턴스인지 검사
- 다수의 매처를 결합하여 둘 다 혹은 둘 중에 어떤 것이든 성공하는지 검사
- 어떤 컬렉션이 요소를 포함하거나 조건에 부합하는지 검사
- 어떤 컬렉션이 아이템 몇 개를 모두 포함하는지 검사
- 어떤 컬렉션에 있는 모든 요소가 매처를 준수하는지 검사
마무리
사실 '테스트코드'는 굉장히 깊은 내용이며, 이 포스팅글은 맛보기 수준입니다.
오늘 배운 내용은 '개발:MemoryMemberRepository → 테스트코드:MemoryMemberRepositoryTest'인데
TDD라고 '테스트코드 → 개발코드'로 적는 테스트주도 방식이라는 것도 있습니다.
테스트코드를 사용하면 여러 명과 현업할 때, 테스트코드의 중요성은 커지므로 잘 이해하는게 중요합니다.
참고 자료(Assertion) - coding-start.tistory.com/259
참고 자료(강의)-www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard
'spring' 카테고리의 다른 글
| 5. DI_ 컴포넌트 스캔과 자동 의존관계 설정 (0) | 2021.03.27 |
|---|---|
| 4.회원 서비스 개발 (0) | 2021.03.26 |
| 3. 서비스 개발 (0) | 2021.03.26 |
| 1.회원관리예제_회원도메인, 리포지토리 만들기 (0) | 2021.03.24 |
| spring_카테고리 길잡이_소개 글 (0) | 2021.03.24 |
댓글