Bibi's DevLog πŸ€“πŸŽ

[Spring] μΈν”„λŸ° μŠ€ν”„λ§ μž…λ¬Έ(κΉ€μ˜ν•œ λ‹˜) - νšŒμ› 관리 예제:λΉ„μ¦ˆλ‹ˆμŠ€ μš”κ΅¬μ‚¬ν•­, νšŒμ› 도메인/리포지토리/μ„œλΉ„μŠ€ 개발, ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€ μž‘μ„± λ³Έλ¬Έ

πŸ–₯ BE λ°±μ—”λ“œ/Spring μŠ€ν”„λ§

[Spring] μΈν”„λŸ° μŠ€ν”„λ§ μž…λ¬Έ(κΉ€μ˜ν•œ λ‹˜) - νšŒμ› 관리 예제:λΉ„μ¦ˆλ‹ˆμŠ€ μš”κ΅¬μ‚¬ν•­, νšŒμ› 도메인/리포지토리/μ„œλΉ„μŠ€ 개발, ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€ μž‘μ„±

λΉ„λΉ„ bibi 2021. 3. 4. 00:55

이 글은 μΈν”„λŸ° κΉ€μ˜ν•œ λ‹˜μ˜ μŠ€ν”„λ§ μž…λ¬Έ κ°•μ˜ λ₯Ό λ“£κ³  μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€.

νšŒμ› 관리 예제 - λ°±μ—”λ“œ 개발

  • λΉ„μ¦ˆλ‹ˆμŠ€ μš”κ΅¬μ‚¬ν•­ 정리
  • νšŒμ› 도메인과 μ €μž₯μ†Œ(리포지토리) λ§Œλ“€κΈ°
  • νšŒμ› 리포지토리 ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€ μž‘μ„±
  • νšŒμ› μ„œλΉ„μŠ€ 개발
  • νšŒμ› μ„œλΉ„μŠ€ ν…ŒμŠ€νŠΈ

*νšŒμ› μ €μž₯μ†Œ(리포지토리) : νšŒμ› 도메인 객체λ₯Ό μ €μž₯ 및 뢈러올 수 μžˆλŠ” 객체.

λΉ„μ¦ˆλ‹ˆμŠ€ μš”κ΅¬μ‚¬ν•­ 정리

μ˜ˆμ‹œμ΄λ―€λ‘œ κ°€μž₯ μ‰¬μš΄ κ²ƒμœΌλ‘œ λ§Œλ“ λ‹€.

λ‹¨μˆœν•œ μ„œλΉ„μŠ€λ‘œ μŠ€ν”„λ§ μƒνƒœκ³„κ°€ μ–΄λ–»κ²Œ 개발, λ™μž‘λ˜λŠ”μ§€ μ΄ν•΄ν•˜λŠ” 게 λͺ©ν‘œλ‹€.

  • 데이터 : νšŒμ› 아이디, 이름
  • κΈ°λŠ₯ : νšŒμ› 등둝, 쑰회
  • (κ°€μƒμ˜ μ‹œλ‚˜λ¦¬μ˜€) 아직 데이터 μ €μž₯μ†Œκ°€ μ„ μ •λ˜μ§€ μ•ŠμŒ

일반적 μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 계측 ꡬ쑰

μŠ€ν”„λ§ μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 계측ꡬ쑰

일반적인 μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ 컨트둀러, μ„œλΉ„μŠ€, 리포지토리, 도메인, DB 의 5 μš”μ†Œλ‘œ κ΅¬μ„±λœλ‹€.

  • 컨트둀러 : μ›Ή MVC의 컨트둀러 μ—­ν• 
  • μ„œλΉ„μŠ€ : 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 κ΅¬ν˜„
    • λΉ„μ¦ˆλ‹ˆμŠ€ 도메인 객체λ₯Ό 가지고 핡심 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 λ™μž‘ν•˜λ„λ‘ κ΅¬ν˜„ν•œ 계측.
  • 리포지토리 : λ°μ΄ν„°λ² μ΄μŠ€μ— μ ‘κ·Ό, 도메인 객체λ₯Ό DB에 μ €μž₯ 및 관리
  • 도메인 : λΉ„μ¦ˆλ‹ˆμŠ€ 도메인 객체.
    • 예) νšŒμ›, μ£Όλ¬Έ, 쿠폰 λ“±λ“± 주둜 λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•˜κ³  관리됨

