회원 관리 예제 작성해보기 - 백엔드 개발
- 비즈니스 요구사항 정리
- 회원 도메인과 레포지토리 제작
- 회원 레포지토리 테스트 케이스 작성
- 회원 서비스 개발
- 회원 서비스 테스트 - Junit이라는 테스트 프레임워크 사용
이전 게시글에서 만든 회원 레포지토리 클래스가 프로그래머가 원하는 방식으로 제대로 작동하는지 검증해야한다.
- Java의 main method를 통해 실행해보기
- 웹 앱의 컨트롤러를 통해 실행해보기
통 이 두가지의 방법을 (공부할 때는) 많이 사용하는데, 준비하는데 오래걸리고 반복실행이 어려우며, 여러 테스트를 한번에 실행하기 어렵다.
따라서, 자바에서는 JUnit이라는 프레임워크로 테스트 코드를 작성한 후, 그 코드를 실행시키는 방식으로 검증한다.
바로, 코드(작성한) 를 코드(테스트 코드)로 검증하기
테스트 코드 작성
src > test 에 테스트하려는 패키지를 그대로 생성해준다. (주로 이름을 동일하게 생성함)
그 내부에 테스트 하려는 클래스명+Test를 붙여 클래스를 생성해준다. (주로 클래스명 뒤에 Test를 붙이는 방식으로 이름을 제작)
테스트 대상인 클래스의 객체를 만들어 준 후, 테스트 하려는 메소드를 작성해주기만 하면 실행된다.
하지만 다음과 같이 @Test (org.junit.jupiter.api)를 위에 작성해주어야 한다.
public class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
}
}
이 상태에서 실행시켜 보면, 다음과 같이 확인할 수 있다. (save method가 실행된 것이다.)
이제 본격적으로 기능을 테스트 해본다.
마치 main함수 내에 작성하는 것과 유사하게 작성한다. (그냥 method를 불러오고 ... 실행해보고..)
- 맴버 객체를 생성하고
- save 메서드를 실행해서 store에 저장하고
- find메서드를 이용해서 해당 메서드의 결과를 확인하고
- 실제 값과 비교하여 잘 저장되었는지 확인하기 → 여기에는 3가지 방법이 있다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
Member member = new Member(); //맴버 객체 생성
member.setName("yujeongLEE");// name에 값 setting
repository.save(member); //save method를 실행 -> store에 방금 생성한 member저장
Member result = repository.findById(member.getId()).get(); // 방금 생성한 member의 ID를 이용해 findById로 맴버가 잘 찾아지는지 확인
1. 콘솔창에 출력해보기
System.out.println("result = " + (result == member)); -> 내가 생성한 member와 DB에서 가져온 결과가 같으면 "result = true"가 콘솔창에 출력됨
}
}
참고
.get()
Optional을 벗겨낼 수 있다.
Optional<Member> result = respository.findByName("yujoengLEE");
이와 같은 코드를 .get()을 이용하면, 다음과 같이 쓸 수 있다.
Member result = repository.findByName("yujeongLEE").get();
하지만 위와 같은 바로 꺼내는 방식은 테스트 이외로는 활용하지 않는 것이 좋다.
3가지 방법이 있다.
- 콘솔로 출력해보는 방법 → 생략(위 코드 참고) / 잘 안쓰임
- Assertions.assertEquals(기댓값, 실제값)
- Assertoins.assertThat() → 가장 많이 사용됨
Assertions.assertEquals
Assertion 중 JUnit의 assertion
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
Member member = new Member(); //맴버 객체 생성
member.setName("yujeongLEE");// name에 값 setting
repository.save(member); //save method를 실행 -> store에 방금 생성한 member저장
Member result = repository.findById(member.getId()).get(); // 방금 생성한 member의 ID를 이용해 findById로 맴버가 잘 찾아지는지 확인
// 2. Assertions 이용 : 둘이 같은지 확인가능 매개변수(expected, actual)
Assertions.assertEquals(member, result); // 같은 경우테스트용
// Assertions.assertEquals(member, null); //다른 경우테스트용
}
}
같은 경우 결과
기댓값과 결과값이 다른경우
Assertations.assertThat
Assertion 중 assertj의 Assertions
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
Member member = new Member(); //맴버 객체 생성
member.setName("yujeongLEE");// name에 값 setting
repository.save(member); //save method를 실행 -> store에 방금 생성한 member저장
Member result = repository.findById(member.getId()).get(); // 방금 생성한 member의 ID를 이용해 findById로 맴버가 잘 찾아지는지 확인
// 3. Assertions 이용(assertj)
Assertions.assertThat(member).isEqualTo(result);
}
}
다음과 같이 사용할 수 있으며, static import가 가능하기 때문에 다음과 같이 변경할 수도 있다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*; //static import
public class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
Member member = new Member(); //맴버 객체 생성
member.setName("yujeongLEE");// name에 값 setting
repository.save(member); //save method를 실행 -> store에 방금 생성한 member저장
Member result = repository.findById(member.getId()).get(); // 방금 생성한 member의 ID를 이용해 findById로 맴버가 잘 찾아지는지 확인
// 3. Assertions 이용(assertj)
assertThat(member).isEqualTo(result);
}
}
실무에서는 빌드 툴과 엮어 테스트 케이스를 통과하지 않으면, 다음단계로 넘어가지 않도록 막는다.
기댓값과 결과값이 동일
동일하지 않음 (result대신 null 넣었을 경우)
여기까지 방법을 알아보았다.
위에서는 save 메소드만 테스트 해보았는데, 이제 위의 방법을 이용해 다른 메소드들도 검증해보도록 하자.
클래스 단위의 테스팅
테스트 코드의 장점 : 여러가지의 메소드를 동시에 테스트 해볼 수 있다 .
다음과 같이 save와 findByName, findAll 3개의 메소드에 대한 테스트 케이스를 작성하였다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
public class MemoryMemberRepositoryTest {
MemberRepository repository = new MemoryMemberRepository();
@Test
public void save(){
Member member = new Member(); //맴버 객체 생성
member.setName("yujeongLEE");// name에 값 setting
repository.save(member); //save method를 실행 -> store에 방금 생성한 member저장
Member result = repository.findById(member.getId()).get(); // 방금 생성한 member의 ID를 이용해 findById로 맴버가 잘 찾아지는지 확인
assertThat(member).isEqualTo(result);
}
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("yujeongLEE");
repository.save(member1);
Member member2 = new Member();
member2.setName("hyunjiLIM");
repository.save(member2);
Member result = repository.findByName("yujeongLEE").get();
assertThat(result).isEqualTo(member1);//member1을 넣으면 성공 / member2를 넣으면 오류 여야함
}
@Test
public void findAll(){
Member member1 = new Member();
member1.setName("yujeongLEE");
repository.save(member1);
Member member2 = new Member();
member2.setName("hyunjiLIM");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2); //모든 것을 찾아내는 메소드의 사이즈가 2이면 성공해야함
}
}
아래의 2가지 방법으로 클래스 단위로 테스트를 진행할 수 있다.
결과
위의 3가지 메소드 테스트를 실행시키면 다음과 같은 결과를 얻을 수 있다.
아래와 같이 각각 메소드에 대한 정상/에러 여부를 한눈에 알 수 있다.
그런데, findByName에서 오류가 있는 모습을 볼 수 있다.
그 이유는 다음과 같다.
위의 사진의 왼쪽의 순서는 테스트 코드가 진행된 순서를 뜻한다. 즉, 입력 순서대로 실행되지 않는 다는 것을 알 수 있다.
모든 테스트는 순서를 보장하지 않는다.
따라서, findAll내에서 만들어 졌던 member1 이 먼저 생성되어 저장되었기 때문에,
@Test
public void findAll(){
Member member1 = new Member();
member1.setName("yujeongLEE");
repository.save(member1);
Member member2 = new Member();
member2.setName("hyunjiLIM");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2); //모든 것을 찾아내는 메소드의 사이즈가 2이면 성공해야함
}
findByName에서 "yujeongLEE"로 검색 시 반환된 값이 findByName내에서 정의한 member1이 아닌 findAll에서 정의한 "yujeongLEE"로 같지 않기 때문에 테스테시 오류가 발생한 것이다.
이를 해결하기 위해서는, 테스트가 끝날때 마다 메모리를 비워주어야 한다.
afterEach
하나의 테스트가 끝날 때 마다 메모리(저장소, 공용데이터)를 비워주는 코드를 작성해주어야 한다.
afterEach는 메소드가 끝날 때 마다 동작하는 콜백메소드이다.
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach(){
// 메소드가 끝날때 마다 실행됨
}
//생략
}
이제 afterEach메소드 내에 store를 비워주는 메소드를 호출해서, store를 비워주면된다.
이때, store는 test가 아닌 main내의 MemoryMemberRepository에 존재하므로, 해당 클래스에 가서 메소드를 작성해주어야한다.
MemoryMemberRepository
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>(); // 데이터를 저장할 Map 자료형
private static long sequence = 0L; // ID용
//생략
public void clearStore(){
store.clear();
}
}
이제 위의 함수를 호출하면된다.
repository라는 이름으로 MemoryMemberRepository객체를 생성했으므로, repository를 이용하여 객체의 메소드를 호출하여 실행한다.
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach(){
repository.clearStore();
}
}
큰 프로그램 특히 협업을 진행할 수록 테스트 케이스 없이는 개발이 불가능하다!
테스트 케이스에 대해서는 깊게 공부해야한다
추가)
TDD(Test-driven development)
테스트 주도 개발, 테스트 케이스 부터 작성한다음 그에 맞는 프로그램(구현 클래스)을 개발하는 개발방법 (위와는 반대로 실행되는 것)
'Backend > Spring' 카테고리의 다른 글
[Spring] Spring Bean1 (0) | 2021.09.10 |
---|---|
[Spring] 회원관리 예제 - 3) 회원 서비스 (0) | 2021.09.07 |
[Spring] 회원관리 예제-1) 도메인과 레포지토리 (0) | 2021.09.07 |
[Spring] 웹개발 기초(정적,MVC,API) (0) | 2021.09.01 |
[Spring]정적, 동적 페이지 동작원리 (0) | 2021.08.31 |