강의에서 진행하는 회원 서비스 개발 따라하기
데이터: 회원id, 이름
기능 : 회원 등록,조회
아직 데이터 저장소가 선정되지않았음(가상의 시나리오)
데이터 저장소가 선정되지않아서 멤버저장소는 인터페이스로 구현한다.
데이터저장소는 구현체를 메모리 구현체로 만들것임. 향후에 RDB든 뭐든 정해지면 바꿔낄것임
바꿀거기 때문에 인터페이스로 정의해두었다.
회원도메인과 리포지토리 만들기
멤버.java
package hello.hellospring.domain;
public class Member {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
다음과 같이 멤버를 구성해준다 ( 위에서 데이터로 사용할 id와 이름을 넣었음 + getter,setter)
MemberRepository 인터페이스
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
다음과 같이 멤버를 저장하고 저장한 멤버를 돌려주는 기능
아이디를 이용하여 멤버를 찾는기능
이름을 이용하여 멤버를 찾는기능
모든 멤버를 조회하는 기능을
가지는 멤버리포지토리 인터페이스를 만든다.
팁 -> 컨트롤+ 쉬프트 + 엔터로 커서가 괄호사이 안쪽에있어서 해당 단축키로 맨끝에 세미클론을 붙이며 빠져나오기가능
MemoryMemberRepository.java 구현
위에서 작성한 메모리레포지토리 인터페이스를 구현하여
메모리 멤버 레포지토리를 구현한다.
가상의 저장공간을 Map으로서 구현한것이다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository {
//실무에서는 동시성문제가있을수있어서 공유되는 변수일때는 다른걸 써야하는데 여기는 예제이므로 Map으로 진행
//저장할 변수를 선언해준다.
private static Map<Long,Member> store = new HashMap<>(); //가상의 저장공간으로 Map을 사용
private static long sequence = 0L; //0,1,2처럼 키값을 생성해주는 역할
@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)); //id를 이용하여 Map에서는 키값이니까 키값에 해당한 밸류를 가져온다.
//하지만 값이 없을경우에 null이 반환될텐데 null이 반환되는것을 막기위해 Optional.ofNullable()이라는것으로 감싸준다.
//감싸서 보내주면 클라이언트에서 뭔가 할 수 있음
}
@Override
public Optional<Member> findByName(String name) {
//.values()는 해당 map의 value 목록을 Collection 형태로 리턴한다. 여기서는 Collection<Member> 형태로 반환.
return store.values().stream() // 밸류값 콜렉션 객체로 스트림객체를 만들어주고
.filter(member -> member.getName().equals(name)) // 스트림객체에서 입력으로 들어온 이름과 같은 멤버 네임을 가진애들만 뽑아서 구성해주고
.findAny(); //그 중 아무거나 하나를 고른다, 그 후 return이므로 그걸 반환해줌 , 없다면 null 하지만 optional로 감싸져서 리턴된다.
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
다음과 같고 각 설명은 주석으로 달아보았다.
Stream과 Optional에 관하여는 더 공부가 필요하다.
스트림관련 참고.
optional 참고
회원 리포지토리 테스트 케이스 작성
개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나,
웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다.
이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다.
자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다.
프로젝트에 원래 main과 test라는 폴더로 나누어져있다.
test안에도 java -> hello.hellospring 처럼 구성이 되어있었고
그안에 repository 디렉토리를 만들고 그안에 메모리멤버 리포지토리를 테스트하기위한
MemoryMemberRepositoryTest 를 만들어준다. 이름뒤에 Test를 붙여준다.
메소드 위에 @Test 어노테이션을 붙여주면 해당 메소드만 돌릴수있게 옆에 아이콘이 나온다. 신기하다.
Assertions중 어떤걸 사용하는가?
Assertions를 매번치기 힘들때 사용하는 방법
Assertions를 클릭후 알트+엔터하고 static import를 선택해준다.
전체적인 코드모습 (MemoryMemberRepositoryTest.java)
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
class MemoryMemberRepositoryTest { //다른곳에 가져다 쓸게아니니까 public일 필요가없음
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach //하나의 테스트 메소드가 동작이 끝날때마다 해당 어노테이션이 붙은 메소드를 동작하라는뜻.
public void afterEach()
{
repository.clearStore(); // 저장소에있는 내용 삭제
}
@Test
public void save(){
Member member = new Member(); //멤버 객체를 만들고
member.setName("spring"); // 이름설정해주고
repository.save(member); // 만든 멤버를 저장해보고
//잘 저장됬는지 id를 리턴받아본다. 반환값이 Optional<Member>인데 .get()으로 Optional안에있는값을 꺼내볼수있다.
//.get()말고 다른게쓰이긴하는데 테스트에서는 그냥 쓰인다고함.
Member result = repository.findById(member.getId()).get();
//검증방법 1. 내가 만든 member와 , result로 받은 멤버가 같은지 비교
// System.out.println("result = " + (result == member));
//하지만 콘솔에 찍어서 눈으로 확인하는 방법보다는 Assertions.assertThat(member).isEqualTo(result)를 많이쓴다.
assertThat(member).isEqualTo(result); //실제값 예상값 순으로 넣는다.
//돌려보면 출력되는건 없지만 잘돌아간다면 Run 왼쪽아래에 녹색체크모양이 뜬다.
}
@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);
}
@Test
public void findAll(){
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member(); //shift+F6으로 편하게 수정해주고
member2.setName("spring2");
repository.save(member2);
List<Member> result = repository.findAll(); //컨트롤+알트+v로 편하게 변수 생성해준다.
assertThat(result.size()).isEqualTo(2); // 갯수로 테스트해본다.
}
}
테스트 결과 모습
Tip -> 같은걸 복사했을때 쉽게 이름 바꾸는법
컨트롤 + p (어떤값을 넣어야하는지)
컨트롤+쉬프트+p (어떤값이 리턴되는지) 확인할 수 있다.
컨트롤 + 알트 + v -> 변수 추출하기 (새로운 변수로 선언)
다음과같이 리턴값을 받아줄 변수가 자동으로 생성된다. 수정하고 싶은거 수정하고 엔터치면 확정됨.
컨트롤 + 쉬프트 + f10으로 커서가있는 위치의 메소드를 실행해주고
쉬프트 + f10을 해주면 최근 실행한 메소드를 실행해주는것같다.
여러개의 테스트 메소드를 한번에 돌리고싶다면 클래스 옆에있는 run을 누르던지 클래스에서 컨트롤 + 쉬프트 + f10 하면 여러개의 메소드를 동시에 테스트 할 수 있다.
오류의 이유는 findAll을 진행할때(테스트 자바파일에서 findAll테스트를 위해 멤버 2개를 집어넣는 코드를 진행했었음)
저장소(테스트 자바파일의)에 이미 member1과 2를 저장해놨는데
findbyname에서 멤버 1,2를 또 저장하고 꺼내오려니 이미 저장된 애들을 가져오게되고(findAll할때 저장해놨던),
예상치못한 객체가 나와서 오류가 발생하는것.(값이 같아도 객체는 주소값이 다르므로,주소값이 다르다 == 다른객체)
전체 테스트할때 메소드 동작순서는 랜덤이다. 그러므로 메소드를 만들때 동작순서에 상관없이 만들어야함.
해결방법은 테스트가 한번끝나고 나면 데이터를 클리어해줘야한다. (저장소에 저장된 데이터를 지워되야한다.)
public void clearStore() { //인터페이스에 있는걸 구현한게아닌 해당 클래스에서 구현한 고유의 메소드
store.clear(); //
}
다음과 같은 메소드를 MemoryMemberRepository.java에 추가해준다.
Map 변수를 저장소로 사용하고 있는데 Map에 저장된 모든 객체를 삭제해주는 메소드이다.
다음과 같은 메소드를 @AfterEach를 붙인 메소드에 넣어주면 , 하나의 테스트 메소드가 동작끝날떄마다 해당 메소드를 실행하게 된다.
@AfterEach //하나의 테스트 메소드가 동작이 끝날때마다 해당 어노테이션이 붙은 메소드를 동작하라는뜻.
public void afterEach()
{
repository.clearStore(); // 저장소에있는 내용 삭제
}
@AfterEach 어노테이션과 다른 어노테이션에 관한 정보참조
이렇게 코드를 다 짜고 난후 테스트에 대한 코드를 짜서 테스트를 이용해 실험하는 방법도 있지만
테스트 코드를 먼저짜고 개발을 시작하는 TDD 테스트 주도개발이라는것도 존재한다.
나중에 테스트 파일이 너무많을대는 패키지폴더에서 Run을 해버리면된다.
'인프런 > 스프링 입문' 카테고리의 다른 글
6)스프링 빈과 의존관계, 스프링 빈 등록방법 1. 컴포넌트 스캔 2. 자바로 직접 등록 (0) | 2022.11.11 |
---|---|
5)회원 서비스 만들기(회원가입..등),테스트하기,static변수특징,생성자사용하기,인터페이스객체,의존성주입 (0) | 2022.10.29 |
3. 스프링 웹 개발 기초(정적컨텐츠,mvc와 템플릿엔진,API) (0) | 2022.10.03 |
2. View 환경설정, welcome page 만들기,컨트롤러 생성, 웰컴페이지가 보이는 동작방식 , 콘솔에서 실행방법 (0) | 2022.10.03 |
1. 스프링 부트 프로젝트 생성 ~ 실행, build.gradle,프로젝트 설정 팁,라이브러리 살펴보기 (0) | 2022.10.03 |
댓글