๋น์ฆ๋์ค ์๊ตฌ์ฌํญ ์ ๋ฆฌ
๋ฐ์ดํฐ : ํ์ ID, ์ด๋ฆ
๊ธฐ๋ฅ : ํ์ ๋ฑ๋ก, ์กฐํ
์์ง ๋ฐ์ดํฐ ์ ์ฅ์๊ฐ ์ ์ ๋์ง ์์๋ค๊ณ ๊ฐ์
์ด ๊ฐ์์ ๋ชฉํ๋ ๋ณต์กํ ๋น์ฆ๋์ค๋ฅผ ํ๋ ๊ฒ์ด ์๋๋ผ ๋จ์ํ ์์ ๋ฅผ ๊ฐ์ง๊ณ
์คํ๋ง ์ํ๊ณ ์ ๋ฐ์ ์ผ๋ก ์ด๋ค ์์ผ๋ก ๊ฐ๋ฐ์ด ์ด๋ฃจ์ด์ง๊ณ ๋์ํ๋์ง ์์๋ณด๋ ๊ฒ์ด๋ค.
๋ฐ๋ผ์ ๋จ์ํ ๋น์ฆ๋์ค๋ก ์ค์ ํ์ฌ ์งํํด๋ณด๊ธฐ๋ก ํ๋ค.
# ์ผ๋ฐ์ ์ธ ์น ์ดํ๋ฆฌ์ผ์ด์ ๊ณ์ธต ๊ตฌ์กฐ
์ปจํธ๋กค๋ฌ : ์น MVC์ ์ปจํธ๋กค๋ฌ ์ญํ
์๋น์ค : ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง ๊ตฌํ
๋ฆฌํฌ์งํ ๋ฆฌ : ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผ, ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ DB์ ์ ์ฅํ๊ณ ๊ด๋ฆฌ
๋๋ฉ์ธ : ๋น์ฆ๋์ค ๋๋ฉ์ธ ๊ฐ์ฒด, ์) ํ์, ์ฃผ๋ฌธ, ์ฟ ํฐ ๋ฑ๋ฑ ์ฃผ๋ก ๋ฐ์ดํฐ ๋ฒ ์ด์ค์ ์ ์ฅํ๊ณ ๊ด๋ฆฌ๋จ
# ํด๋์ค ์์กด๊ด๊ณ
๋ฐ์ดํฐ ์ ์ฅ์๋ RDB, NoSQL ๋ฑ๋ฑ ๋ค์ํ ์ ์ฅ์๋ฅผ ๊ณ ๋ฏผ ์ค์ธ ์ํฉ์ผ๋ก ๊ฐ์ ํ๋ค.
๊ฐ๋ฐ์ ์งํํ๊ธฐ ์ํด์ ์ด๊ธฐ ๊ฐ๋ฐ ๋จ๊ณ์์๋ ๊ตฌํ์ฒด๋ก ๊ฐ๋ฒผ์ด ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ์ ๋ฐ์ดํฐ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ค.
์์ง ๋ฐ์ดํฐ ์ ์ฅ์๊ฐ ์ ์ ๋์ง ์์๊ธฐ์, ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ดํ DB๋ฅผ ๊ฒฐ์ ํ๋ฉด ๊ตฌํ ํด๋์ค๋ฅผ ๋ณ๊ฒฝํ ์ ์๋๋ก ์ค๊ณํ๋ค.
์ต์ข
๊ณ์ธต ๊ตฌ์กฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
ํ์ ๋๋ฉ์ธ๊ณผ ๋ฆฌํฌ์งํ ๋ฆฌ ๋ง๋ค๊ธฐ
# ํ์ ๊ฐ์ฒด
java/hello/hellospring/domain/Member.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;
}
}
# ํ์ ๋ฆฌํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค
java/hello/hellospring/repository/MemberRepository.java
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); // ID๋ก ํ์ ์ฐพ๊ธฐ
Optional<Member> findByName(String name); // Name์ผ๋ก ํ์ ์ฐพ๊ธฐ
List<Member> findAll(); // ์ง๊ธ๊น์ง ์ ์ฅ๋ ๋ชจ๋ ํ์ ๋ฆฌ์คํธ ๋ฐํ
}
# ํ์ ๋ฆฌํฌ์งํ ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ๊ตฌํ์ฒด
java/hello/hellospring/repository/MemoryMemberRepositoryTest.java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import java.util.*;
/**
* ๋์์ฑ ๋ฌธ์ ๊ฐ ๊ณ ๋ ค๋์ด ์์ง ์์, ์ค๋ฌด์์๋ ConcurrentHashMap, AtomicLong ์ฌ์ฉ ๊ณ ๋ ค
*/
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); // member save ํ ๋ sequence ๊ฐ ํ๋์ฉ ์ฌ๋ ค์ ์ ์ฅ
store.put(member.getId(), member); // member Id์ member๋ฅผ ํ ์์ผ๋ก Map์ ์ ์ฅ
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id)); // ofNullable์ ํตํด ๋ฐํ๊ฐ์ด null์ด์ด๋ ํด๋ผ์ด์ธํธ์์ ์ฒ๋ฆฌ ๊ฐ๋ฅ
}
@Override
public Optional<Member> findByName(String name) {
// ๊ฒ์ ๊ฒฐ๊ณผ๋ Optional๋ก ๋ฐํ๋๋ฉฐ, ์๋ ์ด๋ฆ์ด๋ผ๋ฉด Optional์ null์ด ํฌํจ๋์ด ๋ฐํ
return store.values().stream()
.filter(member -> member.getName().equals(name)) // member์ ์ด๋ฆ์ด parameter์ name๊ณผ ์ผ์นํ๋์ง ํ์ธ
.findAny(); // String์์ ๊ฐ์ฅ ๋จผ์ ํ์๋๋ ์์ ๋ฆฌํด
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values()); // store์ member๋ค์ ์ ๋ถ ๋ฐํ
}
public void clearStore() {
store.clear();
}
}
ํ์ ๋ฆฌํฌ์งํ ๋ฆฌ ํ ์คํธ ์ผ์ด์ค ์์ฑ
๋ณดํต ์ฐ๋ฆฌ๊ฐ ๊ฐ๋ฐํ ๊ธฐ๋ฅ์ ์คํํด์ ํ ์คํธ ํ ๋,
1. ์๋ฐ์ Main ๋ฉ์๋๋ฅผ ํตํด ์คํํ๊ฑฐ๋
2. ์น ์ดํ๋ฆฌ์ผ์ด์ ์ ์ปจํธ๋กค๋ฌ๋ฅผ ํตํด์ ํด๋น ๊ธฐ๋ฅ์ ์คํํ๋ค.
ํ์ง๋ง ์ด๋ฌํ ๋ฐฉ๋ฒ์ ์ค๋น์ ์คํ์ด ์ค๋ ๊ฑธ๋ฆฌ๊ณ , ๋ฐ๋ณต ์คํ๊ณผ ์ฌ๋ฌ ํ ์คํธ์ ๋์ ์คํ์ด ์ด๋ ต๋ค๋ ๋จ์ ์ด ์๋ค.
Java๋ JUnit์ด๋ผ๋ ํ๋ ์์ํฌ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ๋ง๋ค๊ณ ๊ทธ ์ฝ๋ ์์ฒด๋ฅผ ์คํํด์ ์์ ๊ฐ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค.
# ํ์ ๋ฆฌํฌ์งํ ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ ๊ตฌํ์ฒด ํ
์คํธ
java/hello/hellospring/repository/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.assertThat;
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
// ๋ฉ์๋๊ฐ ๋๋ ๋๋ง๋ค ๋์ํ๋ ์ฝ๋ฐฑ ๋ฉ์๋
@AfterEach
public void afterEach() {
// clear repository
repository.clearStore();
}
@Test
public void save() {
Member member = new Member(); // ์๋ก์ด member ์์ฑ
member.setName("spring"); // member ์ด๋ฆ spring์ผ๋ก ์ค์
repository.save(member); // repository์ ์ ์ฅ
Member result = repository.findById(member.getId()).get();
assertThat(member).isEqualTo(result);
}
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
// shift + F6์ผ๋ก ๋ณ์ ํธ๋ฆฌํ๊ฒ ๋ณ๊ฒฝ ๊ฐ๋ฅ
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
// result์ repository์์ spring1์ด๋ผ๋ ์ด๋ฆ์ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ์ฐพ์ ์ ์ฅ (hello.hellospring.domain.Member@77f1baf5์ด ์ ์ฅ๋จ)
Member result = repository.findByName("spring1").get();
// result์ member1์ด ๊ฐ์์ง ํ์ธ
assertThat(result).isEqualTo(member1);
}
@Test
public void findAll() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2);
}
}
# save() ํ ์คํธ ๊ฒฐ๊ณผ

