나중에 따로 내용 정리

이제까지 진행했던 테스트는 스프링 컨테이너의 간섭을 받지 않고 순수 자바 코드로만 동작하는 '단일 테스트'였다.

스프링 컨테이너와 함께 테스트를 실행하는 '통합 테스트'도 가능하다.

 

@SpringBootTest 를 통해 간단하게 처리 가능.

 

스프링 통합 테스트는 단일 테스트에 비해 시간이 오래 걸린다. 테스트 케이스가 많아질수록 그 차이는 커진다. 따라서 스프링에 의존하지 않는 단일 테스트 케이스를 만드는 능력이 중요하다.

 

스프링 통합 테스트를 진행할 때 @Transactional 을 통해 @AfterEach를 대체할 수 있다.

스프링과 직접 연동되는 테스트이기 때문에 테스트로 인해 디비에 데이터가 저장된 후 다시 테스트를 돌렸을 때 데이터 중복에 의해 오류가 발생할 수 있다. (사실 이건 굳이 통합 테스트가 아니어도 마찬가지)

 

@Transactional 은 각 테스트 케이스가 실행하기 전 트랜잭션을 생성하고 테스트가 종료되면 해당 트랜잭션을 롤백함으로써 데이터 @AfterEach의 기능을 대신할 수 있다. 

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

 

 

생략

컴포넌트 스캔 방식 외에 Bean을 등록하는 로직을 자바 코드로 직접 구현하는 방식도 있다.

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

@Configuration 어노테이션을 통해 스프링 컨테이너에 등록할 Bean들을 직접 등록할 수 있다.

단, Controller는 반드시 컴포넌트 스캔 방식을 사용해야한다.

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

일단 실행이 되면 스프링 컨테이너는 @SpringBootApplication이 달린 경로부터 시작으로 모든 하위경로를 검색한다.

 

이떄 @Component 어노테이션을 달고있는 객체들은 모두 자동으로 생성되어 스프링 컨테이너에 보관된다. 이러한 방식을 컴포턴트 스캔이라고 한다.

(기본적으로 싱글톤 방식을 사용해 각 컴포넌트 객체는 하나씩만 존재한다.)

 

@Controller, @Service, @Repository 모두 내부적으로 @Component를 포함하고 있다.

 

 

A 클래스가 B 클래스의 객체를 멤버로 사용할 경우 A가 B에 의존성을 갖고 있다고 표현한다.

이때 A 내부에서 B 객체를 직접 생성하는 방식으로 사용하지 않는다.

@Autowired를 사용해 생성자에서 객체를 전달받는데, 이를 의존성 주입(DI : Dependency Injection) 중에서도 생성자 주입이라고 한다.

주의해야할 점은 의존성이 엮여있는 모든 클래스를 전부 다 처리해줘야 제대로 동작한다는 것이다.

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

Service는 Repository(= DAO)를 통해 기능을 수행한다.

Repository객체를 직접 생성하지 않고 생성자를 통해 받아 사용하는 것이 좋다. (= 생산성 주입)

따로 사용할 경우 ServiceTest에서 사용하는 Repository와 다른 Repository를 사용하게 되어 테스트에 문제가 생길 수 있다.

private final MemberRepository memberRepository;

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

 

함수 추출 (Ctrl + Alt + M)

지정한 로직을 함수로 추출해낸다. 해당 로직은 추출된 함수의 호출로 대체된다.

ex, 사용 전

    // 회원 가입
    public Long join(Member member) {
        // 같은 이름이 있는 중복 회원 X
        memberRepository.findByName(member.getName())
                // ifPresent() : Optional의 기능. 담긴 객체가 null이 아닐 경우 호출된다.
                .ifPresent(m -> {  
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });

        memberRepository.save(member);
        return member.getId();
    }

 

ex, 사용 후

	// 회원 가입
    public Long join(Member member) {
        // 같은 이름이 있는 중복 회원 X
        validateDuplicateMember(member);

        memberRepository.save(member);
        return member.getId();
    }

    // 중복 회원 검증
    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName())
        		// ifPresent() : Optional의 기능. 담긴 객체가 null이 아닐 경우 호출된다.
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

 

 

 

assertThat().isEqualTo()를 사용하기 위한 Assertions는 

import static org.junit.jupiter.api.Assertions.*; 이 아니라 

