ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 통합 테스트 / 스프링 JdbcTemplate
    [인프런] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB/섹션 6. 스프링 DB 접근 기술 2023. 3. 31. 11:46

    * 인프런 김영한님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 듣고 작성한 내용입니다.

      (강의 링크)

    스프링 통합 테스트

    테스트도 DB까지 연결해서 동작하는 통합 테스트 진행하기

     

    지금까지의 테스트 코드 → 스프링과 관련이 없는 순수한 자바 코드만 테스트했던 것

    지금부터는 스프링과 엮어서 테스트하기

     

    MemberServiceIntegrationTest.java - 회원가입

    이미 존재하는 회원입니다 에러 → 테스트 전 DB 내용 모두 지우기

    실무에서는 운영DB가 아니라 테스트 전용 DB를 따로 구축함!

    @Transactional 주석처리 하고 돌려보기

    DB에 테스트한 내용 그대로 남아있게 됨 → 다음 테스트 때 다시 이미 존재하는 회원이라 오류 뜸

    테스트는 계속 반복할 수 있어야 한다!

     

    @SpringBootTest : 스프링 컨테이너와 테스트를 함께 실행한다.

    @Transactional : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.

    (서비스에 붙으면 롤백하지 않고 정상적으로 동작하고 테스트에 붙는 경우만 롤백)

     

    스프링 없이 하는 순수한 테스트는 그럼 필요없지 않나요?

    → 가급적이면 순수한 단위 테스트가 훨씬 좋은 테스트일 확률이 높다. 스프링 컨테이너 없이 하는 테스트를 할 수 있도록 설계하는 것이 필요!

     

    MemberServiceIntegrationTest.java

    package hello.hellospring.service;
    import hello.hellospring.domain.Member;
    import hello.hellospring.repository.MemberRepository;
    import hello.hellospring.repository.MemoryMemberRepository;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.transaction.annotation.Transactional;
    
    import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    
    @SpringBootTest
    @Transactional
    class MemberServiceIntegrationTest {
    
        @Autowired MemberService memberService;
        @Autowired MemberRepository memberRepository;
    
        @Test
        void 회원가입() {
            //given
            Member member = new Member();
            member.setName("spring");
    
            //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"); // 이름이 똑같은 Member를 2개 만듦
    
            //when
            memberService.join(member1); // 첫 번째 join에서는 문제가 없음
            IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
            // 이 로직 실행 시 이 예외가 터져야 함
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
        }
    
        @Test
        void findMembers() {
        }
    
        @Test
        void findOne() {
        }
    }

     

    스프링 JdbcTemplate

    • 순수 Jdbc와 동일한 환경설정을 하면 된다.
    • 스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해준다. 하지만 SQL은 직접 작성해야 한다.
    • 실무에서도 많이 사용

    repository-JdbcTemplateMemberRepository.java

    package hello.hellospring.repository;
    
    import hello.hellospring.domain.Member;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
    import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
    
    import javax.sql.DataSource;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    
    public class JdbcTemplateMemberRepository implements MemberRepository{
        private final JdbcTemplate jdbcTemplate;
    
        @Autowired // 생성자가 딱 하나일 경우에는 @Autowired 생략 가능
        public JdbcTemplateMemberRepository(DataSource dataSource){
            this.jdbcTemplate = new JdbcTemplate(dataSource);
        }
    
        @Override
        public Member save(Member member) {
            SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
            jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
    
            Map<String, Object> parameters = new HashMap<>();
            parameters.put("name", member.getName());
    
            Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
            member.setId(key.longValue());
            return member;
        }
    
        @Override
        public Optional<Member> findById(Long id) {
            List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id); // 결과 나오는 것 RowMapper로 매핑
            return result.stream().findAny();
        }
    
        @Override
        public Optional<Member> findByName(String name) {
            List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name); // 결과 나오는 것 RowMapper로 매핑
            return result.stream().findAny();
        }
    
        @Override
        public List<Member> findAll() {
            return jdbcTemplate.query("select * from member", memberRowMapper()); // 결과 나오는 것 RowMapper로 매핑
        }
    
        private RowMapper<Member> memberRowMapper(){
            return (rs, rowNum) -> {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return member;
            };
        }
    }
    

    SpringConfig.java

    return new JdbcMemberRepository(dataSource); 에서 return new JdbcTemplateMemberRepository(dataSource);

    바꾸어 조립

    이제는 통합 테스트 작성해두었으므로 웹 어플리케이션 테스트 없이 바로 테스트 가능

    테스트는 실무에서 정말 중요하다!

     

     

     

Designed by Tistory.