테스트 코드 작성

강한친구·2022년 3월 28일
0

Spring 입문

목록 보기
5/10

그 전에

테스트 코드를 작성해보기전에, 우선 진짜 코드들을 좀 살펴봐야한다.

domain

Member

package com.example.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;
    }
}

DB에 저장활 Member 객체를 생성하는 역할을 한다.

repository

MemberRepository

package com.example.hellospring.repository;

import com.example.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();

}

MemberRepository의 기능을 정의하는 인터페이스이다.
Spring 도메인의 Member 객체를 import 해서 사용하는것을 볼 수 있다.

Optional은 Null값을 처리할 수 있는 기능을 가지고있다.

MemoryMemberRepository

package com.example.hellospring.repository;


import com.example.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();
    }
}

인터페이스에서 선언했던 기능들을 상세히 구현하는 과정이다.
implements를 통해 인터페이스를 받아오고, override를 통해 그 내용들을 정의한다.

service

MemberService

package com.example.hellospring.service;

import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemberRepository;

import java.util.List;
import java.util.Optional;

public class MemberService {
    private final MemberRepository memberRepository;

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


    /**
     * 회원 가입
     */
    public Long join(Member member) {
        // 중복 회원 거부
        validateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    private void validateMember(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);
    }

}

비즈니스 레벨의 서비스를 구현한다.

테스트 코드 작성

Repositroy, Service처럼 실제로 작동하는 부분에 대해서는 테스트코드를 작성해서 정상작동중인지 확인해야 할 필요가 있다.
ctrl + alt + shift + t 를 이용하면 test를 바로 만들 수 있다. 이 기능을 이용하면 test 폴더 아래에 동일한 이름으로 테스트 파일을 바로 만들어준다.

repository 테스트

package com.example.hellospring.repository;

import com.example.hellospring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

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

annotation aftereach는 각 테스트가 끝난 후 리포지토리를 비워서 입력한 member 객체들을 전부 지워준다는 의미이다.
이 과정을 거쳐야지만 전체 test 결과가 깔끔하게 나온다.

Service Test

package com.example.hellospring.service;

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


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("Spring");
        //when
        Long saveID = memberService.join(member);
        //then
        Member findMember = memberService.findOne(saveID).get();
        Assertions.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);
        
        //then
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다. ");

//        try {
//            memberService.join(member2);
//            fail();
//        } catch (IllegalStateException e) {
//            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다. ");
//        }

        //then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}

서비스쪽 테스트 코드이다.

gwt 테크닉

테스트코드는 대부분
given 상황에서 when 하면 then 결과가 나오는 구조이다. 따라서 해당 구조를 취해서 코드를 작성해보면 좋다.

repository 공유

각 테스트 코드마다

MemoryMemberRepository memberRepository = new ...

을 선언해서 사용해도 물론 테스트 코드는 잘 작동한다.
하지만 실제로 이렇게 코딩을 하게 되면, service 코드 테스트와 repository 코드 테스트는 서로 다른 db를 참조하면서 테스트를 하게 된다.

이를 방지하기 위해서 우선 MemberService에서 참조하는 repository를 외부에서 받도록 해준다.

    private final MemberRepository memberRepository;

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

그 후, 테스트 코드에서 MemoryMemberRepository를 직접 MemberService에 넣어줄 수 있다.

  MemberService memberService; // 멤버서비스 선언 
  MemoryMemberRepository memberRepository; // 멤버리포지토리 선언

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository(); // 각 테스트 실행전에 리포지토리를 생성하고 넣어준다.
        memberService = new MemberService(memberRepository);
    }

이러한 것들을 Dependency Injection 이라고한다.

0개의 댓글

관련 채용 정보