https://developer.mozilla.org/ko/docs/Web/API/Document/visibilityState#%EB%AA%85%EC%84%B8%EC%84%9C

 

Document.visibilityState - Web API | MDN

Document.visibilityState 읽기 전용 property로, 이 element가 현재 표시된 컨텍스트를 나타내는 document의 가시성을 반환합니다. document가 background 또는 보이지 않는 탭(다른 탭)에 있는지, 또는 pre-rendering을

developer.mozilla.org

document.addEventListener("visibilitychange", () => {
  console.log(document.visibilityState);
  // 기타 동작...
});

위 이벤트를 이용해서 브라우저가 "hidden" or "visible" 되었는지 확인 가능 (상태가 변경될때마다 감지된다)

 

포커스로 확인하는 코드는 아래와 같다.

window.addEventListener('focus', function() {
    console.log('사용자가 웹페이지에 돌아왔습니다.')
}, false);​

 

AOP가 필요한 상황

모든 메소드의 호출 시간을 측정하고 싶을 때

 

핵심관심사항: 서비스 로직

공통관심사항: 각 서비스 로직 메소드가 얼마나 걸리는지 구한다.

 

직접 구하는 방법

    public Long join(Member member) {
//      메소드 수행시간을 구한다는 과정에서의 코드
        long start = System.currentTimeMillis();

        try {
            // 중복회원 검증
            validateDuplicateMember(member);

            memberRepository.save(member);
            return member.getId();
        } finally {
            long finish = System.currentTimeMillis();
            long times = finish - start;

            System.out.println("join = " + times + "ms");
        }

    }

 

문제점

  • 회원가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심 사항이 아니다.
  • 시간을 측정하는 로직은 공통 관심 사항이다.
  • 시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵다.
  • 시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.
  • 시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다.

AOP는 공통관심사항과 핵심관심사항을 분리하는 것이다. (관점지향 프로그래밍)

원하는 곳에 공통 관심 사항을 적용 시키는 방법 이다.

 

AOP 패키지를 생성

@Aspect 어노테이션을 상단에 입력하여 해당 클래스가 AOP인것을 명시해 준다.

package hello.hellospring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
// @Component: 해당 클래스를 빈등록 하는 간단한 방법 -> 서비스 패키지의 SpringConfig에 작성하여도 된다.
//@Component
public class TimeTraceAop {

//  해당 AOP을 어느 부분에 적용할지 선택 -> 여기서는 hello.hellospring 모든 메소드
//  보통 패키지 레밸로 적용하고 원할 경우 검색해서 사용법을 검색해서 사용
//    @Around("execution(* hello.hellospring.service..*(..))")
//    @Around("execution(* hello.hellospring..*(..))")

//    SpringConfig를 통해서 빈 등록을 할 경우 순환참조문제가 발생하여 아래와 같이 AOP대상에서 SpringConfig를 제외시켜준다.
//    @Around("execution(* hello.hellospring..*(..)) && !target(hello.hellospring.service.SpringConfig)")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

//      현재 진행중인 메서드를 확인
        System.out.println("START: " + joinPoint.toString());
        try {
//          다음 메서드로 진행
            Object result = joinPoint.proceed();
            return result;
            
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
        }
    }
}

 

joinPoint.proceed():  프록시 메서드에서 진짜메서드로 진행하도록 하는 명령어

joinPoint.toString():  진행하는 메서드가 무엇인지 확인하는 명령어

@Around 어노테이션을 이용하여 AOP의 범위를 지정할 수 있다.

SpringConfig클래스를 이용하여 빈 등록을 할 경우 순환참조 에러를 피하기 위해서 SpringConfig 클래스는 범위에서 제외 시켜준다.

 

SpringConfig클래스에서 빈 등록

    //  AOP 빈 등록
//  아래 TimeTraceAop을 생성하는 코드도 AOP의 대상으로 지정되기 때문에 @Around어노테이션에서 SpringConfig클래스를 범위에서 제외 시켜줘야 한다.
    @Bean
    public TimeTraceAop timeTraceAop() {
        return new TimeTraceAop();
    }

 

AOP를 적용하면 해당 범위를 입력하면 프록시(해당 가짜 스프링 빈을 만들고) 기술을 이용해서 먼저 거친다음에 진짜 클래스가 동작한다.

MemberController 클래스에서 확인 가능

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
//      AOP 프록시 클래스 생성 확인 
//      memberService: class hello.hellospring.service.MemberService$$EnhancerBySpringCGLIB$$c70ac3a1
        System.out.println("memberService: " + memberService.getClass());
    }