클래슀 μ˜μ‘΄κ΄€κ³„

μŠ€ν”„λ§ 클래슀 μ˜μ‘΄κ΄€κ³„

  • νšŒμ› 리포지토리 (MemberRepository) : μΈν„°νŽ˜μ΄μŠ€λ‘œ 섀계할 것이닀.

    • μ™œ? 아직 데이터 μ €μž₯μ†Œκ°€ μ„ μ •λ˜μ§€ μ•Šμ•˜μœΌλ―€λ‘œ.

      데이터 μ €μž₯μ†ŒλŠ” RDB, NoSQL λ“± λ‹€μ–‘ν•œ μ €μž₯μ†Œλ₯Ό κ³ λ―Ό 쀑인 μƒν™©μœΌλ‘œ κ°€μ •

    • Memory MemberRepository : νšŒμ› λ¦¬ν¬μ§€ν† λ¦¬μ˜ κ΅¬ν˜„μ²΄ (λ‹¨μˆœ)

      초기 개발 λ‹¨κ³„μ—μ„œλŠ” κ΅¬ν˜„μ²΄λ‘œ κ°€λ²Όμš΄ λ©”λͺ¨λ¦¬ 기반 데이터저μž₯μ†Œλ₯Ό μ‚¬μš©ν•œλ‹€.

    • ꡬ체적인 기술이 μ„ μ •λ˜κ³  λ‚˜λ©΄ 이λ₯Ό λ°”κΏ€ 것이닀. λ•Œλ¬Έμ— μΈν„°νŽ˜μ΄μŠ€λ‘œ λ§Œλ“€λ©΄ λ‚˜μ€‘μ— κ΅¬ν˜„ 클래슀λ₯Ό λ³€κ²½ν•˜κΈ° μ’‹λ‹€.

νšŒμ› 도메인과 리포지토리 λ§Œλ“€κΈ°

μ•žμ˜ λΉ„μ¦ˆλ‹ˆμŠ€ μš”κ΅¬μ‚¬ν•­μ„ λ°”νƒ•μœΌλ‘œ κ΅¬ν˜„μ„ μ‹œμž‘ν•œλ‹€.

  • νšŒμ› : 아이디와 이름을 가진닀.

    src/main/java/ν”„λ‘œμ νŠΈλͺ…/domain νŒ¨ν‚€μ§€λ₯Ό λ§Œλ“€κ³  Member.java 생성

    • μ•„μ΄λ””λŠ” νšŒμ›μ΄ μ§“λŠ” IDκ°€ μ•„λ‹ˆλΌ, νšŒμ› ꡬ뢄을 μœ„ν•΄ μ‹œμŠ€ν…œμ΄ μ •ν•˜λŠ” IDμž„.

      private Long id

    • 이름은 νšŒμ›μ΄ μ λŠ” 이름.

      private String name

    • getter, setterλ₯Ό λ§Œλ“ λ‹€.

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;
    }
}
  • μ €μž₯μ†Œ(리포지토리) : νšŒμ›(Member)을 μ €μž₯ν•˜κ³  κΊΌλ‚Έλ‹€.

    src/main/java/ν”„λ‘œμ νŠΈλͺ…/repository 디렉토리λ₯Ό λ§Œλ“€κ³  MemberRepositoryλΌλŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό μƒμ„±ν•œλ‹€.

    • μ €μž₯ : νšŒμ›μ„ μ €μž₯ν•˜κ³  μ €μž₯된 νšŒμ›μ„ λ°˜ν™˜ν•¨.

      Member save(Member member)

    • μ•„μ΄λ””λ‘œ νšŒμ›μ‘°νšŒ

      Optional<Member> findByID(Long id)

    • μ΄λ¦„μœΌλ‘œ νšŒμ›μ‘°νšŒ

      Optional<Member> findByName(String name)

    • λͺ¨λ“  νšŒμ›μ‘°νšŒ

      List<Member> findAll()