import static org.assertj.core.api.Assertions.*; 이다.

 

 

JUnit 테스트를 위한 @BeforeEach, @AfterEach

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);  // 생성자 주입
    }

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

 

 

Test 클래스는 일일히 따로 만들지 않아도 된다.

Test하려는 클래스에 커서를 두고 Ctrl + Shift + T 를 통해 자동으로 생성할 수 있다. (패키지까지 자동 생성된다.)

 

Test 함수의 로직은 given, when, then 순으로 작성하는 것이 좋다. Test 함수명은 한글로 써도 무방하다.

    @Test
    void 회원가입() {
        // given : 주어진 것들로
        Member member = new Member();
        member.setName("hello");

        // when : 이걸 실행했을때
        Long saveId = memberService.join(member);
        
        // then : 이런 결과가 나와야 됨
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

 

 

에러 처리를 위해 try catch 대신 다른 방법을 쓸 수 있다.

// when
memberService.join(member1);

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

// 방법 2
// 오른쪽 파라미터의 로직을 실행했을때 왼쪽 파라미터의 에러를 반환
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

기본적인 예제를 위한 도메인, 레포지터리를 만든다.

도메인 : 비즈니스의 메인 객체 (ex, 회원, 주문 등). VO객체와 다른 것인지는 답변 보고 다시 수정

레포지터리 : 디비에 접근하여 도메인 객체를 관리. DAO객체와 다른 것인지는 답변 보고 다시 수정

 

도메인 객체는 VO와 똑같이 만들었다.

레포지터리도 DAO와 똑같이 만들었다. (단, 디비는 사용하지 않고 메모리에 저장하는 식으로 만듦. 나중엔 디비까지 사용할듯)

 

test패키지(?)에 repository 패키지를 따로 만들었다.

이 안에 JUnit 테스트 실행할 RepositoryTest 클래스 생성

 

Repository 클래스에 있는 모든 함수에 대한 테스트함수를 하나씩 만든다.

모든 테스트 함수는 @Test 어노테이션으로 표시한다. 

테스트함수는 함수 개별적으로도 클래스 전체적으로도 테스트가 가능하다.

테스트함수에서 Assertions.assertThan(o1).isEqualTo(o2) 를 적극적으로 사용한다. 예상 결과와 실제 결과가 일치하는지 확인하기 위함.

static import를 통해 assertThat() 바로 호출도 가능하다. (Alt + Enter -> static import) 

 

클래스 전체적으로 테스트를 수행할 경우 테스트 함수들의 호출 순서는 보장되지 않는다. 따라서 메모리가 겹쳐 잘못된 결과를 초래할 수 있다. 이를 막기 위해 @AfterEach를 사용한다.

 

@AfterEach는 각각의 테스트함수가 실행된 뒤 불려오는 콜백메서드를 위한 어노테이션이다.

이 콜백메서드는 메모리를 비우는 등의 동작을 수행하여 테스트함수의 결과가 겹치지 않도록 하는 역할을 수행한다.

 

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

@ResponseBody 를 통해 API 방식으로 response처리를 할 수 있다.

@GetMapping("hello-string")
    @ResponseBody  // html의 <body>가 아니라 HTTP의 body에 return값을 직접 넣겠다는 의미
                   // html 태그를 통해 전달하는 것이 아니라 문자열만 그대로 보내는 것
    public String helloString(@RequestParam("name") String name) {
        return "hello " + name;
    }

    @GetMapping("hello-api")
    @ResponseBody  // 이렇게 객체를 넘기게 되면 JSON 형식으로 화면에 띄워준다
    public Hello helloApi(@RequestParam("name") String name) {
        Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }


    static class Hello {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

String을 그대로 띄워주는 방식은 거의 사용하지 않는다.

대부분이 객체를 전달하는 방식으로 사용된다.

 

@GetMapping에 의해 helloApi()가 호출되고 @ResponseBody에 의해 helloApi()의 return값을 그대로 HTTP body에 띄워준다.

이떄 return값에 따라 각각 다른 Converter가 작동한다.

객체가 return되면 MappingJackson2HttpMessageConverter가 작동하는 것으로 기본 등록되어있다.

(Jackson : 객체 -> JSON으로 변환해주는 라이브러리)

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

+ Recent posts