실행하여 콘솔 확인

 

'코드로 배우는 스프링 부트 - 인프런' 카테고리의 다른 글

스프링 데이터 JPA  (0) 2022.08.23
JPA  (0) 2022.08.22
스프링 JDBC Template  (0) 2022.08.19
스프링 통합 테스트  (0) 2022.08.18
순수 JDBC  (0) 2022.08.17

스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술이기 때문에 먼저 JPA를 먼저 익혀야 한다.

 

앞의 JPA 설정을 그대로 사용한다.

 

SpringDataJpaMemberRepository 인터페이스 생성

인터페이스는 다중 상속이 가능하다.

스프링 데이터 JPA사용을 위해서 JpaRepository<Entity 클래스, PK 타입> 를 상속 받는다.

JpaRepository를 사용하면 스프링 데이터 JPA가 인터페이스에 대한 구현체를 만들어서 자동으로 빈 등록 해준다.

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

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

// 인터페이스가 인터페이스를 상속받을 때는 extends
// JpaRepository<Entity 클래스, PK 타입>
public interface SpringDataJpaMemberRepository extends JpaRepository<Member,Long>, MemberRepository{

//  select m from Member m where m.name = ?
    @Override
    Optional<Member> findByName(String name);
}

 

SpringConfig 클래스 수정

package hello.hellospring.service;

import hello.hellospring.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;
import javax.xml.crypto.Data;

// 자바코드로 직접 스프링 빈 등록하는 방법
// 스프링 실행할때 자동 실행
@Configuration
public class SpringConfig {

//    JDBC 사용을 위해 DataSource을 주입 받는다.
//    private DataSource dataSource;
//
//    @Autowired
//    public SpringConfig(DataSource dataSource){
//        this.dataSource = dataSource;
//    }

//  JPA사용을 위해 EntityManager을 주입 받는다.
//    private EntityManager em;
//
//    @Autowired
//    public SpringConfig(EntityManager em) {
//        this.em = em;
//    }

//  스프링 데이터 JPA 사용을 위해 의존성 주입
    private final MemberRepository memberRepository;

    @Autowired
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    //  스프링 빈 등록
    @Bean
    public MemberService memberService() {
        // @Autowired와 같이 동작
//        return new MemberService(memberRepository());

//      스프링 데이터 JPA 사용
        return new MemberService(memberRepository);
    }


//    @Bean
//    public MemberRepository memberRepository() {
//        // 구현체를 리턴해줌
////        return new MemoryMemberRepository();
////      JDBC 연결로 변경경
////       return new JdbcMemberRepository(dataSource);
////      JDBCTemplate 연결로 변경
////        return new JdbcTemplateMemberRepository(dataSource);
////        JPA 사용
////        return new JpaMemberRepository(em);
//    }
}

 

JPQL = JPA를 이용하여 쿼리를 생성하여 돌릴 때 만들어지는 SQL -> 테이블이 아닌 객체를 검색하는 객체지향 쿼리

'코드로 배우는 스프링 부트 - 인프런' 카테고리의 다른 글

AOP  (0) 2022.08.24
JPA  (0) 2022.08.22
스프링 JDBC Template  (0) 2022.08.19
스프링 통합 테스트  (0) 2022.08.18
순수 JDBC  (0) 2022.08.17

JPA는 기존의 반복코드와 SQL문을 직접 만들어준다.

JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계를 할 수 있다.

 

build.gradle 디펜던시 수정 - jdbc 주석 처리 and jpa 추가

//  implementation 'org.springframework.boot:spring-boot-starter-jdbc'
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

application.properties 수정

# jpa 작성된 쿼리를 확인 할 수 있도록 설정
spring.jpa.show-sql=true
# 자동 테이블 생성기능 끄기
spring.jpa.hibernate.ddl-auto=none

 

jpa는 인터페이스이고 hibernate는 구현체라고 생각하면 된다.

 

Member 클래스를 JPA와 연동 되도록 코드 수정

package hello.hellospring.domain;

import javax.persistence.*;

// @Entity: jpa가 관리하는 객체라고 선언
@Entity
public class Member {

//  @Id: PK 맵핑 / @GeneratedValue: ID가 자동으로 생성되도록 설정
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

//  테이블의 name 컬럼 맵핑 설정
    @Column(name = "name")
    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;
    }
}

 

JpaMemberRepository 생성

jpa를 사용하기 위해서는 반드시 EntityManager를 주입받아서 사용하여야 한다.

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.List;
import java.util.Optional;