Optional : μžλ°”8에 λ“€μ–΄κ°„ κΈ°λŠ₯으둜, λ°˜ν™˜λ˜λŠ” 객체가 null일 κ°€λŠ₯성이 μžˆμ„ λ•Œ μ‚¬μš©ν•œλ‹€.

(μ°ΎμœΌλ €λŠ” idλ‚˜ name이 없을 수 μžˆμœΌλ―€λ‘œ 이런 처리λ₯Ό ν•œλ‹€)

우리 μ½”λ“œμ—μ„œλŠ” Member 객체λ₯Ό Optional둜 ν•œ 번 더 κ°μ‹Έμ„œ μ‚¬μš©ν•  것이닀.

*Optional λ³€μˆ˜μ—μ„œ 값을 κΊΌλ‚Ό λ•ŒλŠ” get()을 μ‚¬μš©ν•œλ‹€. (Optionalκ°μ²΄μ—μ„œ Member)

orElseGet() : 값이 있으면 κΊΌλ‚΄κ³ , μ—†μœΌλ©΄ ()λ‚΄μ˜ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•œλ‹€.

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();
}
  • μ €μž₯μ†Œμ˜ κ΅¬ν˜„μ²΄

    src/main/java/ν”„λ‘œμ νŠΈλͺ…/repository에 MemoryMemberRepository.java 생성

    클래슀λͺ… μ˜†μ— implements MemberRepositoryλ₯Ό μž…λ ₯ν•˜κ³  alt + enter둜 λ©”μ„œλ“œλ₯Ό λ§Œλ“ λ‹€.

    λ§Œλ“€μ–΄μ§„ λ©”μ„œλ“œλ₯Ό ν•˜λ‚˜ν•˜λ‚˜ κ΅¬ν˜„ν•œλ‹€.

    • save() : νšŒμ›μ„ μ €μž₯ν•˜λ €λ©΄ μ €μž₯ν•  객체가 ν•„μš”.

      • private static Map<Long, Member> store = new HashMap<>(); : member와 아이디λ₯Ό μ €μž₯ν•  객체이닀.
      • private static long sequence = 0L; : μ‹œν€€μŠ€λŠ” 0, 1, 2.. 와 같이 ν‚€ 값을 생성해 μ£ΌλŠ” 역할이라고 보면 λœλ‹€.
      • sequence에 1을 λ”ν•œ 값을 member의 μ•„μ΄λ””λ‘œ μ§€μ •ν•œλ‹€ - store에 아이디와 memberλ₯Ό λ„£λŠ”λ‹€ - memberλ₯Ό λ°˜ν™˜ν•œλ‹€
    • findById() : storeμ—μ„œ 아이디λ₯Ό 가지고 memberλ₯Ό κΊΌλ‚Έλ‹€.

      • return Optional.ofNullable(store.get(id));
      • 아이디에 ν•΄λ‹Ήν•˜λŠ” νšŒμ›μ΄ μ—†μ–΄ null이 λ‚˜μ˜¬ 수 μžˆμœΌλ―€λ‘œ, Optional.ofNullable()둜 감싸 μ€€λ‹€.
    • findByName() : storeμ—μ„œ 이름을 가지고 μ°ΎλŠ”λ‹€.

      • λžŒλ‹€μ™€ μŠ€νŠΈλ¦Όμ„ ν™œμš©ν•΄ 루프λ₯Ό λŒλ¦°λ‹€.
      • .filter(member -> member.getName().equals(name)) : member의 이름이 νŒŒλΌλ―Έν„°λ‘œ λ„˜μ–΄μ˜¨ nameκ³Ό 같은지 λΉ„κ΅ν•œλ‹€. 같은 κ²½μš°μ—λ§Œ 필터링이 λœλ‹€.
      • .findAny() : 찾은 것듀을 λͺ¨λ‘ λ°˜ν™˜ν•œλ‹€.
    • findAll() : λͺ¨λ“  νšŒμ›μ„ λ°˜ν™˜ν•œλ‹€.

      return new ArrayList<>(store.values())

      • νšŒμ› μ €μž₯은 Map에 ν•˜μ§€λ§Œ λ°˜ν™˜μ€ List둜 ν•œλ‹€. μ΄μœ λŠ” 루프λ₯Ό 돌리기 더 νŽΈν•΄μ„œ.

