스프링 테스트 하기
개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 서버를 직접 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행해서 테스트 할 수 있습니다.
이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있습니다다.
자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결합니다.
대규모 협업 프로젝트 할땐 테스트 케이스 없이 프로젝트 진행이 아예 불가하고, 로버트 마틴의 '클린 코트'책에 따르면 개발코드와 테스트 코드는 1:1 로 투자하는 게 맞을 정도로 엄청 중요하다고 했습니다.
JUnit 자바 단위테스트란?
Java에서 독립된 단위테스트 (Unit test)를 지원해주는 프레임워크입니다.
단위테스트
특정 소스코드의 모듈이 의도한 대로 작동하는지 함수 및 메소드에 대한 테스트를 하는 작업입니다.
Spring에서 단위테스트를 하는것은 스프링 커네이너에 올라와있는 Bean들을 테스트 하는 것입니다.
Junit을 사용하면 스프링 컨테이너를 띄워, 그 위에 올라가있는 Bean을 테스트 할 수 있습니다.
이번 실습에는 저번 포스트에서 작성한 레포지토리와 서비스 모듈을 테스트 해보겠습니다.
JUnit 핵심 기능
assertEquals(예상값, 실제값) : assert()는 junit의 대표적인 기능입니다. 이 메소드를 사용하여 테스트가 정상인지 아닌지 판별합니다.
@Test 어노테이션 메서드 호출 시 새로운 인스턴스를 생성하여 독립적인 테스트가 이루어지도록 함.
테스트할 코드 (저번 포스트의 프로젝트)
멤버 레포지토리 테스트 케이스
피검사 코드
MemoryMemberRepository.java
package example.boot.repository;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ArrayList;
import example.boot.domain.Member;
public class MemoryMemberRepository implements MemberRepository {
// 아직 데이터베이스를 연결 안했으므로 간이로 데이터베이스(store해시맵) 과 id를 일일히 할당해줍시다
// 데이터베이스가 자동으로 id를 할당하지만 아직 연결 안했으므로 id 1씩 증가하면서 하겠습니다.
private static Map<Long, Member> store = new HashMap<Long,Member>();
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) {
// Optional은 null 반환할 수도 있는데 그대로 반환 시킨다. -> 프론트에서 null 처리
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
// value들, 즉 멤버들을 stream으로 하나씩 확인하는데 filter 람다식으로 이름이 같은것만 고릅니다
// findAny로 일치하는 것을 반환합니다.
return store.values().stream().filter(m -> m.getName().equals(name)).findAny();
}
@Override
public List<Member> findAll() {
// 저장된 값들을 arrayList로 변환해서 반응
return new ArrayList<Member>(store.values());
}
public void clearStore(){
store.clear();
}
}
저번 포스트에서 회원 리포지토리와 서비스에 대해서 테스트 케이스를 src/test/java 하위 폴더에 작성할 것입니다.
repository 폴더 만들고 테스트 하려는 파일과 같은 이름에서 Test만 붙인 클래스를 생성하고 테스트 실행합니다.
JUnit의 Assertions 클래스를 이용해서 검증합니다.
@Test 어노테이션을 붙여주면 테이스 케이스를 실행 할수 있고, 각각 에디터에서 클릭해서 개별로 실행할 수 있습니다.
save() 테스트 케이스에서 save 함수가 제대로 수행 되는지 보고, 잘 들어갔는지 assertEqual로 확인하고, 같으면 테스트 케이스 성공합니다.
코드가 기므로 주석으로 설명을 적어놓겠습니다.
테스트 코드
MemoryMemberRepositoryTest.java
package example.boot.repository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import example.boot.domain.Member;
public class MemoryMemberRepositoryTest {
// MemoryMemberRepository 클래스를 테스트를 하는 것이므로 의존성 끌어옴
MemoryMemberRepository repository = new MemoryMemberRepository();
// 이 클래스의 여러 테스트 케이스는 순서가 랜덤이므로 잘못될수 있습니다.
// @AfterEach는 각 테스트케이스 이후에 실행할 내용을 정의할 수 있습니다.
// 각 테스트 케이스 이후에 respotory를 초기화 하는 것입니다.
@AfterEach
public void clear(){
repository.clearStore();
}
// 테스트 케이스마다 @Test 붙여주면 실행 가능
@Test
public void save() {
Member member = new Member();
member.setName("spring");
// save 함수 실행 해봄
repository.save(member);
// 다시 넣은 객체 꺼내봄
Member result = repository.findById(member.getId()).get();
//제대로 들어갔는지 원래 member랑 같은 객체인지 검사
Assertions.assertEquals(member, result);
}
@Test
public void findById() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
// 객체를 넣고 id로 찾은 후, 처음과 같은 객체를 찾으면 테스트 통과
Assertions.assertEquals(member1, repository.findById(member1.getId()).get());
Assertions.assertEquals(member2, repository.findById(member2.getId()).get());
}
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("spring3");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring4");
repository.save(member2);
// 객체를 넣고 name으로 찾은 후, 처음과 같은 객체를 찾으면 테스트 통과
Assertions.assertEquals(member1, repository.findByName(member1.getName()).get());
Assertions.assertEquals(member2, repository.findByName(member2.getName()).get());
}
@Test
public void findAll(){
int num = 10;
// 이름 다른 10개의 객체를 넣어봅니다.
for (int i = 0; i < num; i++) {
Member tempMember = new Member();
String name = "spring" + Integer.toString(i);
tempMember.setName(name);
repository.save(tempMember);
}
// findAll은 위에 코드를 보시면 알겠지만 List로 반환됩니다.
// List 객체의 size() 함수로 갯수를 알아볼수 있는데, 넣어준 갯수와 같은지 검증 합니다.
Assertions.assertEquals(repository.findAll().size(), num);
}
}
테스트 결과 - 성공! (VSCode in gitpod.io)
아래와 같이 줄마다 테스트 케이스 옆에 초록색 체크를 눌러서 개별적으로 테스트도 가능합니다.
멤버 서비스 테스트 케이스
피검사 코드
MemberService.java
package example.boot.service;
import java.util.List;
import java.util.Optional;
import example.boot.domain.Member;
import example.boot.repository.MemberRepository;
import example.boot.repository.MemoryMemberRepository;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// 회원 가입
public Long join(Member member){
// 같은 이름이 있는 중복 회원 X
validateOverapMember(member);
memberRepository.save(member);
return member.getId();
}
// 중복된 회원이 있는지 검증해주는 함수
public void validateOverapMember(Member member){
// Optional 객체 이므로 ifPresent 함수를 이용해서 null 조건문 없이 작성 할 수 있습니다
memberRepository.findByName(member.getName()).ifPresent(m -> {
throw new IllegalStateException("already Exists");
});
}
public List<Member> findAll() {
return memberRepository.findAll();
}
public Optional<Member> findOne (Long id){
return memberRepository.findById(id);
}
}
저번 포스트에서 회원 리포지토리와 서비스에 대해서 테스트 케이스를 src/test/java 하위 폴더에 작성할 것입니다.
service 폴더 만들고 테스트 하려는 파일과 같은 이름에서 Test만 붙인 클래스를 생성하고 테스트 실행합니다.
테스트 케이스 같은 경우는 원래 코드와 다르게 함수를 한글로 작성할 수 있습니다.
회원_중복_확인 테스트
assertEquals를 통해서 두 객체가 같은지 검사 했다면, assertThows 함수를 통해서 올바른 예외가 나오는지 검사 할 수 있습니다.
이름이 똑같은 두 멤버를 넣었으므로, 두번째 멤버를 넣을때 예외가 나와야지 정상입니다.
여기서 끝내도 되지만, 그 에러 메세지마저 피검사코드에서 넣어준 메세지와 같은지 검증하고 싶으면 assertEquals 함수를 사용해주면 됩니다.
코드가 기므로 주석으로 설명을 적어놓겠습니다.
MemberServiceTest.java
package example.boot.service;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import example.boot.domain.Member;
import example.boot.repository.MemoryMemberRepository;
public class MemberServiceTest {
MemberService service;
MemoryMemberRepository repository;
@BeforeEach
public void beforeEach(){
repository = new MemoryMemberRepository();
service = new MemberService(repository);
}
@AfterEach
public void afterEach(){
repository.clearStore();
}
// 테스트 케이스는
// Given when then 형식으로 작성
@Test
public void 회원가입(){
// Given
Member member = new Member();
member.setName("spring");
// When
service.join(member);
// Then
Assertions.assertEquals(member, service.findOne(member.getId()).get());
}
@Test
public void 중복_회원_예외(){
// Given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
// When
service.join(member1);
// Then
Exception e = Assertions.assertThrows(IllegalStateException.class, () -> service.join(member2));
Assertions.assertEquals(e.getMessage(), "already Exists");
}
}
테스트 결과 - 성공 (VSCode in gitpod.io)
테스트 케이스를 다 수행해준 결과 입니다.
'Back-End > 🌱 Spring Boot (java)' 카테고리의 다른 글
[Spring 입문] 스프링부트와 MySQL연결하기 - JDBC Template (AWS이용) (0) | 2023.01.21 |
---|---|
[Spring 입문] 스프링 빈(Bean)과 의존관계 설정(Dependency Injection) (0) | 2023.01.20 |
[Spring 입문] 계층 구조와 클래스 의존관계/주입 (Dependency Injection) 실습 (2) | 2023.01.15 |
[Spring Boot 입문] MVC패턴과 템플릿엔진의 동작과 구현 (1) | 2023.01.14 |
스프링부트 (Spring Boot) VSCode에서 프로젝트 시작하기 (feat. 웹IDE gitpod) (0) | 2023.01.14 |
댓글