진짜 개발자
본문 바로가기

FrameWork/Spring Boot

Spring Boot - Service Layer 테스트하기

728x90
Spring Boot Service Layer 테스트하기

이번 포스팅에서는 Spring Boot에서 Service Layer를 테스트 하는 방법에 대해 알아보도록 하겠습니다.

 

 

 

1. Test 대상 프로젝트


 

 

1.1 의존성 (build.gradle)

꼭 필요한 의존성은 위 세줄입니다. 간단히 말씀드려, Lombok, spring-boot-starter-test가 필요합니다.

 

 

1.2 Class 별 설명

Member.class

Member Entity 클래스입니다. memberId(식별자 ), name, age 세 필드를 가지고 있습니다.

 

 

MemberService.class *

Test 대상인 MemberService클래스입니다. MemberRepository에 의존하여 Database에 Member를 저장하는 로직인 addMember(CreateMember createMember)가 테스트 대상 메소드입니다.

 

 

CreateMember.class

커맨드 객체입니다. 이 객체를 MemberService의 addMember의 매개변수로 전달하며 호출하여 새로운 Member를 등록합니다.

 

 

MemberRepository

Member Repository입니다. 구현을 위해서는 JPA의존성이 필요하지만, 사실 구현하지 않고 빈 Interface로 두어도 상관 없기 때문에, 앞서서 JPA의존성을 추가하지 않았습니다.

 

 

 

 

2. Service 테스트 유의사항

2.1 Service Layer는 Unit Test

Service Layer는 Unit-Test를 실시합니다. 당연하게도, Service Layer가 잘 동작하는지 확인 하기 위해서는 Service Layer만 독립적으로 테스트를 실시해야합니다. 하지만, 이를 간과하는 경우가 종종 발생합니다. 아래에서 예시를 살펴보겠습니다.

 

위 테스트는 정상적으로 작동합니다. 하지만 여러가지 문제점들이 존재합니다. 이를 파악하기 위해서는 마틴 엉클 밥 선생님 께서 작성하신 F.I.R.S.T에 대해 알아야합니다. (https://dzone.com/articles/writing-your-first-unit-tests)

 

Fast

단위 테스트는 가능한 빠르게 실행되어야 합니다. 실행함에 있어 너무 느려 테스트 실행을 꺼리게 된다면 잘못된 단위테스트입니다.

=> 하지만 위 테스트중 @SpringBootTest어노테이션은 해당 어플리케이션의 모든 빈을 IoC Container에 등록하고 테스트를 진행하기 때문에 테스트가 느려질 수 밖에 없습니다. (위 테스트 실행시 제 환경에서는 약 8초가 걸립니다.)

 

Independent

단위테스트는 객체의 상태, 메소드, 이전 테스트 상태, 다른 메소드의 결과등에 의존해서는 안됩니다. 따라서 단위테스트는 어떠한 순서로 실행하더라도 성공해야 합니다.

=> 하지만, MemberRepository 에 의존을 하고 있어, 한번 실행한 뒤에는, 이미 중복된 id가 Database에 존재하기 때문에, 실패하게 됩니다. 물론 @Transactional어노테이션을 부여하면, 실행후 DB를 롤백하기 때문에 실패하지 않지만, 테스트 대상이 MemberRepository인지, MemberService인지 모호해집니다.

 

Repeatable

단위테스트는 반복 가능해야합니다. 앞서 DB에 의존하게되는 현상과 같이 여러번 실행하는 경우 실패하면 안됩니다.

 

Self-validating

단위테스트는 자체검증이 가능해야합니다. 테스트를 개발자가 직접 수동으로 확인할 필요 없이, Assert문 등에 의해 성공 여부가 결과로 나타나야합니다.

 

Timely

단위테스트를 통과하는 제품코드가 작성되기 바로전에 단위테스트를 작성해야합니다. TDD를 하고 있다면 적용이 되지만 그렇지 않을 수도 있습니다.

 

2.2 테스트를 위해 제품코드를 변경하면 안된다

우선, Unit-Test 무엇일까요? 일반적으로, 개발자가 작성하여 실행하는 자동화된 테스트로, 응용 프로그램의 단위가 의도한대로 작동하는지를 확인하는것입니다.

따라서, 코드가 잘 동작하는지 확인하기 위해 작성하는 것(TEST)에 의해 제품코드가 변경되지 않아야하는 것은 당연한 사실입니다.

 

 

2.3 테스트도 가독성이 좋아야 한다

문서로서의 테스트코드

테스트코드도 가독성이 좋아햐합니다. 테스트 코드는 프로젝트에 이제막 뛰어든 사람에게 매우 귀중한 자료입니다. 각각의 메소드들 클래스들을 작성한 개발자가 어떤 의도를 가지고 메소드를 만들었는지 테스트 코드를 통해 유추할 수 있기 때문입니다. 즉, 테스트 코드는 문서로서의 역할을 하기도 합니다.

 

변화하는 테스트코드

테스트코드는 제품코드를 테스팅하는 코드입니다. 따라서, 제품코드가 변경되면 테스트 코드역시 변경되어야 합니다. 때문에 테스트 코드의 가독성도 중요합니다.

 

 

 

 

3. 테스트

MemberServiceTest.class

앞선 유의사항에 주의하며, 작성한 코드입니다.

 

  1. 빠르다

위 테스트코드는, @SpringBootTest대신, @InjectMocks, @Mock, @ExtendWith(MockitoExtensions.class) 를 사용해서, IOC Container가 생성되지 않으며(Spring에 의존하지 않으며), 따라서, 필요한 MemberService 객체만 실제로 생성되어 매우 빠른 테스트를 제공합니다.

 

  1. 의존하지 않는다

또한, Mockito라이브러리를 이용하여, MemberRepository에 대한 의존성을 제거하여, 위 메소드만을 독립적으로 또한 반복적으로 테스트가 가능합니다.

 

 

Unnecessary(Mockito 2.x 버전의 사용하지 않는 스텁감지)

Mockito 사용시 위와 같은 에러가 발생하는 경우가 있는데요, given()을 이용해 만들어낸 스텁이 사용되지 않은 경우 발생하는 현상입니다.

따라서 해당 스텁을 제거하거나, assert에 사용하면 해결이 됩니다.

 

 

Stub 과 Mock

Stub과 Mock은 서로 비슷하지만 다른말인데요, 분명 다른 말이니 짚어보는것이 좋습니다.

 

Stub

Stub은 사전에 준비한 가짜 답(데이터)을 의미합니다. 예를들어, Order객체는, 외부 결제 API에 의존하고 있다고 해보겠습니다. 이때, Order객체의 주문을 테스트 하기 위해서는, 외부 결제 API의 응답을 기다려야 합니다. 따라서 테스트가 독립되지 못합니다. 하지만 이때 Stub을 사용하면 해결할 수 있습니다. 외부 결제 API가 반환하는 값은 결제의 성공여부 입니다. 따라서 Stub에 성공, 실패를 미리 입력한뒤 주문행위를 테스트 하면 됩니다.

 

Mock

Mock은 행위를 테스트하는 것을 의미합니다. (아직 이해가 가지 않아 링크를 첨부합니다.)

https://medium.com/@SlackBeck/mock-object%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-85159754b2ac