νšŒμ› 리포지토리 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ μž‘μ„±

κ΅¬ν˜„λœ MemoryMemberRepositoryκ°€ 잘 λ™μž‘ν•˜λŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄, ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€λ₯Ό λ§Œλ“€μ–΄ λ³Έλ‹€.

κ°œλ°œν•œ κΈ°λŠ₯을 μ‹€ν–‰ν•΄μ„œ ν…ŒμŠ€νŠΈ ν•  λ•Œ, mainλ©”μ„œλ“œλ₯Ό ν†΅ν•΄μ„œ μ‹€ν–‰ν•˜κ±°λ‚˜ 컨트둀러λ₯Ό 톡해 μ‹€ν–‰ν•œλ‹€. 이런 방법은 μ€€λΉ„ 및 싀행이 였래 걸리고, 반볡 싀행이 μ–΄λ ΅κ³ , μ—¬λŸ¬ ν…ŒμŠ€νŠΈλ₯Ό ν•œ λ²ˆμ— μ‹€ν–‰ν•˜κΈ° μ–΄λ ΅λ‹€.

μžλ°”λŠ” JUnitμ΄λΌλŠ” ν”„λ ˆμž„μ›Œν¬λ‘œ ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•΄ 이런 문제λ₯Ό ν•΄κ²°ν•œλ‹€.

  • ν…ŒμŠ€νŠΈ ν΄λž˜μŠ€λŠ” src/test/java/ν”„λ‘œμ νŠΈλͺ… κ²½λ‘œμ— λ™μΌν•œ νŒ¨ν‚€μ§€λ₯Ό λ§Œλ“€κ³  거기에 μž‘μ„±ν•œλ‹€.

  • ν…ŒμŠ€νŠΈ 클래슀의 이름은 관둀상 ν…ŒμŠ€νŠΈν•˜λ €λŠ” 클래슀 이름+Test 이닀.

    MemoryMemberRepository의 ν…ŒμŠ€νŠΈν΄λž˜μŠ€ 이름은 MemoryMemberRepositoryTestκ°€ λœλ‹€.

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.*;

public class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach() {
        repository.clearStore();
    }

    @Test
    public void save(){
        Member member = new Member();
        member.setName("spring");
        repository.save(member);

        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);

        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();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);

    }
}
  • ν…ŒμŠ€νŠΈ ν΄λž˜μŠ€λŠ” public일 ν•„μš”κ°€ μ—†λ‹€.
  • ν…ŒμŠ€νŠΈ ν΄λž˜μŠ€μ— ν…ŒμŠ€νŠΈλ₯Ό μ›ν•˜λŠ” 객체λ₯Ό μƒμ„±ν•˜κ³ , λ©”μ„œλ“œ μœ„μ— @Test μ–΄λ…Έν…Œμ΄μ…˜μ„ 뢙이면 ν•΄λ‹Ή λ©”μ„œλ“œλ₯Ό ν…ŒμŠ€νŠΈν•  수 있게 λœλ‹€.
    • import org.junit.jupiter.api.Test;κ°€ ν•„μš”ν•˜λ‹€.
  • ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ μ•ˆμ—μ„œ mainλ©”μ„œλ“œμ—μ„œ ν…ŒμŠ€νŠΈν•˜λ˜ 것과 같이 ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό 짜면 λœλ‹€.
  • ν…ŒμŠ€νŠΈ λ©”μ„œλ“œμ—μ„œ 검증을 ν•  λ•ŒλŠ” org.assertj.core.api.Assertions의 Assertions.assertThat(member).isEqualTo(result)와 같이 μ‚¬μš©.
    • import static org.assertj.core.api.Assertions.*; 둜 κ°€μ Έμ˜€λ©΄ Assertions 없이 μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.
  • 각 @Test의 μˆœμ„œλŠ” 보μž₯λ˜μ§€ μ•ŠλŠ”λ‹€.
    • ν…ŒμŠ€νŠΈλŠ” μ„œλ‘œ μ˜μ‘΄κ΄€κ³„ 없이 (μˆœμ„œμ— 관계없이) κ²°κ³Όκ°€ 보μž₯λ˜μ–΄μ•Ό 함.
    • @AfterEach : 각 ν…ŒμŠ€νŠΈλ©”μ„œλ“œκ°€ 끝날 λ•Œ λ§ˆλ‹€ μ‹€ν–‰ν•˜λŠ” λ©”μ„œλ“œλ₯Ό μ •μ˜ν•¨
  • ν…ŒμŠ€νŠΈ λ©”μ†Œλ“œμ˜ 이름은 ν•œκΈ€λ‘œ 적을 μˆ˜λ„ μžˆλ‹€. void νšŒμ›κ°€μž…() { ... } - μ’€ 더 직관적이기 λ•Œλ¬Έ

