이번에 그로우 업 프로젝트를 진행하면서
같이 프로젝트를 하는 친구와 TDD를 도입 해보도록 하였습니다.
TDD 는 테스트 주도 개발이라고 합니다. 반복 테스트를 이용한 소프트웨어 방법론으로 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현합니다.
실패하는 테스트 코드를 작성할 때까지 실제 코드를 작성하지 않는 것과, 실패하는 테스트를 통과할 정도의 최소 실제 코드를 작성해야 하는 것입니다.
이를 통해 실제 코드에 대해 기대되는 바를 보다 명확하게 정의함으로써 불필요한 설계를 피할 수 있고, 정확한 요구사항에 집중할 수 있습니다.
TDD 의 효과로 디버깅 시간을 단축하고, 작성한 코드가 가지는 불안정성을 개선해 생산성을 높일 수 있습니다.
보통 테스트를 위해 JUnit 과 AssertJ 조합을 사용하여 테스트 합니다.
given : 데이터가 주어질때
when : 기능 실행할때
then : 결과
Mockito 를 사용해 단위 테스트를 하는데 Mockito 는 Java 언어를 위한 모의 객체 프레임 워크 중 하나로 주로 단위테스트 시에 다른 객체와의 협력을 시뮬레이션하거나 대체하기 위해 사용합니다.
일반적으로 Mockito를 사용하면 가짜 객체를 만들고, 그 객체의 동작을 제어하고 예상대로 동작하도로 가짜 객체를 구성합니다. 스프링 프로젝트의 경우 극 클래스 빈에 등록되어 Spring 에 관리되어 서로 의존성이 부여되기 때문에
하나를 테스트 하기 위해 다른 모든것을 일일이 값을 넣어 객체 만들고 설정한 후 동작시키는 번거러움 때문에 가짜 객체를 사용합니다.
자세한 내용은 계속 진행하면서 알아보겠 습니다.
1. Repo 테스트
Repository Layer 단위 테스트가 필요한 이유는 RDBMS, Cache, Queue 등 저장 컴포넌트와 구체적인 내용과는 상관이 없이 저장, 삭제, 조회 등을 일관된 형태로 접근할 수 게 하고 서비스 레이어 에서 구체적인 데이터 접근 방식을 모르게 하여 의존성을 줄이게 만드는 것입니다.
즉. 특정 DB를 사용하고 어떻게 접근할지에 대한 구체적인 접근 방식을 정의하는 Layer이기 때문에 데이터의 저장, 삭제, 조회가 잘 이루어지는지만 테스트하면 되고 구체적인 구현에 대해서는 테스트 할 필요가 없다.
환경
Repo Layer 에서는 통합 테스트와는 달리 Application Context 에 Repo를 테스트 하기 위한 Bean 들만 최소화 하여 로딩 해야하고, 테스트를 위한 DB가 필요하다
이를 위해 나온게 @DataJpaTest 라는 어노테이션이다.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.test.autoconfigure.orm.jpa;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration;
import org.springframework.boot.test.autoconfigure.core.AutoConfigureCache;
import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.properties.PropertyMapping;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.repository.config.BootstrapMode;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})
@OverrideAutoConfiguration(
enabled = false
)
@TypeExcludeFilters({DataJpaTypeExcludeFilter.class})
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
String[] properties() default {};
@PropertyMapping("spring.jpa.show-sql")
boolean showSql() default true;
@PropertyMapping("spring.data.jpa.repositories.bootstrap-mode")
BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
@AliasFor(
annotation = ImportAutoConfiguration.class,
attribute = "exclude"
)
Class<?>[] excludeAutoConfiguration() default {};
}
이 클래스에서는 JPA와 관련된 설정만 활성화 되어 Service, Controller 와 관련된 빈은 생성되지 않습니다.
또한 기본적으로 내장형 데이터베이스(H2) 를 사용하도록 합니다.
@transaction 으로 rollback 을 시켜주고 includeFilters와 excludeFilters를 사용하여 특정 컴포넌트(예: 특정 엔티티 매핑)를 포함하거나 제외할 수 있습니다.
TDD를 사용하기 전에 이미 코드가 짜여져 있어서 앞 부분(Member, Role 부분)이 없습니다.
@DataJpaTest
class MemberRepositoryTest {
@Autowired
private MemberRepository memberRepository;
@Test
public void success_createDB() {
assertThat(memberRepository).isNotNull();
}
@Test
public void success_saveData() {
// given
final Member member = Member.builder()
.email("test@naver.com")
.role(Role.GOLD)
.build();
// when
final Member savemember = memberRepository.save(member);
// then
assertThat(savemember.getEmail()).isEqualTo("test@naver.com");
assertThat(savemember.getRole()).isEqualTo(Role.GOLD);
}
그 다음엔 중복 검사를 해야하는 테스트를 해야한다. 그 로직을 구현하려면 회원가입을 했는지 안했는지 체크를 하면 된다.
@Test
void success_selectData(){
// given
Member newMember = newMember();
// when
memberRepository.save(newMember);
final Member byEmail = memberRepository.findByEmail("test@naver.com").orElseThrow(NullPointerException::new);
//given
assertThat(byEmail).isNotNull();
assertThat(byEmail.getEmail()).isEqualTo("test@naver.com");
}
public Member newMember(){
return Member.builder().email("test@naver.com").role(Role.GOLD).build();
}
이렇게 하면 Repo 테스트는 끝이다. 다음으로 Service 쪽 개발을 해보도록 하겠다.
댓글