일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- nestJS
- Nodejs
- AWS
- git
- 가상면접사례로배우는대규모시스템설계기초
- 스프링
- spring boot
- 해시
- @Component
- 파이썬
- OpenCV
- 시스템호출
- 코딩테스트
- 구조체배열
- 카카오 코테
- C언어
- thymeleaf
- 알고리즘
- Spring
- 카카오
- @Autowired
- nestjs typeorm
- TypeORM
- nestjs auth
- python
- 코테
- 카카오 알고리즘
- 컴포넌트스캔
- C++
- 프로그래머스
- Today
- Total
공부 기록장 💻
[Spring] 회원 서비스 개발 및 테스트 (Business Logic Service and Test Code), 서비스와 레포지터리의 의존 관계 주입(Dependency Injection) 본문
[Spring] 회원 서비스 개발 및 테스트 (Business Logic Service and Test Code), 서비스와 레포지터리의 의존 관계 주입(Dependency Injection)
dream_for 2023. 1. 10. 10:14인프런 "스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술" 정리
회원 서비스 개발
회원의 Domain과 Repository를 활용하여 비즈니스 로직 역할을 하는 회원 서비스를 작성해보자.
Repository에는 직접 DB에 회원 정보를 저장하거나 어떤 특정 값을 이용해 회원을 찾는 등의 save(), findById(), findByName(), findAll() 등의 메서드를 포함하고 있었다.
Service에는 보다 비즈니스적 로직을 갖는 메서드들을 작성하게 된다. Repository에는 조금 더 기계적인, DB에 단순하게 접근하도록 하는 메서드 이름을 지었던 반면, Service에 포함될 메서드의 경우 조금 더 비즈니스에 의존적인 이름을 짓게 된다.
회원 이름 중복이 되지 않도록 이름 검증을 한 후에 회원 정보를 저장하는 회원 가입의 join() 메서드, 전체 회원을 조회하는 findMembers() 메서드를 작성해보자.
우선 위와 같이 hellospring 패키지 내에 service 패키지를 만들고, MemberService 클래스를 생성하자.
회원 가입 join()
MemoryMemberRepository 클래스의 객체인 memberRepository를 아래와 같이 선언하고,
회원 가입을 하는 join() 메서드의 틀을 생성해보자.
인자로 받은 Memeber 객체를 메모리에 저장하고 최종적으로 해당 객체의 id 값을 반환하게 되는 메서드이다.
이때 같은 이름을 가지는 중복 회원은 가입되지 못하도록 하기 위해, memberRepository에 member 객체의 이름을 가진 회원이 있는지 확인을 하는 코드를 작성해보자.
Optional 클래스로 값 result를 받아, result에 null이 아닌 실제 값이 포함되어 있는 경우 에러를 처리하도록 다음과 같이 작성한다. (Optional을 통해 값을 한 번 감싸는 방법을 실무에서 많이 사용하며, 값을 바로 받아 if ~ null 문으로 처리하는 코드보다 아래 코드와 같이 사용하는 경우가 많다고 한다.)
중복을 확인하는 두 문장은 Extract method 기능을 통해 또다른 메서드로 추출을 해보자. (Windows 단축키 Ctrl + alt + m)
다음과 같이 ValidateDuplicateMember() 라는 이름의 새로운 메서드로 따로 분리하였다.
전체 회원 조회 findMembers(), 회원 조회 findOne()
이제 전체 회원을 조회하고, id 값을 이용해 한 명의 회원을 조회하는 서비스 메서드 findMembers(), findOne() 을 다음과 같이 작성해주자.
회원 서비스 테스트
이제 위에서 만든 회원 서비스를 테스트해보도록 하자.
IntelliJ에서 제공하는 Create New Test shortcut 을 이용해 MemberServiceTest 를 만들어보자.
확인해보면 아래와 같은 틀을 자동적으로 만들어준다.
MemberService의 각 메서드에 대한 테스트 코드 메서드가 나타나는 것을 확인할 수 있다.
회원 가입 Test
회원 서비스에 대한 테스트를 해야 하므로, 가장 우선적으로 MemberSerivce 객체를 생성하자.
이후 join() 메서드의 이름을 회원가입() 으로 바꾸자. (production level 코드가 아니므로, 개발하는 사람의 입장에서 보기 편하게 메서드의 이름은 한글로 작성하자.)
Test Case 코드를 표현하는 방식으로는 Given-When-Then 패턴이 자주 사용된다.
테스트를 수행하기 전 테스트에 필요한 환경을 설정하는 Given 단계 (변수 정의, 객체의 특정 상황에 대한 행동 정의),
테스트의 목적을 보여주는 단계인 When 단계 (테스트를 통한 결괏값 가져오기),
그리고 테스트의 결과를 검증하는 단게인 Then 단계 (when 단게에서 나온 결괏값을 검증하는 작업 수행)
으로 구성할 수 있다.
위의 패턴을 이용해 회원가입() 테스트 메서드를 작성해보면,
Given 단계에서는 Member 객체의 이름을 "hello" 로 지정하였고,
When 단계에서는 memberService를 통해 해당 member 객체의 회원가입을 진행하여 해당 회원의 id 값을 saveId에 저장하였고,
Then 단계에서는 saveId 값으로 memberService를 통해 회원을 찾아 findMember에 해당 객체를 저장하여, 결과적으로 Assertions.assertThat() 을 통해 member의 이름과 findMember의 이름이 동일한지 결과값을 검증하였다.
테스트케이스가 통과한 것을 확인할 수 있다.
중복 회원 가입 예외 Test
이제 단순히 회원 가입 Test에서 넘어, 중복 회원 예외에 대한 Test Code를 작성해보도록 하자.
memberService에서 작성한, 동일한 이름의 회원이 이미 가입되어 있는지 확인하는 validateDuplicateMember() 메서드를 검증해볼 것이다.
중복회원_예외() 라는 이름의 테스트 메서드를 작서하자.
마찬가지로 Given-When-Then 패턴을 이용해 내부 코드를 작성하면 다음과 같다.
spring 이라는 이름의 member1, member2 객체를 생성하고,
memberService를 통해 우선 member1이 join 하도록 한다.
이후에 member2가 join 하는 경우, fail() 메서드가 실행되며, IllegalStateException 에러가 발생하는 것을 가정한다.
그리고 최종적으로 이 에러 메시지가 "이미 존재하는 회원입니다." 와 동일한지 검증하는 것이 위 테스트의 목표일 것이다.
결국 위의 중복회원 테스트 케이스는 통과하지만, 문제는 "spring" 이라는 이름의 회원이 가입될 때가 아니다.
진짜 문제는 "hello"라는 이름의 회원이 가입하려는 경우를 살펴볼 수 있다.
현재 우리는 각 테스트 메서드들이 독립적으로 실행될 수 있도록 MemberService 객체를 MemberServiceTest 클래스 내에 선언하여 사용하기로 했다.
그러나 아래와 같이 중복회원 예외 발생 테스트케이스에서 가입하려는 두 회원의 이름이 "hello" 인 경우라면,
이미 회원가입 케이스에서 가입한 "hello" 라는 회원과 충돌하여 member1 객체를 저장할 때부터 에러가 발생할 것이다.
이 문제를 어떻게 해결할 수 있을까?
우리는 모든 테스트 케이스가 독립적으로 실행될 수 있도록 하기 위해서,
각 케이스마다 각기 다른 MemberRepository 메모리를 사용하는 독립적인 MemberSerivce를 사용하도록 해야 한다.
MemberService에 MemberRepository 의존성 주입(DI, Dependency Injection) 으로 해결해보자
이는 MemberService에 MemberRepository를 주입하여 서비스 내에서 메모리를 관리할 수 있도록 구조를 조금 변형함으로써 해결할 수 있다.
우선 MemberService 에서 하나의 메모리를 이용하도록 private final 형으로 MemeberRepository 변수를 선언하고,
MemberService 생성자 내부에서 생성되는 MemoryMemberRepository 클래스 객체를 해당 변수에 담아보자.
이렇게 MemberSerivce 내에 외부의 MemberRepository 객체를 주입하는 것을 의존관계(DI, Dependency Injection) 이라 한다.
그러면 이제 MemberServiceTest에서, 각 테스트가 실행될 때마다 새로운 MemberService 객체를 생성함과 동시에 MemberRepository를 해당 서비스 내에서 사용할 수 있도록 DI를 주입해주면 각 테스트가 독립적으로 작동할 수 있게 된다.
MemeberServiceTest 클래스에서는 MemberSerivce, MemberRepository 변수를 선언한 후에,
@BeforeEach 어노테이션을 사용해 beforeEach() 메서드를 작성하여, 각 테스트가 실행되기 전에 MemberService 객체가 생성되도록 하자.
그리고 @AfterEach 어노테이션을 이용해 clear() 메서드에는, 각 테스트 실행 후 memberRepository 메모리를 전부 비우도록 구현을 하자.
이로써 비즈니스 로직을 담당하는 회원 서비스를 개발하고,
각 회원 서비스의 메서드들에 대해 테스트 케이스를 작성하여 서비스 검증을 해보았다.
그리고 각 테스트 케이스가 독립적으로 실행될 수 있도록 MemberService에 Repository의 의존 관계를 주입해보았다.