(μ°Έκ³ ) ν…ŒμŠ€νŠΈ 주도 개발(TDD)

: ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό λ¨Όμ € λ§Œλ“€κ³  κΈ°λŠ₯을 λ§Œλ“œλŠ” 것.

μš°λ¦¬λŠ” κΈ°λŠ₯ κ°œλ°œμ„ λ¨Όμ € ν•˜κ³  κ·Έ κΈ°λŠ₯을 ν…ŒμŠ€νŠΈν•˜κΈ° μœ„ν•΄ ν…ŒμŠ€νŠΈλ₯Ό λ§Œλ“€μ—ˆμ§€λ§Œ,

ν…ŒμŠ€νŠΈ 주도 κ°œλ°œμ—μ„œλŠ” ν…ŒμŠ€νŠΈλ₯Ό λ¨Όμ € λ§Œλ“€κ³  κ·Έ ν…ŒμŠ€νŠΈμ— λ§žλŠ” κΈ°λŠ₯을 κ°œλ°œν•œλ‹€.

규λͺ¨κ°€ 컀질수둝, ν˜‘μ—…μ„ ν• μˆ˜λ‘ ν…ŒμŠ€νŠΈμ˜ μ€‘μš”μ„±μ€ 컀진닀.

ν…ŒμŠ€νŠΈ μž‘μ„± μ—­μ‹œ κΌ­ 깊이 있게 곡뢀해야 ν•˜λŠ” 뢀뢄이닀.

νšŒμ› μ„œλΉ„μŠ€ 개발

도메인을 ν™œμš©ν•΄ μ‹€μ œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μž‘μ„±ν•œλ‹€.

src/main/java/ν”„λ‘œμ νŠΈλͺ…/service νŒ¨ν‚€μ§€λ₯Ό λ§Œλ“€κ³  MemberService.java 생성.

MemberRepository의 λ©”μ„œλ“œλͺ…은 클래슀 μ΄λ¦„λŒ€λ‘œ "μ €μž₯μ†Œμ— λ„£κ³  λΉΌλŠ” λŠλ‚Œ"이 λ“€μ–΄μ•Ό ν•œλ‹€. μ €μž₯μ†ŒλŠ” κ°œλ°œμžμ—κ²Œ 더 가깝기 λ•Œλ¬Έ.

MemberService의 λ©”μ„œλ“œλͺ…은 클래슀 μ΄λ¦„λŒ€λ‘œ "λΉ„μ¦ˆλ‹ˆμŠ€μ— 더 κ°€κΉŒμš΄ λŠλ‚Œ"이 λ“€μ–΄μ•Ό ν•œλ‹€. μ„œλΉ„μŠ€κ°€ 더 λΉ„μ¦ˆλ‹ˆμŠ€ 의쑴적이기 λ•Œλ¬Έ.

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);
    }
}
  • νšŒμ›κ°€μž…

    • 같은 이름이 μžˆλŠ” νšŒμ›μ€ κ°€μž…ν•  수 μ—†λ‹€

    • // 같은 μ΄λ¦„μ˜ νšŒμ› κ°€μž… 차단
      memberRepository.findByName(member.getName())
          .ifPresent(m -> { // 값이 있으면
              throw new IllegalStateException("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€.");
          });

      ifPresent() : Optional의 λ©”μ„œλ“œλ‘œ, μ–΄λ–€ 값이 μ‘΄μž¬ν•˜λ©΄ μ†Œκ΄„ν˜Έ λ‚΄μ˜ λ‘œμ§μ„ μˆ˜ν–‰ν•œλ‹€.

