인프런 커뮤니티 질문&답변

kim님의 프로필 이미지

작성한 질문수

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발

회원 기능 테스트

회원기능 테스트에서 질문드립니다.

해결된 질문

19.10.06 14:37 작성

·

2.2K

0


dependencies
{
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools'
compile('com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.7')
compileOnly
'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

spring:
datasource:
url: jdbc:h2:tcp://localhost/~/Dev/projects/book/db;MVCC=TRUE
username: sa
password:
driver-class-name: org.h2.Driver

jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
show_sql: true
format_sql: true

logging:
level:
org.hibernate.SQL: debug
org.hibernate.type: trace
package com.ym.book.shop.service;

import com.ym.book.shop.domain.entity.Member;
import com.ym.book.shop.repository.MemberRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;

@Test
public void 회원가입()throws Exception{
//given
Member member = new Member();
member.setName("Kim");
//when
Long saveId = memberService.join(member);
//then
assertEquals(member, memberRepository.findOne(saveId));
}

@Test
public void 중복_회원_예외()throws Exception{
//given

//when

//then
}

}
결과 :
2019-10-06 14:52:56.074 INFO 2244 --- [ Test worker] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory 2019-10-06 14:52:56.173 DEBUG 2244 --- [ Test worker] org.hibernate.SQL : select member0_.member_id as member_i1_4_, member0_.city as city2_4_, member0_.street as street3_4_, member0_.zipcode as zipcode4_4_, member0_.name as name5_4_ from member member0_ where member0_.name=? Hibernate: select member0_.member_id as member_i1_4_, member0_.city as city2_4_, member0_.street as street3_4_, member0_.zipcode as zipcode4_4_, member0_.name as name5_4_ from member member0_ where member0_.name=? 2019-10-06 14:52:56.204 DEBUG 2244 --- [ Test worker] org.hibernate.SQL : insert into member (member_id, city, street, zipcode, name) values (null, ?, ?, ?, ?) Hibernate: insert into member (member_id, city, street, zipcode, name) values (null, ?, ?, ?, ?) 2019-10-06 14:52:56.216 INFO 2244 --- [ Test worker] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@4d40c3e testClass = MemberServiceTest, testInstance = com.ym.book.shop.service.MemberServiceTest@b322034, testMethod = 회원가입@MemberServiceTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@1ee9b049 testClass = MemberServiceTest, locations = '{}', classes = '{class com.ym.book.shop.ShopApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@275315df, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@47bf348f, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7db5eaa6, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@2cda0ac3], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]] 2019-10-06 14:52:56.223 INFO 2244 --- [ Thread-6] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor' 2019-10-06 14:52:56.223 INFO 2244 --- [ Thread-6] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2019-10-06 14:52:56.226 INFO 2244 --- [ Thread-6] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2019-10-06 14:52:56.231 INFO 2244 --- [ Thread-6] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. BUILD SUCCESSFUL in 5s 5 actionable tasks: 3 executed, 2 up-to-date 2:52:56 PM: Tasks execution finished ':cleanTest :test --tests "com.ym.book.shop.service.MemberServiceTest.회원가입"'.

이렇게 결과가 나옵니다.
select, insert가 2번씩 실행이 되는데요
혹시 제가 설정이 잘못된 게 있을까요?

답변 6

6

김영한님의 프로필 이미지
김영한
지식공유자

2019. 10. 06. 23:38

안녕하세요 kim님 연타성 질문이니 하나씩 답을 드릴께요^^

1. 다른 프로젝트랑 비교해봤는데 보통 [ main] 이런식으로 쓰레드를 표기하는데 제꺼는 [ test worker]로 쓰레드가 표기되네요  혹시  gradle version이 문제일까요?

최근 intellij 버전이 올라가면서 자바로 바로 실행해야 하는데, gradle로 실행하는게 디폴트가 되었습니다. 다시 자바로 바꾸려면 다음처럼 하시면 됩니다^^

Preferences 메뉴에 들어가셔서

Build, Execution, Depolyment -> Build Tools -> Gradle에 들어가면

Build And run using: -> 이것을 Gradle, Intellij 중에서 Intellij를 선택해주면 됩니다.

Run Test using: -> 이것도 Gradle, Intellij 중에서 Intellij를 선택해주면 됩니다.

만약 잘 안되면 다음 쓰레드를 읽어주세요

https://www.inflearn.com/questions/14470

2. show-sql이 보니깐 강사님이 주석 처리하셨던데 저도 주석처리하니 1개로 정상적으로

-> show-sql은 System.out으로 출력하기는 것이기 때문에, 가급적이면 로그로 출력하는 기능을 사용하는게 좋습니다. 실무에서는 모든 출력을 로그를 통해서 관리해야 합니다.

