우선 아이디와 회원이름만 들어가는 도메인을 설계해보자
Long
으로 id
를, string
타입으로 name
을 만들고, 게터세터를 만들어준다.
게터와 세터는 OOP의 특징중 하나로써 다른 클래스에서 직접적으로 현재 클래스의 변수를 다룰수 없게 만들어준다.
예를들어 이름을 입력하는칸에 특수문자를 입력하면 이를 세터에서 입력을 안한걸로 처리한다든지 해서 문제가 생기지 않도록 한다.
package hello.hellospring.domain;
public class Member {
private Long id;
private String name;
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
}
인터페이스란 통일된 어떠한 형식을 말한다.
그니까 축구에서 감독이 선수가 필요하다며 스카우터보고 선수를 찾아보라고 하면 A스카우터는 골키퍼를, B스카우터는 나이많은 베테랑 수비수를, C 스카우터는 한참 전성기인 공격수를, D 스카우터는 나이어린 미드필더를 추천할 수 도 있다.
하지만 만약 감독이 '왼쪽 윙포워드가 주포지션이고 스피드가 빠른 나이어린 선수' 이렇게 하면 특정 형식에 맞게 스카우터들이 선수를 찾아온다.
이런식으로 어떠한 통일된 형식을 강제하는게 인터페이스이다.
코드로 따지면 MemberRepository
는 파라미터로 특정값을 받는 save, findById, findByName, findAll
같은게 된다.
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();
}
아래와 같이 인터페이스를 구현했다.
ofNullable
은 null
값을 허용한다는 말이다.
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();
}
}
개발을 했으면 테스트를 해야한다.
아래코드들은 src/text/java
하위 폴더에 생성했다.
이렇게 개발을 하고 테스트코드를 짤 수도 있고 반대로 테스트 코드먼저 짜고 그다음 개발을 하는 경우도 있다. 이걸 TDD(테스트 주도 개발)이라고 하는데 나도 나중에 경험해보고 싶다.
여기서 @AfterEach
는 각각의 독립적인 테스트가 끝나고 실행되는 메소드이다.
테스트가 끝나고 난뒤 레파지토리에 저장된 유저정보를 초기화하고 다시 다른 독립적인 테스틀 진행한다.
테스트코드를 작성할때는 절대 의존적이게 짜면안되고 독립적이어야한다.
junit
이나 assertj
을 사용하면 보다 쉽게 테스트코드를 짤 수 있다.
given-when-then
패턴을 테스트 코드 스타일중 하나이다.
given: 테스트 하기 위해 기본적으로 세팅하는 값
when: 테스트를 하기 위한 조건을 지정
then: 테스트 하기 위한 행위가 우리가 예상하는대로 동작하는지 검증하는 행동/절차
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;
import static org.assertj.core.api.Assertions.*;
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach
public void afterEach() {
repository.clearStore();
}
@Test
public void save() {
//given
Member member = new Member();
member.setName("sterling");
//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("haaland");
repository.save(member1);
Member member2 = new Member();
member2.setName("jesus");
repository.save(member2);
//when
Member result = repository.findByName("haaland").get();
//then
assertThat(result).isEqualTo(member1);
}
@Test
public void findAll() {
//given
Member member1 = new Member();
member1.setName("haaland");
repository.save(member1);
Member member2 = new Member();
member2.setName("jesus");
repository.save(member2);
//when
List<Member> result = repository.findAll();
//then
assertThat(result.size()).isEqualTo(2);
}
}