> ์ ์์ ์ผ๋ก ์คํ๋๋ค.
# findByName() ํ ์คํธ ๊ฒฐ๊ณผ
1) member๊ฐ result์ ๊ฐ์ ๊ฒฝ์ฐ
Member result = repository.findByName("spring1").get();
assertThat(result).isEqualTo(member1);

> ์ ์์ ์ผ๋ก ์คํ๋๋ค.
2) member๊ฐ result๊ฐ ๋ค๋ฅธ ๊ฒฝ์ฐ
Member result = repository.findByName("spring2").get();
assertThat(result).isEqualTo(member1);

> result์๋ spring2 ์ด๋ฆ์ ํตํด ์ฐพ์ member2๊ฐ ์ ์ฅ๋์ด ์๋๋ฐ member1๊ณผ ๊ฐ์์ง ๋น๊ตํ๊ธฐ ๋๋ฌธ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
# findAll() ํ ์คํธ ๊ฒฐ๊ณผ
1) isEqualTo๊ฐ 2์ธ ๊ฒฝ์ฐ

> ์ ์์ ์ผ๋ก ์คํ๋๋ค.
2) isEqualTo๊ฐ 3์ธ ๊ฒฝ์ฐ

> isEqualTo๋ฅผ 2์์ 3์ผ๋ก ๋ฐ๊พธ๋ฉด ์์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
# Test ์ ์ฃผ์์ฌํญ