3. insert문이 저는 실행이 됩니다. join에서 영속성을 갖고 Long id가 생성되고 findOne을 할 때 같은 Id가 있기때문에 insert select가 아닌 영속성 콘텍스트에서 가지고와서 비교를 하고 마지막은 transaction이 끝나므로 insert가 실행된다고 생각하면 될까요?

-> JPA는 영속성 컨텍스트에 엔티티를 등록할 때 항상 PK 값이 있어야 합니다. 그런데 키 생성 전략을 IDENTITY로 두면 데이터베이스에 INSERT를 해야 PK를 구할 수 있습니다. IDENTITY라는 것이 데이터베이스에 PK 생성을 위임하는 것이기 때문이지요. 대표적으로 MySQL에 auto increment가 있습니다. 그래서 IDENTITY 전략은 PK를 구하기 위해 어쩔 수 없이 DB에 INSERT를 강제로 먼저 하게 됩니다. 참고로 나머지 전략은 DB에 INSERT를 강제로 하지 않습니다.

관련해서 더 자세한 내용은 자바 ORM표준 JPA 프로그래밍 강의에 있는 기본 키 매핑 부분을 참고해주세요.

4. @Transactional을 지워주면 fail이 납니다.

-> 네 맞습니다^^ fail이 납니다! 이것을 이해하는게 정말 중요합니다. 결론부터 말씀드리면 트랜잭션 당 하나의 영속성 컨텍스트가 적용됩니다. 다르게 말하면 트랜잭션이 달라지면 영속성 컨텍스트도 달라집니다.

스프링은 기본적으로 @Transactional을 사용하면 시작부터 끝까지 트랜잭션을 하나로 묶어서 전파합니다. 여기서 테스트에 @Tx(Transactional)가 있으면 이 테스트 시작부터 끝날 때 까지 모든 트랜잭션은 별도의 옵션이 없으면 다 하나로 묶입니다. 결론적으로 영속성 컨텍스트도 하나로 유지됩니다.

그런데! 테스트에서 @Tx를 제거하면 어떻게 될까요?

memberService.join()에서 사용하는 트랜잭션과 memberRepository.findOne()에서 사용하는 트랜잭션이 묶이지 않기 때문에 서로 완전히 상관없는 트랜잭션이 됩니다. 결과적으로 영속성 컨텍스트도 각각 다르기 때문에, 서로 다른 객체를 저장하고 있는 것이지요.

관련해서 더 자세한 내용은 자바 ORM 표준 JPA 프로그래밍 책 13.1 트랜잭션 범위의 영속성 컨텍스트 부분과 15.2 엔티티 비교 부분을 참고하시면 됩니다.

감사합니다^^

1

kim님의 프로필 이미지
kim
질문자

2019. 10. 07. 08:40

감사합니다. 이해가 돠었습니다

0

kim님의 프로필 이미지
kim
질문자

2019. 10. 06. 15:53

1번 질문에 대한 추가 내용입니다.

아마도 생성전략이 달라서 그런것 같습니다.

이렇게 이해하면 될가요?

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;

0

kim님의 프로필 이미지
kim
질문자

2019. 10. 06. 15:45

추가적인 질문인데요.

1.  insert문이 저는 실행이 됩니다. join에서 영속성을 갖고 Long id가 생성되고 findOne을 할 때 같은 Id가 있기때문에 insert select가 아닌 영속성 콘텍스트에서 가지고와서 비교를 하고 마지막은 transaction이 끝나므로 insert가 실행된다고 생각하면 될까요?

2. @Transactional을 지워주면 fail이 납니다.

expected:<com.ym.book.shop.domain.entity.Member@3f9bc39c> but was:<com.ym.book.shop.domain.entity.Member@b8a4a4b>

Expected :com.ym.book.shop.domain.entity.Member@3f9bc39c

Actual   :com.ym.book.shop.domain.entity.Member@b8a4a4b

이런식으로 나는데요. 이해가 정확히 되지가 않습니다.

0

kim님의 프로필 이미지
kim
질문자

2019. 10. 06. 15:39

show-sql이 보니깐 강사님이 주석 처리하셨던데 저도 주석처리하니 1개로 정상적으로 보입니다.

혹시나 저와 같은 궁금증이 생기실 분도 계실거 같아 이 질문은 그대로 두겠습니다.

0

kim님의 프로필 이미지
kim
질문자

2019. 10. 06. 15:16

다른 프로젝트랑 비교해봤는데 보통 [ main] 이런식으로 쓰레드를 표기하는데 제꺼는 [ test worker]로 쓰레드가 표기되네요  혹시  gradle version이 문제일까요?

kim님의 프로필 이미지

작성한 질문수

질문하기