νšŒμ› μ„œλΉ„μŠ€ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ μž‘μ„±

ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜λ €λŠ” 클래슀 이름에 λŒ€κ³  Ctrl + Shift + Tλ₯Ό λˆ„λ₯΄λ©΄ νŽΈν•˜κ²Œ ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€λ₯Ό λ§Œλ“€ 수 μžˆλ‹€.

ν…ŒμŠ€νŠΈ : given - when - then 법

ν…ŒμŠ€νŠΈ λ©”μ†Œλ“œ 내에 //given, //when, //then을 μ‚¬μš©ν•˜λŠ” 기법이닀.

ν…ŒμŠ€νŠΈ λ©”μ†Œλ“œκ°€ κΈΈκ³  λ³΅μž‘ν•  λ•Œ 가독성을 λ†’μ—¬μ€€λ‹€.

''ν…ŒμŠ€νŠΈ''λΌλŠ” κ³Όμ • 자체λ₯Ό 생각해봀을 λ•Œ,

  • 무언가 주어지고 (given)
    • ν…ŒμŠ€νŠΈν•˜λŠ” 데이터λ₯Ό λ‚˜νƒ€λ‚Έλ‹€.
  • μ–΄λ–€ λ‘œμ§μ„ μ‹€ν–‰ν–ˆμ„ λ•Œ (when)
    • κ²€μ¦ν•˜λŠ” λ‘œμ§μ„ λ‚˜νƒ€λ‚Έλ‹€.
  • κ²°κ³Όκ°€ μ΄λŸ¬ν•΄μ•Ό ν•œλ‹€ (then)
    • 검증뢀λ₯Ό λ‚˜νƒ€λ‚Έλ‹€.

의 절차둜 κ²€μ¦ν•˜λŠ” 과정이기 λ•Œλ¬Έ.

ν…ŒμŠ€νŠΈλŠ” 정상flow보닀 μ˜ˆμ™Έflowκ°€ μ€‘μš”ν•˜λ‹€

'νšŒμ›κ°€μž…'μ΄λΌλŠ” λ‘œμ§μ„ 생각해 봀을 λ•Œ, νšŒμ›κ°€μž…μ΄ 잘 λ˜λŠ” 것도 μ€‘μš”ν•˜μ§€λ§Œ 쀑볡 νšŒμ›μΌ λ•Œ μ˜ˆμ™Έκ°€ λ°œμƒλ˜λŠ”μ§€ ν™•μΈν•˜λŠ” 게 μ€‘μš”ν•˜λ‹€.

  • try - catch 둜 μ˜ˆμ™Έκ°€ λ°œμƒν•˜λŠ”μ§€ μž‘μ•„λ‚΄λŠ” 방법.
    • μ„€μ •ν•œ μ˜ˆμ™Έλ₯Ό catch문에 μž‘μ„±ν•΄ μ˜ˆμ™Έκ°€ 잘 λ°œμƒν•˜λŠ”μ§€ 확인.
    • error.getMessage() : μ—λŸ¬ λ©”μ‹œμ§€μ˜ λ‚΄μš©μ„ κ°€μ Έμ˜€λŠ” λ©”μ„œλ“œ
    • fail() : ν…ŒμŠ€νŠΈμΌ€μ΄μŠ€κ°€ μ‹€νŒ¨ν–ˆμŒμ„ λ‚˜νƒ€λ‚΄λŠ” λ©”μ„œλ“œ
  • assertThrows(error.class, λžŒλ‹€μ‹ ) : λžŒλ‹€μ‹ 을 μ‹€ν–‰ν–ˆμ„ λ•Œ μ§€μ •λœerror κ°€ ν„°μ Έμ•Ό ν•œλ‹€. 그렇지 μ•ŠμœΌλ©΄ ν…ŒμŠ€νŠΈλŠ” μ‹€νŒ¨ν•œλ‹€.