class ๋จ์์์์ ํ ์คํธ ์งํ ์ findByName()์์ ์ด์ ๊ณผ ๋ค๋ฅธ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ด๋ findAll()์์ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ค repository์ ์ ์ฅํ๋ ์ฝ๋๊ฐ findByName()์์ ํ ๋ฒ ๋ ๋ฑ์ฅํด์
์ด์ ์ ์ ์ฅํ๋ spring1์ ๋ํ ์ ๋ณด๊ฐ ๋ถ๋ฌ์์ก๊ธฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ด๋ค.
// ๋ฉ์๋๊ฐ ๋๋ ๋๋ง๋ค ๋์ํ๋ ์ฝ๋ฐฑ ๋ฉ์๋
@AfterEach
public void afterEach() {
repository.clearStore();
}
@AfterEach : ํ๋ฒ์ ์ฌ๋ฌ ํ
์คํธ๋ฅผ ์คํํ๋ฉด ๋ฉ๋ชจ๋ฆฌ DB์ ์ง์ ํ
์คํธ์ ๊ฒฐ๊ณผ๊ฐ ๋จ์ ์ ์๋ค. ์ด๋ ๊ฒ ๋๋ฉด ๋ค์ ์ด์ ํ
์คํธ ๋๋ฌธ์ ๋ค์ ํ
์คํธ๊ฐ ์คํจํ ๊ฐ๋ฅ์ฑ์ด ์๋ค. @AfterEach๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ ํ
์คํธ๊ฐ ์ข
๋ฃ๋ ๋ ๋ง๋ค ์ด ๊ธฐ๋ฅ์ ์คํํ๋ค. ์ฌ๊ธฐ์๋ ๋ฉ๋ชจ๋ฆฌ DB์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋ค.
๋ํ, ํ ์คํธ๋ ๊ฐ๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ์คํ๋์ด์ผ ํ๋ค. ํ ์คํธ ์์์ ์์กด๊ด๊ณ๊ฐ ์๋ ๊ฒ์ ์ข์ ํ ์คํธ๊ฐ ์๋๋ค.