public class JpaMemberRepository implements MemberRepository{

//  jpa를 사용할려면 EntityManager를 주입 받아야 한다.
    private final EntityManager em;

    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        // em.find(조회 타입, 식별자);
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
//      em.createQuery("실행할 쿼리", 반환할 타입)
        List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
//              파라미터값 선언
                .setParameter("name", name)
//              리스트 형태로 반환
                .getResultList();
//      Optional로 처리하기 때문에 아래와 같이 반환 stream().findAny(): 첫번째 찾은 데이터를 반환(순서가 중요하지 않을때 사용)
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
//      em.createQuery("실행할 쿼리", 반환할 타입)
        List<Member> result = em.createQuery("select m from Member m", Member.class)
                .getResultList();
        return result;
    }

    @Override
    public void clearStore() {

    }
}

 

jpa의 기능은 항상 트랜잭션기능 안에서 동작되어야 한다.

따라서 각 기능을 구현한 MemberService 클래스에 @Transactional 어노테이션 선언

 

SpringConfig 클래스에서 jpa기능을 사용하도록 코드 수정

package hello.hellospring.service;

import hello.hellospring.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;
import javax.xml.crypto.Data;

// 자바코드로 직접 스프링 빈 등록하는 방법
// 스프링 실행할때 자동 실행
@Configuration
public class SpringConfig {

//    JDBC 사용을 위해 DataSource을 주입 받는다. 
//    private DataSource dataSource;
//
//    @Autowired
//    public SpringConfig(DataSource dataSource){
//        this.dataSource = dataSource;
//    }
    
//  JPA사용을 위해 EntityManager을 주입 받는다. 
    private EntityManager em;

    @Autowired
    public SpringConfig(EntityManager em) {
        this.em = em;
    }

    //  스프링 빈 등록
    @Bean
    public MemberService memberService() {
        // @Autowired와 같이 동작
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        // 구현체를 리턴해줌
//        return new MemoryMemberRepository();
//      JDBC 연결로 변경경
//       return new JdbcMemberRepository(dataSource);
//      JDBCTemplate 연결로 변경
//        return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }
}

 

테스트 진행

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.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;

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

// 테스트는 한글로 바꾸어도 된다.
// 빌드될때 테스트코드는 포함되지 않는다.
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {

//  컨테이너로 부터 주입받는다.
//  테스트를 진행할 때는 편하게 @Autowired 사용하면 된다. -> 필드 주입 방법(DI)
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;


    @Test
//  @Commit: 실제로 데이터가 저장되도록 선언
    @Commit
    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");

        // when
        memberService.join(member1);
        // member2를 join 할 때 IllegalStateException이 발생하것을 예상하는 코드 
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        assertThrows(NullPointerException.class,() -> memberService.join(member2));

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

}

 

'코드로 배우는 스프링 부트 - 인프런' 카테고리의 다른 글

AOP  (0) 2022.08.24
스프링 데이터 JPA  (0) 2022.08.23
스프링 JDBC Template  (0) 2022.08.19
스프링 통합 테스트  (0) 2022.08.18
순수 JDBC  (0) 2022.08.17

환경설정은 순수 JDBC 설정과 동일하게 하면 된다.

 

순수 JDBC에서 작성한 반복 코드를 제거해준다.

실무에서 많이 사용된다고 한다.

 

공식 문서

https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbc-JdbcTemplate

 

Data Access

The Data Access Object (DAO) support in Spring is aimed at making it easy to work with data access technologies (such as JDBC, Hibernate, or JPA) in a consistent way. This lets you switch between the aforementioned persistence technologies fairly easily, a

docs.spring.io

 

참고 블로그

https://velog.io/@seculoper235/JDBC-5%ED%8E%B8-jdbcTemplate

 

JdbcTemplate이란? 1편

jdbc+Template 즉, 직역하면 "jdbc를 위한 틀"이라는 뜻이다.그만큼 jdbc 프로그래밍에 특화되어 있다는 것 일텐데, 어떤 점이 더 좋다는 걸까? 한번 알아보자기존의 방식과 비교해서 알아보자.// SELECT

velog.io

JdbcTemplateMemberRepository 클래스 작성(JDBC템플릿 사용을 위해서)

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;

//   생성자가 하나일 경우에는 DI를 위해서 @Autowired 선언을 안해줘도 된다
//    @Autowired
    public JdbcTemplateMemberRepository(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
//      테이블명(member), PK(id) 알려주면 sql 작성을 안해도 된다.
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", member.getName());
// executeAndReturnKey: 작업 수행과 동시에 자동 생성된 PK(auto_increment)를 반환한다.
        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        member.setId(key.longValue());
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
//      사용법: jdbcTemplate.query("쿼리문", RowMapper(), 쿼리의 ? 파라미터 값)
        List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(),id);
        return result.stream().findAny();
    }

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

    @Override
    public List<Member> findAll() {
        return jdbcTemplate.query("select * from member", memberRowMapper());
    }

    @Override
    public void clearStore() {

    }

