예제 코드
public class RepositoryRank {
interface GithubService {
GitHub connect() throws IOException;
}
class DefaultTestService implements GithubService {
@Overridwe
public Github connect() throws IOException {
return Github.connect();
}
}
----------------------------------------
private GithubService gitHubService;
public RepositoryRank(GithubService gitHubService){
this.gitHubService = gitHubService;
}
public int getPoint(String repositoryName) throws IOException {
GitHub gitHub = GitHub.connect(); //올바르지 않은 방법
GitHub gitHub2 = gitHubService.connect(); //올바른 방법
"스프링 제대로 공부했는지 5분안에 확인하는 방법"이라는 영상에서 나온 내용이다.
첫번째 문제 - API에 대한 반복 접근
위의 코드의 문제점은 RepositoryRank 클래스에서 getPoint 메소드가 실행 될 때 Github.connect() 라는 static 메소드를 통해 API 연결을 실행한다.
테스트를 할 때마다 해당 API를 연결하는 건 자원 소모가 크고 API 특성상 연결이 끊길 수도 있고 반환 값이 달라질 수도 있다. 그렇기에 Github에 대한 가짜 객체(Stub)를 만들어주고 해당 객체를 반환해주는 방식으로 만들어야 한다.
Github github = mock(Github.class);
when(github.connect()).thenReturn("");
두번째 문제 - Static 메소드에 대한 테스트의 어려움
가짜 객체를 만들어주는 것은 좋은데, 아마 그 객체를 만들기가 꽤나 어려울 것이다. 대표적으로 사용하는 Mockito 프레임워크를 사용했을 때 Static 메소드를 테스트하는 경우 테스트가 되지 않는다는 에러를 만나게 된다.
왜냐면 기본적으로 Mocktio의 동작 방식은 가짜 클래스를 만들어 추상화하는 개념으로 동작하는데, 이 과정에서 Static 메소드는 일반 메소드와 다르게 상속이 되지 않기 때문이다.
public class TestServiceMock extends TestService {
@Override
public void test(){ //메소드가 static인 것들은 Override 사용 불가
}
}
만약에 Static 메소드를 테스트 하려고 하면
when() requires an argument which has to be 'a method call on a mock'.
왜냐면 @Mock이 붙은건 프록시 객체를 만들어주는데 Static 메소드는 프록시 객체가 원본 객체를 상속할 때 오버라이드 하지 않기 때문임.
해결방법 1 - Static 메소드 테스트 프레임워크 설치하기
그렇기에 Mockito 프레임워크 3.4.0 에서는 mockStatic()을 통해 static 메소드를 테스트 하는 방법을 제공하고 있다.
(You need to use mockito-inline version 3.4.0 or higher and remove mockito-core from your dependencies)
testImplementation("org.springframework.boot:spring-boot-starter-test") {
// mockito-inline instead of mockito-core is needed in order to mock static methods
exclude("org.mockito", "mockito-core")
}
testImplementation("org.mockito:mockito-inline")
inline을 추가하지 않아도 MockedStatic 클래스를 사용할 수 있긴한데 기능의 차이가 있다.
- mockedStatic은 정적 메서드만 모킹할 수 있습니다.
- mockedStatic은 정적 메서드의 인수를 매개변수로 전달할 수 없습니다.
- mockedStatic은 정적 메서드의 반환 값을 조작할 수 없습니다.
2023년 초에는 mockito-core에 mockito-inline이 포함되었기 때문에
아니면 spring-boot-starter-test 버전을 올려서 mockito-core를 5.3.0으로 맞추거나
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.mockito:mockito-core:5.3.0")
이렇게 mocktiro-core 버전만 따로 올리면 된다.
해결방법 2 - Mocktio를 이용하지 않고 해결하기
일단 static 메소드를 일반 메소드로 감싸고 호출해버리면 그만이다.
public static String staticMethod() {
return "dddd";
}
public String getStaticMethodReturn() {
return staticMethod();
}
여기서 getStaticMethodReturn을 사용해서 이것만 Stub하면된다.
DI개념을 사용할 수도 있다. 여기서 PSA와 IoC의 개념이 들어간다.
Mockito를 이용하지 않고 해결하려면 Mockito의 원리처럼 해당 테스트 대상이 될 클래스를 추상화(PSA)해서 다시 구현시키면 된다.
스프링의 PSA의 원리, PSA는 대표적으로 @Transactional, @RequestMapping과 같은 어노테이션을 말하며 스프링에서 자체 제공해주는 추상화 기능을 말한다
그리고 추상화된 객체를 주입 받는 형태의 의존성 주입(IoC)의 개념도 사용된다.
public RepositoryRank(GithubService gitHubService){
this.gitHubService = gitHubService;
}
이렇게 하면 API에 대한 반복 접근은 해결되며 객체 지향 개념(그 중에 상속)을 사용할 수 없었던 static에 대한 기능을 빼버리고 새롭게 구현도 가능하다. 왜냐면 단순히 Github 타입의 리턴만 제공하면 되는 것이기 때문에 static 메소드로부터 쉽게 벗어날 수 있기 때문이다.
@Overridwe
public Github connect() throws IOException {
return Github.connect(); //가짜 구현체를 만들 영역
}
일반 메소드로 감싸는 것에 비해서 이렇게 하면 어디서든 갖다 쓸 수 있고 유연하다는 특징이 존재한다.
'🗂️ Etc' 카테고리의 다른 글
Windows에 Redis 설치하기 (0) | 2022.10.03 |
---|---|
다이나믹 쿼리 사용 시 간과할 수 있는 개인정보 유출 문제(Feat. iBatis, myBatis, QueryDsl) (0) | 2022.09.08 |
개발자도구로 특정 이벤트 찾는 법 (1) | 2022.07.29 |
앱에서 크롬 개발자도구 여는 법(eruda.js) (0) | 2022.07.28 |
세션 탈취해서 로그인하기(세션 하이재킹) feat. 쿠키와 세션 실습 (2) | 2022.07.27 |