본문 바로가기

개발일지/Spring

[Spring] 스프링 입문 - 스프링 통합 테스트, 스프링 JDBC Template

스프링 통합 테스트

이전에 했던 테스트는 순수한 자바 코드에 대한 테스트이다,

이제는 스프링과 엮어서 테스트를 해보자

 

 

Test 클래스의 어노테이션으로 SpringBootTest 선언

그리고, Transactional 도 선언

@SpringBootTest
@Transactional

 

이제 beforeEach는 지워도 된다!

왜?

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

이 코드는 직접 객체를 생성하지만

이제는? 스프링 컨테이너에서 멤버 리포지토리를 가져와야 한다

 

 

테스트는 테스트만 하면 되기 때문에, 다른 곳에서 사용하지 않을 것이다

편하게 @Autowired 를 사용하자

 

그리고, MemoryMemberRepository가 아닌 MemberRepository를 선언

@Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

 

AfterEach 어노테이션까지 삭제하자!

왜?

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

테스트끼리 메모리 DB의 영향을 끼칠 수 있어서, 저장했던 것을 지워주는 코드인데

@Transational 어노테이션으로 인해 필요 없어짐

 

 

  • 데이터베이스에는 기본적으로 Transation이라는 개념이 있는데, DB의 데이터에서 커밋을 해줘야, 반영이 된다
  • 자동으로 커밋을 하는지 안하는지 에 대한 차이
  • 커밋하기 전엔 반영이 안됨
  • 이 어노테이션을 사용해서, 테스트 케이스에 달면, 테스트를 실행할 때 Transation을 먼저 실행한다
  • DB의 데이터를 insert query한 후, 테스트가 끝나면 롤백한다 → DB의 데이터가 반영이 안되고 지워진다

 

Example

테스트 실행 전에 Transation을 걸고 DB에 쿼리를 날린다.

그 다음에 테스트가 끝나면, 데이터를 지워버리고 반영을 안한다

→ 다음 테스트를 반복해서 실행할 수 있다

 

 

 

테스트는 테스트 전용 DB를 따로 만들어서 테스트 하던가

로컬 PC의 DB에서 테스트를 하던가 해서 안전하다

 

 

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

@Transactional : 테스트 케이스에 해당 어노테이션이 있으면 테스트 시작 전에 트랜잭션을 시작한다, 그리고 테스트 완료 후 항상 롤백한다 → DB에 데이터가 남지 않아서, 다음 테스트에 영향을 주지 않는다

 

 

Transactional이 서비스에 붙으면 롤백하지 않고 정상적으로 작동, 테스트 케이스에 붙었을 때만 롤백

 

 

좋은 테스트 케이스란.. 고민해보자

  • 단위로 잘게 쪼갠 테스트
  • 스프링 컨테이너 없이 테스트

 

컨테이너까지 어쩔 수 없게 올려야 하면 설계가 잘못되어있을 확률이 높다

진짜 좋은 테스트는 단위 테스트를 잘 만드는 것…

 

 

스프링 JDBC Template

JDBC API에서의 반복 코드를 대부분 제거해준다 !

대신, SQL은 직접 작성해야 한다

 

 

JdbcTemplateMemberRepository 클래스 만들기

public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate jdbcTemplate;

    public JdbcTemplateMemberRepository(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        return null;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.empty();
    }

    @Override
    public Optional<Member> findByName(String name) {
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        return null;
    }
}

일단 JdbcTemplate 생성

💡 생성자가 하나 일 때,
JdbcTemplateMemberRepository의 어노테이션으로 @Autowired를 생략할 수 있다

 

조회하는 Query 만들기

 

1. RowMapper를 설정해주자!

private RowMapper<Member> memberRowMapper() {
        return (rs, rowNum) -> {
            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        };
    }

 

2. 조회 Query를 Optional로 반환하자!

@Override
    public Optional<Member> findById(Long id) {
        List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper());
        return result.stream().findAny();
    }

memberRowMapper를 통해서 매핑하고,

리스트로 받아서,

Optional로 반환

 

저장하는 Query 만들기

@Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");

        Map<String, Objects> parametrers = new HashMap<>();
        parametrers.put("name", member.getName());

        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parametrers));
        member.setId(key.longValue());
        return member;
    }

 

 

@참고자료

[인프런] 김영한 - 스프링 입문 강의 (무료)