> AfterEach ์ฌ์ฉ : class ๋จ์์์ ํ ์คํธ ์ผ์ด์ค ์คํ ์ ์ ์์ ์ผ๋ก ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
# ๊ฐ๋ฐ๊ณผ ํ ์คํธ ์์
ํ์ฌ๋ MemoryMemberRepository ๊ฐ๋ฐ ์๋ฃ ํ ํ ์คํธ ํด๋์ค๋ฅผ ์์ฑํ๋ค.
ํ์ง๋ง ํ ์คํธ ํด๋์ค๋ฅผ ๋จผ์ ์์ฑํ๊ณ MemoryRepository๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ด ์๋๋ฐ
์ด๋ฅผ ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD)์ด๋ผ๊ณ ํ๋ค.
ํ์ ์๋น์ค ๊ฐ๋ฐ
# ํ์ ์๋น์ค ๊ฐ๋ฐ
java/hello/hellospring/service/MemberService.java
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// ํ์ ๊ฐ์
public Long join(Member member){
validateDuplicateMember(member); // ์ค๋ณต ํ์ ๊ฒ์ฆ
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m -> { // ๊ฐ์ด ์กด์ฌํ๋ค๋ฉด
throw new IllegalStateException("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
});
}
// ์ ์ฒด ํ์ ์กฐํ
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
ํ์ ์๋น์ค ํ ์คํธ

# DI(Dependency Injection, ์์กด๊ด๊ณ ์ฃผ์ )
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
}
๊ธฐ์กด์๋ ํ์ ์๋น์ค๊ฐ ๋ฉ๋ชจ๋ฆฌ ํ์ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์ง์ ์์ฑํ๊ฒ ํ๋ค.
MemberService์ MemberServiceTest์์ new MemoryMemberRepository()๋ฅผ ๊ฐ๊ฐ ์ ์ธํด์ฃผ๋ฉด
์๋ก ๋ค๋ฅธ Repository(๋ค๋ฅธ ๊ฐ์ฒด)๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ์์กด๊ด๊ณ๋ฅผ ์ฃผ์ ํด์ค์ผ ํ๋ค.
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
-> ํ์ ๋ฆฌํฌ์งํ ๋ฆฌ์ ์ฝ๋๊ฐ ํ์ ์๋น์ค ์ฝ๋๋ฅผ DI ๊ฐ๋ฅํ๊ฒ ๋ณ๊ฒฝํ๋ค. (Constructor ์ถ๊ฐ)
# given, when, then ๋ฌธ๋ฒ
given - ๋ฌด์ธ๊ฐ๊ฐ ์ฃผ์ด์ก๋๋ฐ
when - ์ด๊ฑธ ์คํํ์ ๋
then - ์ด๋ฐ ๊ฒฐ๊ณผ๊ฐ ๋์์ผ ํจ
# ํ์ ์๋น์ค ํ ์คํธ
test/java/hello/hellospring/service/MemberServiceTest.java
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
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 java.util.Optional;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
@Test
void ํ์๊ฐ์
() {
//given
Member member = new Member();
member.setName("hello");
//when
Long saveId = memberService.join(member);
//then
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public void ์ค๋ณต_ํ์_์์ธ() {
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
/*
try {
memberService.join(member2);
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
}
*/
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
@BeforeEach : ๊ฐ ํ ์คํธ ์คํ ์ ์ ํธ์ถ๋๋ค. ํ ์คํธ๊ฐ ์๋ก ์ํฅ์ด ์๋๋ก ํญ์ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ , ์์กด๊ด๊ณ๋ ์๋ก ๋งบ์ด์ค๋ค.
๋ด์ฉ ๋ฐ ์ด๋ฏธ์ง ์ถ์ฒ : ๊น์ํ์ ์คํ๋ง ์ ๋ฌธ