package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    MemberService memberService = new MemberService();
    MemoryMemberRepository memberRepository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    void νšŒμ›κ°€μž…() {
        // given - ν…ŒμŠ€νŠΈν•  데이터
        Member member = new Member();
        member.setName("spring");
        // when - κ²€μ¦ν•˜λ €λŠ” 둜직 : MemberService의 join().
        Long saveId = memberService.join(member);

        // then - ν…ŒμŠ€νŠΈ κ²°κ³Ό (검증뢀)
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    void μ€‘λ³΅νšŒμ›μ˜ˆμ™Έ() {
        // given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        // when
        memberService.join(member1);
        /*
        try {
            memberService.join(member2); // μ—¬κΈ°μ„œ μ€‘λ³΅νšŒμ›μ„ κ°€λ €λ‚΄λŠ” μ˜ˆμ™Έκ°€ λ°œμƒν•΄μ•Ό.
            fail(); // μ—¬κΈ°κΉŒμ§€ λ„λ‹¬ν•˜λ©΄ ν…ŒμŠ€νŠΈλŠ” μ‹€νŒ¨μ΄λ‹€.
        } catch (IllegalStateException e) {
            assertThat(e.getMessage()).isEqualTo("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€.");
        }
         */

        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        // then
        assertThat(e.getMessage()).isEqualTo("이미 μ‘΄μž¬ν•˜λŠ” νšŒμ›μž…λ‹ˆλ‹€.");
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

DI : ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μœ„ν•΄ 클래슀 μΈμŠ€ν„΄μŠ€λ₯Ό μƒˆλ‘œ λ§Œλ“€μ§€ μ•Šκ³ ..

ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ§Œμ„ μœ„ν•΄ 클래슀 μΈμŠ€ν„΄μŠ€λ₯Ό μƒˆλ‘œ λ§Œλ“€λ©΄ κΈ°μ‘΄ ν΄λž˜μŠ€μ™€ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ΄λ―€λ‘œ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€.

  • ν•΄λ‹Ή 클래슀의 μΈμŠ€ν„΄μŠ€λ³€μˆ˜κ°€ λͺ¨λ‘ 항상 static일 μˆ˜λŠ” μ—†λ‹€.
  • 원본과 λ™μΌν•œ λŒ€μƒμ„ 가지고 ν…ŒμŠ€νŠΈν•΄μ•Ό ν•˜λŠ”λ°, λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ ν…ŒμŠ€νŠΈν•˜λŠ” 상황이 λœλ‹€.

βœ” ν•΄κ²° : 클래슀의 μΈμŠ€ν„΄μŠ€λ³€μˆ˜ 값을 new둜 μƒμ„±ν•˜μ§€ μ•Šκ³ , μƒμ„±μžλ₯Ό 톡해 값을 μ™ΈλΆ€μ—μ„œ 넣도둝 λ³€κ²½ν•œλ‹€. 이λ₯Ό DI (Dependency Injection, μ˜μ‘΄μ„± μ£Όμž…) 라고 ν•œλ‹€.

  • Before

    MemberService.java

    public class MemberService {
        private final MemberRepository memberRepository = new MemberRepository(); 
    }

    MemberServiceTest.java

    class MemberServiceTest {
        MemberService memberService = new MemberService();
        MemoryMemberRepository memberRepository = new MemoryMemberRepository();
    }
  • After

    MemberService.java

    public class MemberService {
        private final MemberRepository memberRepository;
    
        public MemberService(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
    }

    MemberServiceTest.java

    class MemberServiceTest {
        MemberService memberService;
        MemoryMemberRepository memberRepository;
    
        @BeforeEach
        public void beforeEach() {
            memberRepository = new MemoryMemberRepository();
            memberService = new MemberService(memberRepository);
        }
    }