//  결과를 받아오기 위한 함수
    private RowMapper<Member> memberRowMapper() {
        return new RowMapper<Member>() {
            @Override
            public Member mapRow(ResultSet rs, int rowNum) throws SQLException {

                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return member;
            }
        };
    }
}

SpringConfig 클래스 수정 -> JDBC템플릿을 사용하는 것으로 설정 변경

package hello.hellospring.service;

import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import javax.xml.crypto.Data;

// 자바코드로 직접 스프링 빈 등록하는 방법
// 스프링 실행할때 자동 실행
@Configuration
public class SpringConfig {


    private DataSource dataSource;

    @Autowired
    public SpringConfig(DataSource dataSource){
        this.dataSource = dataSource;
    }
//  스프링 빈 등록
    @Bean
    public MemberService memberService() {
        // @Autowired와 같이 동작
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        // 구현체를 리턴해줌
//        return new MemoryMemberRepository();
//      JDBC 연결로 변경경
//       return new JdbcMemberRepository(dataSource);
//      JDBCTemplate 연결로 변경
        return new JdbcTemplateMemberRepository(dataSource);
    }
}

 

MemberServiceIntegrationTest -> 기존에 작성한 테스트 코드를 이용하여 테스트 진행

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.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

// 테스트는 한글로 바꾸어도 된다.
// 빌드될때 테스트코드는 포함되지 않는다.
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {

//  컨테이너로 부터 주입받는다.
//  테스트를 진행할 때는 편하게 @Autowired 사용하면 된다. -> 필드 주입 방법(DI)
    @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");

        // when
        memberService.join(member1);
        // member2를 join 할 때 IllegalStateException이 발생하것을 예상하는 코드 
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        assertThrows(NullPointerException.class,() -> memberService.join(member2));

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

}

'코드로 배우는 스프링 부트 - 인프런' 카테고리의 다른 글

스프링 데이터 JPA  (0) 2022.08.23
JPA  (0) 2022.08.22
스프링 통합 테스트  (0) 2022.08.18
순수 JDBC  (0) 2022.08.17
H2 데이터베이스 설치  (0) 2022.08.16

JDBC를 이용하여 스프링과 DB를 연결한 후 테스트를 진행하는 코드를 작성한다.

 

@Transactional: DB에서는 기본적으로 커밋을 해야 데이터가 최종적으로 저장되는데, 해당 어노테이션을 선언하면 자동적으로 롤백을 해주어서 테스트 데이터가 최종적으로 저장되지 않도록 해준다.

따라서, 반복적으로 같은 데이터로 테스트를 진행할 수 있다.

 

@SpringBootTest: 스프링 컨테이너와 테스트를 함께 실행한다.-> 스프링이 실제로 구동된다.

 

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.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

// 테스트는 한글로 바꾸어도 된다.
// 빌드될때 테스트코드는 포함되지 않는다.
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {

//  컨테이너로 부터 주입받는다.
//  테스트를 진행할 때는 편하게 @Autowired 사용하면 된다. -> 필드 주입 방법(DI)
    @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");

        // when
        memberService.join(member1);
        // member2를 join 할 때 IllegalStateException이 발생하것을 예상하는 코드 
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        assertThrows(NullPointerException.class,() -> memberService.join(member2));

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

}

순수한 단위 테스트는 스프링 컨테이너 없이 하는 순수한 자바코드로 되어진 각각의 기능을 테스트하는 것이다. 

단위테스트가 실행속도가 빠르고 최소 단위로 진행하는 테스트이기 때문에 정말 중요하다고 한다.

 

통합테스트는 스프링 컨테이너와 DB까지 함께 테스트를 진행하는 것을 말한다.

 

테스트에서의 주의사항은 반드시 테스트용 DB를 이용하거나 로컬에서만 사용하는 DB를 이용해야한다.

운영중인 DB를 바라보게하고 테스트를 진행하면 안된다.

'코드로 배우는 스프링 부트 - 인프런' 카테고리의 다른 글

JPA  (0) 2022.08.22
스프링 JDBC Template  (0) 2022.08.19
순수 JDBC  (0) 2022.08.17
H2 데이터베이스 설치  (0) 2022.08.16
회원 웹 기능 - 조회  (0) 2022.08.15

+ Recent posts